কম্পিউটার টিউটোরিয়াল

অ্যান্ড্রয়েডে ক্লায়েন্ট-সার্ভার প্রোগ্রামিং: সকেট ব্যবহার করে ডেটা এক্সচেঞ্জ

এই আর্টিকেলটি পূর্বের আর্টিকেলের (ID: 1006061523) একটি রিফাইন এবং ডকুমেন্টেশন-ফোকাসড ভার্সন। এখানে একই Server-Client সকেট আর্কিটেকচারটি আরও ক্লিন কোড, বেটার এরর হ্যান্ডলিং, এবং আর্কিটেকচারাল ব্যাখ্যা সহ উপস্থাপন করা হলো।

নোট: এই আর্টিকেলটি Socket Programming Tutorial-র কম্পаньন পিস। কোড লজিক একই, কিন্তু আর্কিটেকচার, ডিজাইন প্যাটার্ন, এবং প্রোডাকশন রেডিনেসে ফোকাস করা হয়েছে।

ক্লায়েন্ট-সার্ভার আর্কিটেকচার ফান্ডামেন্টালস

কম্পোনেন্টদায়িত্বঅ্যান্ড্রয়েড ক্লাস
Serverপোর্টে লিসেন করে, ক্লায়েন্ট এক্সেপ্ট করে, ডেটা ব্রডকাস্ট/রাউট করেServerSocket, Thread/Executor
Clientসার্ভার IP/পোর্টে কানেক্ট করে, রিকোয়েস্ট পাঠায়, রেসপন্স পায়Socket, Thread/Executor
Protocolমেসেজ ফরম্যাট ডিফাইন করে (লাইন-বেসড, JSON, Protobuf)Application Layer Logic
TransportTCP (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)

  1. TLS/SSL: `SSLSocketFactory` ব্যবহার করে এনক্রিপশন। সার্ভারের জন্য কিস্টোর, ক্লায়েন্টের জন্য ট্রাস্টস্টোর।
  2. Authentication: কানেকশন স্থাপনের পর প্রথম মেসেজে টোকেন/সার্টিফিকেট এক্সচেঞ্জ।
  3. Input Validation: সাইজ লিমিট, স্যানিটাইজেশন, রেট লিমিটিং।
  4. 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-CopyFileChannel.transferTo() ফাইল ট্রান্সফারের জন্য।
Compressionবড় পেলোডের জন্য gzip/snappy কমপ্রেশন।

সারাংশ: কবে Raw Socket ব্যবহার করবেন?

  • ✅ লোকাল নেটওয়ার্ক টুলস (ফাইল ট্রান্সফার, রিমোট কন্ট্রোল, গেম)।
  • ✅ লিগ্যাসি সিস্টেমের সাথে ইন্টিগ্রেশন।
  • ✅ কাস্টম প্রোটোকল ইমপ্লিমেন্টেশন।
  • ✅ লো-লেভেল নেটওয়ার্কিং শেখার জন্য।
  • ❌ ইন্টারনেট-স্কেল অ্যাপ (WebSocket/gRPC ব্যবহার করুন)।
  • ❌ ফায়ারওয়াল/প্রক্সি পিছনে থাকা ক্লায়েন্ট (HTTP/WebSocket ভালো)۔
  • ❌ কমপ্লেক্স ডেটা সিঙ্ক (Firebase/Supabase ব্যবহার করুন)।

Raw Socket প্রোগ্রামিং নেটওয়ার্কিং এর গভীর বোঝাপড়া দেয়। কিন্তু মডার্ন প্রোডাকশন অ্যাপে হাই-লেভেল লাইব্রেরি (OkHttp, Ktor, gRPC) ব্যবহার করা উচিত, যদি না খুব 스페সিফিক লো-লেভেল কন্ট্রোল দরকার হয়।

  1. Android Popup Menu উদাহরণ

  2. অ্যান্ড্রয়েডে CheckedTextView কীভাবে ব্যবহার করবেন?

  3. Android-এ ToggleButton ব্যবহার করার উদাহরণ

  4. প্রোগ্রামিং এবং কোডিং শিখতে সেরা অ্যান্ড্রয়েড অ্যাপস