এই আর্টিকেলটি পূর্বের আর্টিকেলের (ID: 1006061523) একটি রিফাইন এবং ডকুমেন্টেশন-ফোকাসড ভার্সন। এখানে একই Server-Client সকেট আর্কিটেকচারটি আরও ক্লিন কোড, বেটার এরর হ্যান্ডলিং, এবং আর্কিটেকচারাল ব্যাখ্যা সহ উপস্থাপন করা হলো।
ক্লায়েন্ট-সার্ভার আর্কিটেকচার ফান্ডামেন্টালস
| কম্পোনেন্ট | দায়িত্ব | অ্যান্ড্রয়েড ক্লাস |
|---|---|---|
| Server | পোর্টে লিসেন করে, ক্লায়েন্ট এক্সেপ্ট করে, ডেটা ব্রডকাস্ট/রাউট করে | ServerSocket, Thread/Executor |
| Client | সার্ভার IP/পোর্টে কানেক্ট করে, রিকোয়েস্ট পাঠায়, রেসপন্স পায় | Socket, Thread/Executor |
| Protocol | মেসেজ ফরম্যাট ডিফাইন করে (লাইন-বেসড, JSON, Protobuf) | Application Layer Logic |
| Transport | TCP (Reliable, Ordered) বা UDP (Fast, Unreliable) | Socket / DatagramSocket |
ডিজাইন প্যাটার্ন: Producer-Consumer with Thread Pool
সার্ভার সাইডে প্রতিটি ক্লায়েন্টের জন্য একটি ডেডিকেটেড থ্রেড বা থ্রেড পুল ব্যবহার করা হয় (Thread-per-Client Model)।
Server Side Architecture
┌─────────────────────────────────────────────────────────────┐
│ MAIN THREAD (UI) │
│ - View Updates (runOnUiThread) │
│ - User Input Handling │
└─────────────────────────────────────────────────────────────┘
│
┌─────────▼─────────┐
│ EXECUTOR SERVICE │ (FixedThreadPool / CachedThreadPool)
│ - Acceptor Task │ → ServerSocket.accept()
│ - Reader Tasks │ → input.readLine() per client
└───────────────────┘
রিফাইন্ড সার্ভার কোড (Production Ready Pattern)
public class SocketServer {
private final int port;
private ServerSocket serverSocket;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final List clients = Collections.synchronizedList(new ArrayList<>());
private final ServerCallback callback;
public interface ServerCallback {
void onClientConnected(String clientId);
void onMessageReceived(String clientId, String message);
void onClientDisconnected(String clientId);
void onError(String error);
}
public SocketServer(int port, ServerCallback callback) {
this.port = port;
this.callback = callback;
}
public void start() {
executor.execute(() -> {
try {
serverSocket = new ServerSocket(port);
callback.onError("Server started on port " + port);
while (!serverSocket.isClosed()) {
Socket clientSocket = serverSocket.accept(); // Blocking
ClientHandler handler = new ClientHandler(clientSocket);
clients.add(handler);
executor.execute(handler);
}
} catch (IOException e) {
callback.onError("Server error: " + e.getMessage());
}
});
}
public void broadcast(String message) {
for (ClientHandler client : clients) {
client.send(message);
}
}
public void stop() {
try {
for (ClientHandler client : clients) client.close();
if (serverSocket != null) serverSocket.close();
executor.shutdown();
} catch (IOException e) {
callback.onError("Stop error: " + e.getMessage());
}
}
private class ClientHandler implements Runnable {
private final Socket socket;
private PrintWriter out;
private BufferedReader in;
private final String clientId = UUID.randomUUID().toString().substring(0, 8);
private volatile boolean running = true;
ClientHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try {
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
callback.onClientConnected(clientId);
String msg;
while (running && (msg = in.readLine()) != null) {
callback.onMessageReceived(clientId, msg);
}
} catch (IOException e) {
if (running) callback.onError("Client " + clientId + " error: " + e.getMessage());
} finally {
close();
clients.remove(this);
callback.onClientDisconnected(clientId);
}
}
void send(String msg) { if (out != null) out.println(msg); }
void close() { running = false; try { socket.close(); } catch (IOException ignored) {} }
}
}
MainActivity.java Integration (Server)
public class MainActivity extends AppCompatActivity implements SocketServer.ServerCallback {
private SocketServer server;
private TextView tvLog;
private EditText etBroadcast;
private Button btnBroadcast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_main);
tvLog = findViewById(R.id.tvLog);
etBroadcast = findViewById(R.id.etBroadcast);
btnBroadcast = findViewById(R.id.btnBroadcast);
server = new SocketServer(8080, this);
server.start();
btnBroadcast.setOnClickListener(v -> {
String msg = etBroadcast.getText().toString().trim();
if (!msg.isEmpty()) server.broadcast("[Broadcast] " + msg);
});
}
// Callback implementations - ALL run on background thread!
@Override public void onClientConnected(String id) { log("Client connected: " + id); }
@Override public void onMessageReceived(String id, String msg) { log("[" + id + "] " + msg); }
@Override public void onClientDisconnected(String id) { log("Client disconnected: " + id); }
@Override public void onError(String err) { log("ERROR: " + err); }
private void log(String msg) {
runOnUiThread(() -> tvLog.append(msg + "\n"));
}
@Override protected void onDestroy() { server.stop(); super.onDestroy(); }
}
Client Side Refined (Reconnection Logic)
public class SocketClient {
private final String host;
private final int port;
private Socket socket;
private PrintWriter out;
private BufferedReader in;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private ClientCallback callback;
private volatile boolean running = false;
private ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
public interface ClientCallback {
void onConnected();
void onMessage(String message);
void onDisconnected(String reason);
void onError(String error);
}
public SocketClient(String host, int port, ClientCallback callback) {
this.host = host; this.port = port; this.callback = callback;
}
public void connect() {
if (running) return;
running = true;
executor.execute(this::connectInternal);
}
private void connectInternal() {
try {
socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000); // 5s timeout
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
callback.onConnected();
String msg;
while (running && (msg = in.readLine()) != null) {
callback.onMessage(msg);
}
} catch (IOException e) {
callback.onError("Connection failed: " + e.getMessage());
} finally {
cleanup();
if (running) scheduleReconnect(); // Auto-reconnect
}
}
private void scheduleReconnect() {
reconnectScheduler.schedule(this::connectInternal, 5, TimeUnit.SECONDS);
}
public void send(String message) {
if (out != null) out.println(message);
}
public void disconnect() {
running = false;
cleanup();
reconnectScheduler.shutdown();
}
private void cleanup() {
try {
if (in != null) in.close();
if (out != null) out.close();
if (socket != null) socket.close();
} catch (IOException ignored) {}
}
}
প্রোটোকল ডিজাইন: লাইন-বেসড vs ফ্রেমড
লাইন-বেসড (Simple Text)
// Sender
out.println("MESSAGE_TYPE|PAYLOAD");
// Receiver
String line = in.readLine();
String[] parts = line.split("\\|", 2);
String type = parts[0];
String payload = parts.length > 1 ? parts[1] : "";
Pros: সিম্পল, হিউম্যান-রিডেবল, টেলনেট দিয়ে টেস্ট করা যায়।
Cons: বাইনারি ডেটা সাপোর্ট নেই, বড় পেলোডে পারফরম্যান্স ইস্যু।
Length-Prefixed Binary Frame (Robust)
// Sender (4-byte length + payload) byte[] payload = serialize(message); ByteBuffer buffer = ByteBuffer.allocate(4 + payload.length); buffer.putInt(payload.length); buffer.put(payload); outputStream.write(buffer.array()); outputStream.flush(); // Receiver DataInputStream dis = new DataInputStream(inputStream); int length = dis.readInt(); byte[] payload = new byte[length]; dis.readFully(payload); Message msg = deserialize(payload);
Pros: বাইনারি সাপোর্ট, স্ট্রিমিং-ফ্রেন্ডলি, পার্সিং দ্রুত।
Cons: थोড়া জটিল, ডিবাগিং কঠিন।
সিকিউরিটি বিবেচনা (Security Hardening)
- TLS/SSL: `SSLSocketFactory` ব্যবহার করে এনক্রিপশন। সার্ভারের জন্য কিস্টোর, ক্লায়েন্টের জন্য ট্রাস্টস্টোর।
- Authentication: কানেকশন স্থাপনের পর প্রথম মেসেজে টোকেন/সার্টিফিকেট এক্সচেঞ্জ।
- Input Validation: সাইজ লিমিট, স্যানিটাইজেশন, রেট লিমিটিং।
- Network Security Config (Android): `res/xml/network_security_config.xml` দিয়ে ক্লিয়ারটেক্সট ট্রাফিক ব্লক করুন।
Network Security Config Example
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">192.168.1.0/24</domain> <!-- Local only -->
</domain-config>
<!-- Debug build-এ cleartext allow করুন -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
Manifest-এ অ্যাট্যাচ
<application
android:networkSecurityConfig="@xml/network_security_config"
... >
টেস্টিং ও ডিবাগিং টিপস
- Netcat (nc): `nc -lvp 8080` (Server), `nc 192.168.1.5 8080` (Client) — কমান্ড লাইন থেকে টেস্ট।
- Wireshark: প্যাকেট ক্যাপচার করে প্রোটোকল এনালাইসিস।
- Android Studio Network Inspector: রিয়েল-টাইম ট্রাফিক মনিটরিং।
- Unit Test: `MockWebServer` (OkHttp) বা এম্বেডেড `ServerSocket` জুনিট টেস্টে।
পারফরম্যান্স অপ্টিমাইজেশন
| টেকনিক | ব্যাখ্যা |
|---|---|
| NIO (Non-blocking I/O) | Selector + SocketChannel ব্যবহার করে হাজার হাজার কানেকশন হ্যান্ডেল (Netty framework)۔ |
| Message Batching | ছোট মেসেজগুলো বাফারে জমা করে একসাথে পাঠানো (Nagle's Algorithm / TCP_CORK)۔ |
| Zero-Copy | FileChannel.transferTo() ফাইল ট্রান্সফারের জন্য। |
| Compression | বড় পেলোডের জন্য gzip/snappy কমপ্রেশন। |
সারাংশ: কবে Raw Socket ব্যবহার করবেন?
- ✅ লোকাল নেটওয়ার্ক টুলস (ফাইল ট্রান্সফার, রিমোট কন্ট্রোল, গেম)।
- ✅ লিগ্যাসি সিস্টেমের সাথে ইন্টিগ্রেশন।
- ✅ কাস্টম প্রোটোকল ইমপ্লিমেন্টেশন।
- ✅ লো-লেভেল নেটওয়ার্কিং শেখার জন্য।
- ❌ ইন্টারনেট-স্কেল অ্যাপ (WebSocket/gRPC ব্যবহার করুন)।
- ❌ ফায়ারওয়াল/প্রক্সি পিছনে থাকা ক্লায়েন্ট (HTTP/WebSocket ভালো)۔
- ❌ কমপ্লেক্স ডেটা সিঙ্ক (Firebase/Supabase ব্যবহার করুন)।
Raw Socket প্রোগ্রামিং নেটওয়ার্কিং এর গভীর বোঝাপড়া দেয়। কিন্তু মডার্ন প্রোডাকশন অ্যাপে হাই-লেভেল লাইব্রেরি (OkHttp, Ktor, gRPC) ব্যবহার করা উচিত, যদি না খুব 스페সিফিক লো-লেভেল কন্ট্রোল দরকার হয়।