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

অ্যান্ড্রয়েডে সকেট (Socket) ব্যবহার করে সার্ভার-ক্লায়েন্ট ডেটা ট্রান্সফার

সকেট (Socket) হলো নেটওয়ার্ক কমিউনিকেশনের ফান্ডামেন্টাল বিল্ডিং ব্লক। অ্যান্ড্রয়েডে TCP সকেট ব্যবহার করে দুটি ডিভাইস (সার্ভার এবং ক্লায়েন্ট) এর মাঝপথে রিয়েল-টাইম, দ্বিমুখী ডেটা এক্সচেঞ্জ করা যায়। এই টিউটোরিয়ালে একটি সম্পূর্ণ **Server-Client আর্কিটেকচার** বানানো দেখানো হলো, যেখানে সার্ভার লিসেন করে এবং ক্লায়েন্ট কানেক্ট হয়ে মেসেজ এক্সচেঞ্জ করে।

প্রয়োজন: دوটি আলাদা প্রজেক্ট (Server App এবং Client App) বা একই অ্যাপে দুটি মডিউল। রিয়েল ডিভাইসে টেস্ট করতে একই Wi-Fi নেটওয়ার্কে দুটি ফোন দরকার।

আর্কিটেকচার ওভারভিউ

┌─────────────────┐     TCP Port 8080      ┌─────────────────┐
│   SERVER APP    │ ◄────────────────────► │   CLIENT APP    │
│  (Listens)      │   Bi-directional       │  (Connects)     │
│  Port: 8080     │   Text Messages        │  IP: Server IP  │
└─────────────────┘                        └─────────────────┘

PART 1: SERVER APP

১. build.gradle (Server Module)

// কোনো এক্সট্রা ডিপেন্ডেন্সি লাগবে না (java.net.* стандарт লাইব্রেরি)

1006061523
অ্যান্ড্রয়েডে সকেট (Socket) ব্যবহার করে সার্ভার-ক্লায়েন্ট ডেটা ট্রান্সফার

সকেট (Socket) হলো নেটওয়ার্ক কমিউনিকেশনের ফান্ডামেন্টাল বিল্ডিং ব্লক। অ্যান্ড্রয়েডে TCP সকেট ব্যবহার করে দুটি ডিভাইস (সার্ভার এবং ক্লায়েন্ট) এর মাঝপথে রিয়েল-টাইম, দ্বিমুখী ডেটা এক্সচেঞ্জ করা যায়। এই টিউটোরিয়ালে একটি সম্পূর্ণ **Server-Client আর্কিটেকচার** বানানো দেখানো হলো, যেখানে সার্ভার লিসেন করে এবং ক্লায়েন্ট কানেক্ট হয়ে মেসেজ এক্সচেঞ্জ করে।

প্রয়োজন: দুটি আলাদা প্রজেক্ট (Server App এবং Client App) বা একই অ্যাপে দুটি মডিউল। রিয়েল ডিভাইসে টেস্ট করতে একই Wi-Fi নেটওয়ার্কে দুটি ফোন দরকার।

আর্কিটেকচার ওভারভিউ

┌─────────────────┐     TCP Port 8080      ┌─────────────────┐
│   SERVER APP    │ ◄────────────────────► │   CLIENT APP    │
│  (Listens)      │   Bi-directional       │  (Connects)     │
│  Port: 8080     │   Text Messages        │  IP: Server IP  │
└─────────────────┘                        └─────────────────┘

PART 1: SERVER APP

১. AndroidManifest.xml (Server)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.socketserver">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Socket Server"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SocketServer">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

২. activity_main.xml (Server UI)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvIP"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Server IP: Detecting..."
        android:textAppearance="@style/TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvPort"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tvIP"
        android:text="Port: 8080"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tvPort"
        android:layout_marginTop="8dp"
        android:text="Status: Waiting for connection..."
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        android:textColor="@android:color/holo_orange_dark" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_above="@id/etMessage"
        android:layout_below="@id/tvStatus"
        android:layout_marginTop="8dp"
        android:background="@android:color/darker_gray"
        android:padding="8dp"
        android:textColor="@android:color/white"
        android:textSize="14sp"
        tools:text="Messages will appear here..." />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/btnSend"
        android:layout_marginTop="8dp"
        android:hint="Type message..."
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

