সকেট (Socket) হলো নেটওয়ার্ক কমিউনিকেশনের ফান্ডামেন্টাল বিল্ডিং ব্লক। অ্যান্ড্রয়েডে TCP সকেট ব্যবহার করে দুটি ডিভাইস (সার্ভার এবং ক্লায়েন্ট) এর মাঝপথে রিয়েল-টাইম, দ্বিমুখী ডেটা এক্সচেঞ্জ করা যায়। এই টিউটোরিয়ালে একটি সম্পূর্ণ **Server-Client আর্কিটেকচার** বানানো দেখানো হলো, যেখানে সার্ভার লিসেন করে এবং ক্লায়েন্ট কানেক্ট হয়ে মেসেজ এক্সচেঞ্জ করে।
আর্কিটেকচার ওভারভিউ
┌─────────────────┐ 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)
- দুটি প্রজেক্ট বানান: Android Studio-তে دوটি আলাদা প্রজেক্ট (SocketServer, SocketClient) তৈরি করুন।
- কোড কপি করুন: উপরের কোডগুলো সংশ্লিষ্ট ফাইলে পেস্ট করুন।
- নেটওয়ার্ক কনফিগারেশন: দুটি ফোন একই Wi-Fi নেটওয়ার্কে কানেক্ট করুন (হটস্পটও চালাবে)।
- সার্ভার অ্যাপ চালান: Server অ্যাপটি রান করুন। এটা IP ঠিকানা দেখাবে (যেমন: 192.168.43.123)।
- ক্লায়েন্ট অ্যাপ চালান: Client অ্যাপটি রান করুন। Server IP ফিল্ডে সার্ভারের IP দিন, Port ৮০৮০ রাখুন।
- কানেক্ট করুন: "Connect to Server" বাটনে ক্লিক করুন। সফল হলে "Connected to Server!" দেখাবে।
- চ্যাট করুন: যেকোনো এক সাইড থেকে মেসেজ লিখে 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 ব্যবহার করুন। রিকানেকশন লজিক রিয়েল অ্যাপে এক্সপোনেনশিয়াল ব্যাকঅফ সহ রিকানেকশন লজিক যোগ করুন। উন্নত ফিচার আইডিয়া
- TLS/SSL: `SSLSocketFactory` ব্যবহার করে এনক্রিপ্টেড কানেকশন।
- Protocol: JSON/Protobuf/MessagePack সিরিয়ালাইজেশন।
- Heartbeat: পেরিয়ডিক পিং/পং দিয়ে কানেকশন এলাইভ চেক।
- Multiple Clients: Server-এ `while(true) { accept(); new Thread(handleClient).start(); }` লুপ।
- 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 নেটওয়ার্কিং বুঝতে এবং সিম্পল লোকাল নেটওয়ার্ক টুল বানাতে চমৎকার। কিন্তু প্রোডাকশন-গ্রেড অ্যাপের জন্য উপরের আধুনিক প্রোটোকলগুলো রিকমেন্ডেড।