Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

Client-Server Programming ใน Android: Socket TCP/IP ฉบับสมบูรณ์

บทความนี้เป็นคู่มือครบวงจรสำหรับการพัฒนาแอปพลิเคชัน Client-Server บน Android โดยใช้ Java Socket (TCP) แบบ Low-level เน้นความเข้าใจพื้นฐานของ Network Programming

🎯 เป้าหมายการเรียนรู้

  • เข้าใจหลักการทำงานของ TCP Socket
  • สร้าง Server ที่รองรับหลาย Client (Multi-threaded)
  • จัดการ Thread และ UI Thread ให้ถูกต้อง
  • รู้จักข้อจำกัดและ Best Practices ของ Network บน Android

🏛 สถาปัตยกรรมโดยรวม

┌─────────────────┐         WiFi LAN         ┌─────────────────┐
│   Server App    │ ◄──────────────────────► │   Client App    │
│  (Port 8080)    │      TCP Socket          │                 │
│                 │                          │                 │
│ ServerSocket    │   accept() ──► Socket    │ Socket          │
│  .bind()        │                          │  .connect()     │
│  .listen()      │   InputStream ◄────────► │  OutputStream   │
│  .accept()      │   OutputStream ────────► │  InputStream    │
└─────────────────┘                          └─────────────────┘

🖥 โปรเจกต์ Server (รายละเอียดเพิ่มเติม)

ปรับปรุง: รองรับหลาย Client (Multi-client Server)

// ใน ServerRunnable - แทนที่ accept() เดียว
while (!Thread.currentThread().isInterrupted()) {
    try {
        Socket clientSocket = serverSocket.accept();
        // สร้าง Thread ใหม่สำหรับแต่ละ Client
        new Thread(new ClientHandler(clientSocket)).start();
    } catch (IOException e) {
        if (!serverSocket.isClosed()) e.printStackTrace();
        break;
    }
}

// ClientHandler Class
private class ClientHandler implements Runnable {
    private final Socket socket;
    private PrintWriter out;
    private BufferedReader in;

    ClientHandler(Socket socket) { this.socket = socket; }

    @Override
    public void run() {
        try {
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            
            String msg;
            while ((msg = in.readLine()) != null) {
                broadcast(msg, this); // ส่งให้ Client ทุกคน
            }
        } catch (IOException e) {
            // Client disconnect
        } finally {
            removeClient(this);
            closeResources();
        }
    }
    
    void send(String msg) { if (out != null) out.println(msg); }
}

Broadcast ข้อความให้ทุก Client

private final List clients = Collections.synchronizedList(new ArrayList<>());

private void broadcast(String message, ClientHandler sender) {
    for (ClientHandler client : clients) {
        if (client != sender) { // ไม่ส่งกลับให้ตัวเอง (ถ้าเป็น Chat)
            client.send(message);
        }
    }
    // แสดงบน Server UI
    runOnUiThread(() -> tvMessages.append("Broadcast: " + message + "\n"));
}

📱 โปรเจกต์ Client (ปรับปรุง)

เพิ่ม Auto-Reconnect

private void connectWithRetry(String ip, int port) {
    new Thread(() -> {
        int retryCount = 0;
        final int MAX_RETRIES = 5;
        
        while (retryCount < MAX_RETRIES && !isConnected && !Thread.currentThread().isInterrupted()) {
            try {
                socket = new Socket(ip, port);
                // ... setup streams ...
                runOnUiThread(() -> updateUIConnected(true));
                startReceiveThread();
                return; // Success
            } catch (IOException e) {
                retryCount++;
                try { Thread.sleep(3000); } catch (InterruptedException ignored) {}
            }
        }
        runOnUiThread(() -> tvMessages.append("Connection failed after " + MAX_RETRIES + " retries\n"));
    }).start();
}

Heartbeat Mechanism (Ping/Pong)

// Client ส่ง Ping ทุก 30 วินาที
private void startHeartbeat() {
    new Thread(() -> {
        while (isConnected && !Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(30000);
                if (output != null) {
                    output.println("__PING__");
                    output.flush();
                }
            } catch (Exception e) {
                break; // Disconnected
            }
        }
    }).start();
}

// Server รับ Ping ตอบ Pong
if ("__PING__".equals(msg)) {
    out.println("__PONG__");
    out.flush();
} else {
    broadcast(msg, this);
}

📋 AndroidManifest.xml Permissions (ทั้งคู่)

<!-- Server -->
<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" />

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

<!-- Android 9+ Cleartext Traffic -->
<application
    android:usesCleartextTraffic="true"
    ... >

⚡ ประสิทธิภาพและ Scalability

ปัญหา Solution
Thread ต่อ Client = Memory/Context Switch มาก Thread Pool (ExecutorService) + Non-blocking I/O (NIO)
Blocking I/O (readLine) ค้าง Thread NIO Selector / Netty / Okio
ไม่มี Protocol Framing กำหนด Protocol: Length-prefixed, Delimiter, หรือ Protobuf/JSON
No Flow Control Backpressure handling, Buffer limits

🔒 Security Hardening

  1. TLS/SSL: ใช้ SSLSocketFactory กับ Certificate
  2. Authentication: Token/Key Exchange ใน Handshake แรก
  3. Input Validation: ตรวจสอบข้อความก่อน Process
  4. Rate Limiting: ป้องกัน DoS/DDoS

ตัวอย่าง SSL Socket (Server)

// ต้องมี Keystore (BKS format) ใน res/raw/keystore.bks
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(getResources().openRawResource(R.raw.keystore), "password".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);

SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
ServerSocket serverSocket = factory.createServerSocket(PORT);

🧪 การทดสอบและ Debug

เครื่องมือช่วย

  • netcat (nc): `nc 8080` - ทดสอบ Server จาก Terminal
  • Wireshark: ดู Packet การสื่อสาร
  • Android Studio Network Profiler: ดู Traffic Real-time
  • adb logcat | grep -i socket - ดู Log

Test Cases

  1. Normal Send/Receive
  2. Client Disconnect/Reconnect
  3. Server Restart
  4. Multiple Clients (ถ้า Multi-threaded)
  5. Large Message (> 64KB)
  6. Network Switch (WiFi ↔ Mobile Data)
  7. App Background/Foreground
  8. Doze Mode / Battery Optimization

📚 สรุปและขั้นตอนต่อไป

ตัวอย่างนี้เป็นพื้นฐาน (Educational) สำหรับ Production จริงแนะนำ:

  1. ใช้ OkHttp WebSocket หรือ Ktor (Kotlin)
  2. หรือ Firebase/Backend-as-a-Service ไม่ต้องจัดการ Server เอง
  3. ศึกษา Netty หรือ Vert.x สำหรับ High-performance Server
  4. เรียนรู้ Protocol Buffers / FlatBuffers สำหรับ Serialization ที่เร็ว
💡 เคล็ดลับ: สำหรับแอป Chat จริง ให้ใช้ Firebase Realtime Database หรือ Firestore - ประหยัดเวลา ปลอดภัย Scale ได้ดี และรองรับ Offline Sync อัตโนมัติ