৩. MainActivity.java (Server Logic)

package com.example.socketserver;

import android.annotation.SuppressLint;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {

    private static final int SERVER_PORT = 8080;
    private ServerSocket serverSocket;
    private Socket clientSocket;
    private PrintWriter output;
    private BufferedReader input;
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    private TextView tvIP, tvPort, tvStatus, tvMessages;
    private EditText etMessage;
    private Button btnSend;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvIP = findViewById(R.id.tvIP);
        tvPort = findViewById(R.id.tvPort);
        tvStatus = findViewById(R.id.tvStatus);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);

        // Get Local IP
        try {
            String ip = getLocalIpAddress();
            tvIP.setText("Server IP: " + ip);
            tvPort.setText("Port: " + SERVER_PORT);
        } catch (Exception e) {
            tvIP.setText("Server IP: Error");
            e.printStackTrace();
        }

        // Start Server Thread
        executorService.execute(this::startServer);

        btnSend.setOnClickListener(v -> {
            String msg = etMessage.getText().toString().trim();
            if (!msg.isEmpty()) {
                sendMessage(msg);
                etMessage.setText("");
            }
        });
    }

    private void startServer() {
        try {
            serverSocket = new ServerSocket(SERVER_PORT);
            runOnUiThread(() -> tvStatus.setText("Status: Listening on port " + SERVER_PORT));

            // Blocking call - waits for client
            clientSocket = serverSocket.accept();
            
            runOnUiThread(() -> tvStatus.setText("Status: Client Connected!"));

            output = new PrintWriter(clientSocket.getOutputStream(), true);
            input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            // Listen for incoming messages
            executorService.execute(() -> {
                String msg;
                try {
                    while ((msg = input.readLine()) != null) {
                        String finalMsg = msg;
                        runOnUiThread(() -> tvMessages.append("Client: " + finalMsg + "\n"));
                    }
                } catch (IOException e) {
                    runOnUiThread(() -> tvStatus.setText("Status: Client Disconnected"));
                }
            });

        } catch (IOException e) {
            e.printStackTrace();
            runOnUiThread(() -> tvStatus.setText("Status: Server Error - " + e.getMessage()));
        }
    }

    private void sendMessage(String message) {
        if (output != null) {
            output.println(message);
            runOnUiThread(() -> tvMessages.append("Server: " + message + "\n"));
        }
    }

    private String getLocalIpAddress() throws Exception {
        WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        int ipInt = wifiInfo.getIpAddress();
        byte[] ipBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array();
        return InetAddress.getByAddress(ipBytes).getHostAddress();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if (input != null) input.close();
            if (output != null) output.close();
            if (clientSocket != null) clientSocket.close();
            if (serverSocket != null) serverSocket.close();
            executorService.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PART 2: CLIENT APP

১. AndroidManifest.xml (Client)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.socketclient">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Socket Client"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SocketClient">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

২. activity_main.xml (Client UI)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etIP"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Server IP Address (e.g., 192.168.1.5)"
        android:inputType="text" />

    <EditText
        android:id="@+id/etPort"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/etIP"
        android:layout_marginTop="8dp"
        android:hint="Port (8080)"
        android:inputType="number"
        android:text="8080" />

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/etPort"
        android:layout_marginTop="16dp"
        android:text="Connect to Server" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_above="@id/etMessage"
        android:layout_below="@id/btnConnect"
        android:layout_marginTop="8dp"
        android:background="@android:color/darker_gray"
        android:padding="8dp"
        android:textColor="@android:color/white"
        android:textSize="14sp" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/btnSend"
        android:layout_marginTop="8dp"
        android:hint="Type message..."
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

৩. MainActivity.java (Client Logic)

package com.example.socketclient;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    private Socket socket;
    private PrintWriter output;
    private BufferedReader input;
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    private EditText etIP, etPort, etMessage;
    private Button btnConnect, btnSend;
    private TextView tvMessages;

    private String serverIP;
    private int serverPort;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        etIP = findViewById(R.id.etIP);
        etPort = findViewById(R.id.etPort);
        btnConnect = findViewById(R.id.btnConnect);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);
        btnSend.setEnabled(false);

        btnConnect.setOnClickListener(v -> connectToServer());
        btnSend.setOnClickListener(v -> sendMessage());
    }

    private void connectToServer() {
        serverIP = etIP.getText().toString().trim();
        String portStr = etPort.getText().toString().trim();

        if (serverIP.isEmpty() || portStr.isEmpty()) {
            Toast.makeText(this, "Enter IP and Port", Toast.LENGTH_SHORT).show();
            return;
        }

        serverPort = Integer.parseInt(portStr);
        tvMessages.setText("Connecting to " + serverIP + ":" + serverPort + "...\n");

        executorService.execute(() -> {
            try {
                socket = new Socket(serverIP, serverPort);
                output = new PrintWriter(socket.getOutputStream(), true);
                input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                runOnUiThread(() -> {
                    tvMessages.append("Connected to Server!\n");
                    btnConnect.setEnabled(false);
                    btnSend.setEnabled(true);
                    etIP.setEnabled(false);
                    etPort.setEnabled(false);
                });

                // Listen for server messages
                executorService.execute(() -> {
                    String msg;
                    try {
                        while ((msg = input.readLine()) != null) {
                            String finalMsg = msg;
                            runOnUiThread(() -> tvMessages.append("Server: " + finalMsg + "\n"));
                        }
                    } catch (IOException e) {
                        runOnUiThread(() -> onDisconnected("Server disconnected"));
                    }
                });

            } catch (IOException e) {
                e.printStackTrace();
                runOnUiThread(() -> {
                    tvMessages.append("Connection Failed: " + e.getMessage() + "\n");
                    Toast.makeText(this, "Connection Failed", Toast.LENGTH_SHORT).show();
                });
            }
        });
    }

    private void sendMessage() {
        String msg = etMessage.getText().toString().trim();
        if (!msg.isEmpty() && output != null) {
            output.println(msg);
            runOnUiThread(() -> {
                tvMessages.append("Client: " + msg + "\n");
                etMessage.setText("");
            });
        }
    }

    private void onDisconnected(String reason) {
        tvMessages.append("Disconnected: " + reason + "\n");
        btnConnect.setEnabled(true);
        btnSend.setEnabled(false);
        etIP.setEnabled(true);
        etPort.setEnabled(true);
        try {
            if (socket != null) socket.close();
        } catch (IOException ignored) {}
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        onDisconnected("App closing");
        executorService.shutdown();
    }
}

চলানোর ধাপসমূহ (Step-by-Step)

  1. দুটি প্রজেক্ট বানান: Android Studio-তে دوটি আলাদা প্রজেক্ট (SocketServer, SocketClient) তৈরি করুন।
  2. কোড কপি করুন: উপরের কোডগুলো সংশ্লিষ্ট ফাইলে পেস্ট করুন।
  3. নেটওয়ার্ক কনফিগারেশন: দুটি ফোন একই Wi-Fi নেটওয়ার্কে কানেক্ট করুন (হটস্পটও চালাবে)।
  4. সার্ভার অ্যাপ চালান: Server অ্যাপটি রান করুন। এটা IP ঠিকানা দেখাবে (যেমন: 192.168.43.123)।
  5. ক্লায়েন্ট অ্যাপ চালান: Client অ্যাপটি রান করুন। Server IP ফিল্ডে সার্ভারের IP দিন, Port ৮০৮০ রাখুন।
  6. কানেক্ট করুন: "Connect to Server" বাটনে ক্লিক করুন। সফল হলে "Connected to Server!" দেখাবে।
  7. চ্যাট করুন: যেকোনো এক সাইড থেকে মেসেজ লিখে SEND চাপলে অন্য সাইডে দেখাবে।

গুরুত্বপূর্ণ টেকনিক্যাল বিবেচনা

থ্রেডিং মডেল

  • ServerSocket.accept() ব্লকিং কল — অবশ্যই ব্যাকগ্রাউন্ড থ্রেডে চলতে হবে (ExecutorService ব্যবহার করা হয়েছে)।
  • UI আপডেট: `runOnUiThread()` ব্যবহার করে মেইন থ্রেডে UI আপডেট করা হয়েছে।
  • দুই থ্রেড: Server-এ একটা লিসেনার থ্রেড, একটা সেন্ডার থ্রেড (মেইন থ্রেড থেকে)。

সীমাবদ্ধতা ও সমস্যা সমাধান

সমস্যাসমাধান
Firewall/Router ব্লক করছেএকই সাবনেটে থাকুন, Windows Firewall-এ পোর্ট ৮০৮০ ওপেন করুন।
IP চেঞ্জ হয় (DHCP)Static IP সেট করুন বা রাউটারে DHCP Reservation দিন।
Mobile Data-এ কাজ করে নাNAT পিছনে থাকলে পোর্ট ফরওয়ার্ডিং/STUN/TURN দরকার।
বড় মেসেজ/বাইনারি ডেটাPrintWriter/BufferedReader টেক্সটের জন্য। বাইনারি용 InputStream/OutputStream ব্যবহার করুন।
রিকানেকশন লজিকরিয়েল অ্যাপে এক্সপোনেনশিয়াল ব্যাকঅফ সহ রিকানেকশন লজিক যোগ করুন।

উন্নত ফিচার আইডিয়া

  1. TLS/SSL: `SSLSocketFactory` ব্যবহার করে এনক্রিপ্টেড কানেকশন।
  2. Protocol: JSON/Protobuf/MessagePack সিরিয়ালাইজেশন।
  3. Heartbeat: পেরিয়ডিক পিং/পং দিয়ে কানেকশন এলাইভ চেক।
  4. Multiple Clients: Server-এ `while(true) { accept(); new Thread(handleClient).start(); }` লুপ।
  5. Kotlin Coroutines: `suspend` ফাংশন এবং `Flow` ব্যবহার করে ক্লিনার কোড।

মডার্ন অ্যাল্টারনেটিভ (Production Apps)

  • WebSocket (OkHttp/Ktor): HTTP-কম্প্যাটিবল, ফায়ারওয়াল-ফ্রেন্ডলি, ব্রাউজার সাপোর্ট।
  • gRPC: কন্ট্র্যাক্ট-ফার্সট, হাই-পারফরম্যান্স, মাল্টি-ল্যাঙ্গুয়েজ।
  • Firebase Realtime DB / Firestore: ম্যানেজড, রিয়েল-টাইম সিঙ্ক, অফলাইন সাপোর্ট।
  • Nearby Connections API: ইন্টারনেট ছাড়া Bluetooth/Wi-Fi Direct হাইব্রিড।

Raw TCP Socket নেটওয়ার্কিং বুঝতে এবং সিম্পল লোকাল নেটওয়ার্ক টুল বানাতে চমৎকার। কিন্তু প্রোডাকশন-গ্রেড অ্যাপের জন্য উপরের আধুনিক প্রোটোকলগুলো রিকমেন্ডেড।

  1. উবুন্টুতে ADB ব্যবহার করে অ্যান্ড্রয়েড ডেটা ব্যাকআপ लेने का উপায়

  2. WiFi File Transfer দিয়ে Android ও PC-র মাঝে ফাইল ট্রান্সফার

  3. অ্যান্ড্রয়েডে SMS পাঠানো/পাওয়ার সমস্যা সমাধান - দ্রুত ও নির্ভরযোগ্য সমাধান

  4. এন্ড্রয়েড ডেটা ইউজেজ ট্র্যাক ও ম্যানেজ করুন: ধাপে ধাপে গাইড