Android Socket 深度剖析:从原理到实战

Android Socket 深度剖析:从原理到实战

目录

  1. 引言
  2. [Socket 基础架构](#Socket 基础架构)
  3. [Android 平台特殊性](#Android 平台特殊性)
  4. [TCP Socket 深入解析](#TCP Socket 深入解析)
  5. [UDP Socket 与组播](#UDP Socket 与组播)
  6. [NIO 与多路复用](#NIO 与多路复用)
  7. 心跳机制设计
  8. 断线重连策略
  9. 粘包与拆包
  10. [SSL/TLS 安全通信](#SSL/TLS 安全通信)
  11. 性能优化
  12. 实战架构设计
  13. 调试与问题排查

引言

在移动互联网时代,实时通信已成为应用的核心能力。无论是即时通讯、实时游戏、股票行情推送,还是IoT设备控制,Socket都是底层通信的基石。本文将从操作系统内核层面到应用层架构,全方位剖析Android平台上的Socket编程。

Socket 基础架构

1.1 Socket 本质

Socket(套接字)本质上是操作系统内核中的一个数据结构,它是应用层与传输层之间的抽象接口。在Linux内核(Android基于Linux)中,Socket以文件描述符(File Descriptor)的形式存在。

复制代码
┌─────────────────────────────────────────────────────────┐
│                    Application Layer                     │
│                   (Your Android App)                     │
├─────────────────────────────────────────────────────────┤
│                    Socket API (JNI)                      │
│              java.net.Socket / java.nio.*                │
├─────────────────────────────────────────────────────────┤
│                   System Call Layer                      │
│         socket(), bind(), connect(), send(), recv()      │
├─────────────────────────────────────────────────────────┤
│                    Kernel Space                          │
│  ┌─────────────────────────────────────────────────┐    │
│  │              Socket Buffer (SKB)                 │    │
│  │  ┌─────────────┐         ┌─────────────┐       │    │
│  │  │ Send Buffer │         │ Recv Buffer │       │    │
│  │  └─────────────┘         └─────────────┘       │    │
│  └─────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────┤
│                  TCP/IP Protocol Stack                   │
│        TCP/UDP → IP → ARP → Network Driver               │
└─────────────────────────────────────────────────────────┘

1.2 Linux 内核中的 Socket 结构

c 复制代码
// 简化的内核socket结构
struct socket {
    socket_state            state;      // 连接状态
    short                   type;       // SOCK_STREAM/SOCK_DGRAM
    unsigned long           flags;      // 标志位
    struct file            *file;       // 关联的文件对象
    struct sock            *sk;         // 网络层socket
    const struct proto_ops *ops;        // 协议操作函数指针
};

struct sock {
    struct sk_buff_head     sk_receive_queue;  // 接收队列
    struct sk_buff_head     sk_write_queue;    // 发送队列
    wait_queue_head_t       sk_wq;             // 等待队列
    // ... 更多字段
};

1.3 TCP 三次握手在 Socket 中的体现

复制代码
     Client                                   Server
        │                                        │
        │  socket() → SYN_SENT                   │ socket() → bind() → listen()
        │                                        │ (LISTEN state)
        │                                        │
        │  ──────── SYN (seq=x) ────────────►   │
        │                                        │ SYN_RCVD
        │                                        │
        │  ◄─────── SYN+ACK (seq=y,ack=x+1) ──  │
        │                                        │
        │  ESTABLISHED                           │
        │  ──────── ACK (ack=y+1) ──────────►   │
        │                                        │ ESTABLISHED
        │                                        │
     connect()                               accept()
     returns                                 returns

Android 平台特殊性

2.1 主线程网络限制

Android 3.0+ 引入了 StrictMode,在主线程执行网络操作会抛出 NetworkOnMainThreadException

java 复制代码
// 错误示例 - 主线程网络操作
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 这会崩溃!
        Socket socket = new Socket("192.168.1.1", 8080);
    }
}

底层原理 :Android在BlockGuard中设置了策略检查:

java 复制代码
// android.os.StrictMode 内部实现
public static void onNetwork() {
    BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
    if (policy.getPolicyMask() != 0) {
        policy.onNetwork();  // 触发检查
    }
}

2.2 Doze 模式与网络限制

Android 6.0+ 的 Doze 模式会影响网络连接:

复制代码
┌──────────────────────────────────────────────────────────────┐
│                      Doze Mode States                         │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│   ACTIVE ──► INACTIVE ──► IDLE_PENDING ──► IDLE              │
│      ▲                                        │               │
│      │                                        │               │
│      └──────── Motion/Screen On ◄────────────┘               │
│                                                               │
│   IDLE状态下:                                                │
│   • 网络访问被暂停                                            │
│   • 仅在维护窗口期允许网络                                     │
│   • 高优先级FCM消息可唤醒                                      │
│                                                               │
└──────────────────────────────────────────────────────────────┘

解决方案

java 复制代码
// 请求忽略电池优化
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
    Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivity(intent);
}

// 使用前台服务保活Socket连接
public class SocketService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(NOTIFICATION_ID, buildNotification());
        return START_STICKY;
    }
}

2.3 网络切换处理

java 复制代码
public class NetworkMonitor {
    private ConnectivityManager.NetworkCallback networkCallback;

    public void register(Context context) {
        ConnectivityManager cm = (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkRequest request = new NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            .build();

        networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                // 网络可用,尝试重连Socket
                reconnectSocket();
            }

            @Override
            public void onLost(Network network) {
                // 网络丢失,标记断开状态
                markDisconnected();
            }

            @Override
            public void onCapabilitiesChanged(Network network,
                    NetworkCapabilities capabilities) {
                // 网络能力变化(如从WiFi切换到4G)
                int downBandwidth = capabilities.getLinkDownstreamBandwidthKbps();
                adjustBufferSize(downBandwidth);
            }
        };

        cm.registerNetworkCallback(request, networkCallback);
    }
}

TCP Socket 深入解析

3.1 连接建立的完整流程

java 复制代码
public class TcpClient {
    private Socket socket;
    private DataInputStream inputStream;
    private DataOutputStream outputStream;

    public void connect(String host, int port, int timeout) throws IOException {
        // 创建未连接的Socket
        socket = new Socket();

        // 配置Socket选项(必须在connect之前)
        configureSocket();

        // 连接(带超时)
        InetSocketAddress address = new InetSocketAddress(host, port);
        socket.connect(address, timeout);

        // 获取流
        inputStream = new DataInputStream(
            new BufferedInputStream(socket.getInputStream(), 8192));
        outputStream = new DataOutputStream(
            new BufferedOutputStream(socket.getOutputStream(), 8192));
    }

    private void configureSocket() throws SocketException {
        // TCP_NODELAY: 禁用Nagle算法
        // Nagle算法会将小包合并,降低网络利用率但增加延迟
        // 实时性要求高的场景应禁用
        socket.setTcpNoDelay(true);

        // SO_KEEPALIVE: TCP层心跳
        // 默认2小时发送一次探测包,不适合移动网络
        socket.setKeepAlive(true);

        // SO_RCVBUF/SO_SNDBUF: 缓冲区大小
        // 影响TCP窗口大小和吞吐量
        socket.setReceiveBufferSize(64 * 1024);  // 64KB
        socket.setSendBufferSize(64 * 1024);

        // SO_TIMEOUT: 读取超时
        // 超时后抛出SocketTimeoutException,但连接不关闭
        socket.setSoTimeout(30000);  // 30秒

        // SO_LINGER: 关闭行为
        // linger=true, time=0: 立即关闭,发送RST,丢弃未发送数据
        // linger=true, time>0: 等待指定秒数,发送完成或超时后关闭
        // linger=false: 默认行为,close()立即返回,后台继续发送
        socket.setSoLinger(true, 5);

        // SO_REUSEADDR: 地址复用
        // 允许绑定TIME_WAIT状态的地址
        socket.setReuseAddress(true);
    }
}

3.2 Nagle 算法深度理解

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Nagle Algorithm                           │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  发送条件(满足任一):                                        │
│  1. 数据量 >= MSS (Maximum Segment Size)                     │
│  2. 之前发送的所有小包都已被ACK                                │
│  3. 超时(通常200ms)                                         │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  App: send("H") send("e") send("l") send("l") send("o") │
│  │                                                       │   │
│  │  With Nagle (TCP_NODELAY=false):                     │   │
│  │  ──► [H] ──wait ACK──► [ello] ──►                    │   │
│  │  延迟高,包数少                                        │   │
│  │                                                       │   │
│  │  Without Nagle (TCP_NODELAY=true):                   │   │
│  │  ──► [H] ──► [e] ──► [l] ──► [l] ──► [o] ──►        │   │
│  │  延迟低,包数多                                        │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  最佳实践:                                                   │
│  • 实时游戏、交互应用:禁用 Nagle                             │
│  • 文件传输、批量数据:启用 Nagle                             │
│                                                              │
└─────────────────────────────────────────────────────────────┘

3.3 TCP 滑动窗口与流量控制

java 复制代码
/**
 * TCP窗口大小直接影响吞吐量
 *
 * 理论最大吞吐量 = Window Size / RTT
 *
 * 例如:
 * - Window Size = 64KB = 65536 bytes
 * - RTT = 100ms
 * - 吞吐量 = 65536 / 0.1 = 655360 bytes/s ≈ 640 KB/s
 *
 * 在高延迟网络中,需要更大的窗口才能充分利用带宽
 */
public class TcpWindowAnalyzer {

    public void optimizeForNetwork(Socket socket, NetworkInfo info)
            throws SocketException {
        int rtt = info.getEstimatedRtt();  // 毫秒
        int bandwidth = info.getBandwidthKbps();  // Kbps

        // 计算BDP (Bandwidth-Delay Product)
        // BDP = Bandwidth × RTT
        // 这是网络"管道"中能容纳的数据量
        long bdp = (long) bandwidth * 1000 / 8 * rtt / 1000;

        // 窗口大小应至少等于BDP
        int windowSize = (int) Math.min(bdp * 2, 1024 * 1024);  // 最大1MB
        windowSize = Math.max(windowSize, 64 * 1024);  // 最小64KB

        socket.setReceiveBufferSize(windowSize);
        socket.setSendBufferSize(windowSize);

        Log.d("TCP", String.format(
            "RTT=%dms, BW=%dKbps, BDP=%d, Window=%d",
            rtt, bandwidth, bdp, windowSize));
    }
}

3.4 TCP 状态机详解

复制代码
                              ┌───────────────────┐
                              │      CLOSED       │
                              └─────────┬─────────┘
                                        │
              ┌─────────────────────────┼─────────────────────────┐
              │                         │                         │
              ▼                         │                         ▼
     ┌────────────────┐                 │               ┌────────────────┐
     │     LISTEN     │                 │               │    SYN_SENT    │
     │   (服务端)      │                 │               │    (客户端)     │
     └───────┬────────┘                 │               └───────┬────────┘
             │                          │                       │
             │ recv SYN                 │                       │ recv SYN+ACK
             │ send SYN+ACK             │                       │ send ACK
             ▼                          │                       │
     ┌────────────────┐                 │                       │
     │   SYN_RCVD     │                 │                       │
     └───────┬────────┘                 │                       │
             │                          │                       │
             │ recv ACK                 │                       │
             └──────────────────────────┼───────────────────────┘
                                        │
                                        ▼
                              ┌───────────────────┐
                              │   ESTABLISHED     │
                              │    (数据传输)      │
                              └─────────┬─────────┘
                                        │
              ┌─────────────────────────┼─────────────────────────┐
              │ (主动关闭)               │                (被动关闭) │
              ▼                         │                         ▼
     ┌────────────────┐                 │               ┌────────────────┐
     │    FIN_WAIT_1  │                 │               │   CLOSE_WAIT   │
     └───────┬────────┘                 │               └───────┬────────┘
             │ recv ACK                 │                       │ send FIN
             ▼                         │                       ▼
     ┌────────────────┐                 │               ┌────────────────┐
     │    FIN_WAIT_2  │                 │               │    LAST_ACK    │
     └───────┬────────┘                 │               └───────┬────────┘
             │ recv FIN                 │                       │ recv ACK
             │ send ACK                 │                       │
             ▼                         │                       │
     ┌────────────────┐                 │                       │
     │   TIME_WAIT    │ ◄───────────────┘───────────────────────┘
     │  (2MSL等待)     │
     └───────┬────────┘
             │ timeout (2MSL ≈ 4min)
             ▼
     ┌────────────────┐
     │     CLOSED     │
     └────────────────┘

UDP Socket 与组播

4.1 UDP 基础

java 复制代码
public class UdpClient {
    private DatagramSocket socket;
    private InetAddress serverAddress;
    private int serverPort;

    public void init(String host, int port) throws Exception {
        // UDP Socket不需要连接
        socket = new DatagramSocket();

        // 可选:设置超时
        socket.setSoTimeout(5000);

        // 可选:绑定本地端口(用于接收响应)
        // socket = new DatagramSocket(localPort);

        serverAddress = InetAddress.getByName(host);
        serverPort = port;

        // 可选:"连接"UDP Socket
        // 这不是真正的连接,而是记录目标地址
        // 好处:1.可以使用send()而不是sendTo() 2.只接收来自该地址的数据
        socket.connect(serverAddress, serverPort);
    }

    public void send(byte[] data) throws IOException {
        DatagramPacket packet = new DatagramPacket(
            data, data.length, serverAddress, serverPort);
        socket.send(packet);
    }

    public byte[] receive() throws IOException {
        byte[] buffer = new byte[65535];  // UDP最大包65535字节
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);  // 阻塞直到收到数据

        // 返回实际接收的数据
        return Arrays.copyOf(packet.getData(), packet.getLength());
    }
}

4.2 UDP 组播(Multicast)

组播适用于一对多通信场景,如局域网设备发现、实时视频分发等。

java 复制代码
public class MulticastClient {
    private MulticastSocket socket;
    private InetAddress groupAddress;
    private int port;
    private NetworkInterface networkInterface;

    /**
     * 组播地址范围:224.0.0.0 - 239.255.255.255
     *
     * 224.0.0.0 - 224.0.0.255: 本地链路(TTL=1,不跨路由器)
     * 224.0.1.0 - 224.0.1.255: 互联网控制
     * 239.0.0.0 - 239.255.255.255: 管理作用域(私有组播)
     */
    public void joinGroup(String group, int port) throws Exception {
        this.port = port;
        this.groupAddress = InetAddress.getByName(group);

        socket = new MulticastSocket(port);

        // 设置TTL(Time To Live)
        // TTL=1: 仅本地网络
        // TTL=32: 同一站点
        // TTL=64: 同一区域
        // TTL=128: 同一大陆
        // TTL=255: 全球
        socket.setTimeToLive(1);

        // 获取WiFi网络接口
        networkInterface = getWifiInterface();

        if (networkInterface != null) {
            // 加入组播组
            socket.joinGroup(
                new InetSocketAddress(groupAddress, port),
                networkInterface);
        }

        // 是否接收自己发送的消息
        socket.setLoopbackMode(true);  // true = 不接收
    }

    private NetworkInterface getWifiInterface() throws SocketException {
        Enumeration<NetworkInterface> interfaces =
            NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface ni = interfaces.nextElement();
            if (ni.getName().startsWith("wlan") && ni.isUp()) {
                return ni;
            }
        }
        return null;
    }

    public void sendToGroup(byte[] data) throws IOException {
        DatagramPacket packet = new DatagramPacket(
            data, data.length, groupAddress, port);
        socket.send(packet);
    }

    public void leaveGroup() throws IOException {
        if (socket != null && networkInterface != null) {
            socket.leaveGroup(
                new InetSocketAddress(groupAddress, port),
                networkInterface);
            socket.close();
        }
    }
}

4.3 UDP 可靠性增强

UDP本身不保证可靠性,需要应用层实现:

java 复制代码
public class ReliableUdp {
    private static final int MAX_RETRIES = 3;
    private static final int TIMEOUT_MS = 1000;
    private final Map<Integer, PendingPacket> pendingAcks = new ConcurrentHashMap<>();
    private final AtomicInteger sequenceNumber = new AtomicInteger(0);

    /**
     * 自定义可靠UDP协议头
     *
     * ┌─────────────────────────────────────────────────────┐
     * │  0-1  │  2-5   │   6    │  7-N  │
     * │ Type  │ SeqNum │ Flags  │ Data  │
     * └─────────────────────────────────────────────────────┘
     *
     * Type: 0x01=DATA, 0x02=ACK, 0x03=NACK
     * Flags: 0x01=需要ACK, 0x02=重传
     */

    public void sendReliable(byte[] data, DatagramSocket socket,
            InetAddress address, int port) throws IOException {
        int seq = sequenceNumber.incrementAndGet();
        byte[] packet = buildPacket(0x01, seq, (byte) 0x01, data);

        DatagramPacket dgram = new DatagramPacket(
            packet, packet.length, address, port);

        PendingPacket pending = new PendingPacket(dgram, System.currentTimeMillis());
        pendingAcks.put(seq, pending);

        // 发送并等待ACK
        for (int i = 0; i < MAX_RETRIES; i++) {
            socket.send(dgram);

            if (waitForAck(socket, seq, TIMEOUT_MS)) {
                pendingAcks.remove(seq);
                return;  // 成功
            }
        }

        pendingAcks.remove(seq);
        throw new IOException("发送失败:未收到ACK");
    }

    private boolean waitForAck(DatagramSocket socket, int expectedSeq,
            int timeoutMs) throws IOException {
        socket.setSoTimeout(timeoutMs);
        byte[] buffer = new byte[7];  // ACK包很小
        DatagramPacket ackPacket = new DatagramPacket(buffer, buffer.length);

        try {
            socket.receive(ackPacket);
            int type = buffer[0];
            int ackSeq = ByteBuffer.wrap(buffer, 1, 4).getInt();
            return type == 0x02 && ackSeq == expectedSeq;
        } catch (SocketTimeoutException e) {
            return false;
        }
    }

    private byte[] buildPacket(int type, int seq, byte flags, byte[] data) {
        ByteBuffer buffer = ByteBuffer.allocate(7 + data.length);
        buffer.put((byte) type);
        buffer.putInt(seq);
        buffer.put(flags);
        buffer.put((byte) data.length);
        buffer.put(data);
        return buffer.array();
    }

    static class PendingPacket {
        DatagramPacket packet;
        long timestamp;
        int retries;

        PendingPacket(DatagramPacket packet, long timestamp) {
            this.packet = packet;
            this.timestamp = timestamp;
            this.retries = 0;
        }
    }
}

NIO 与多路复用

5.1 BIO vs NIO 对比

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    BIO (Blocking I/O)                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Thread-1 ────► Socket-1.read() [阻塞] ────► 处理 ────► 继续        │
│  Thread-2 ────► Socket-2.read() [阻塞] ────► 处理 ────► 继续        │
│  Thread-3 ────► Socket-3.read() [阻塞] ────► 处理 ────► 继续        │
│  ...                                                                 │
│  Thread-N ────► Socket-N.read() [阻塞] ────► 处理 ────► 继续        │
│                                                                      │
│  问题:                                                              │
│  • 每个连接需要一个线程,资源消耗大                                   │
│  • 线程切换开销大                                                    │
│  • 连接数受限于线程数                                                 │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                    NIO (Non-blocking I/O)                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│                    ┌─────────────┐                                   │
│                    │  Selector   │                                   │
│                    │  (多路复用)  │                                   │
│                    └──────┬──────┘                                   │
│           ┌───────────────┼───────────────┐                         │
│           ▼               ▼               ▼                         │
│      ┌─────────┐    ┌─────────┐    ┌─────────┐                     │
│      │Channel-1│    │Channel-2│    │Channel-N│                     │
│      └─────────┘    └─────────┘    └─────────┘                     │
│                                                                      │
│  单线程处理多个连接:                                                 │
│  while (true) {                                                      │
│      selector.select();  // 阻塞直到有事件                           │
│      for (SelectionKey key : selectedKeys) {                        │
│          if (key.isReadable()) handleRead(key);                     │
│          if (key.isWritable()) handleWrite(key);                    │
│      }                                                               │
│  }                                                                   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

5.2 NIO 核心组件

java 复制代码
public class NioSocketClient {
    private Selector selector;
    private SocketChannel channel;
    private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(8192);
    private final Queue<ByteBuffer> writeQueue = new ConcurrentLinkedQueue<>();

    public void connect(String host, int port) throws IOException {
        // 创建Selector
        selector = Selector.open();

        // 创建SocketChannel
        channel = SocketChannel.open();
        channel.configureBlocking(false);  // 非阻塞模式

        // 配置Socket选项
        channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
        channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
        channel.setOption(StandardSocketOptions.SO_RCVBUF, 64 * 1024);
        channel.setOption(StandardSocketOptions.SO_SNDBUF, 64 * 1024);

        // 注册连接事件
        channel.register(selector, SelectionKey.OP_CONNECT);

        // 发起连接(非阻塞,立即返回)
        channel.connect(new InetSocketAddress(host, port));

        // 启动事件循环
        startEventLoop();
    }

    private void startEventLoop() {
        new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    // 阻塞等待事件,最多1秒
                    int readyCount = selector.select(1000);

                    if (readyCount == 0) {
                        continue;
                    }

                    Iterator<SelectionKey> iterator =
                        selector.selectedKeys().iterator();

                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();  // 必须手动移除

                        if (!key.isValid()) continue;

                        if (key.isConnectable()) {
                            handleConnect(key);
                        }
                        if (key.isReadable()) {
                            handleRead(key);
                        }
                        if (key.isWritable()) {
                            handleWrite(key);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }, "NIO-EventLoop").start();
    }

    private void handleConnect(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();

        if (channel.finishConnect()) {
            // 连接成功,注册读事件
            key.interestOps(SelectionKey.OP_READ);
            onConnected();
        }
    }

    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        readBuffer.clear();

        int bytesRead = channel.read(readBuffer);

        if (bytesRead == -1) {
            // 连接关闭
            key.cancel();
            channel.close();
            onDisconnected();
            return;
        }

        if (bytesRead > 0) {
            readBuffer.flip();
            byte[] data = new byte[readBuffer.remaining()];
            readBuffer.get(data);
            onDataReceived(data);
        }
    }

    private void handleWrite(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();

        while (!writeQueue.isEmpty()) {
            ByteBuffer buffer = writeQueue.peek();
            channel.write(buffer);

            if (buffer.hasRemaining()) {
                // 写缓冲区满,等待下次可写事件
                return;
            }

            writeQueue.poll();
        }

        // 队列空了,取消写事件监听
        key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
    }

    public void send(byte[] data) {
        ByteBuffer buffer = ByteBuffer.wrap(data);
        writeQueue.offer(buffer);

        // 注册写事件
        SelectionKey key = channel.keyFor(selector);
        if (key != null && key.isValid()) {
            key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            selector.wakeup();  // 唤醒阻塞的select()
        }
    }

    // 回调方法
    protected void onConnected() {}
    protected void onDisconnected() {}
    protected void onDataReceived(byte[] data) {}
}

5.3 Direct Buffer vs Heap Buffer

java 复制代码
/**
 * ByteBuffer 类型对比
 *
 * ┌─────────────────────────────────────────────────────────────────┐
 * │                     Heap Buffer                                  │
 * │  ByteBuffer.allocate(size)                                       │
 * ├─────────────────────────────────────────────────────────────────┤
 * │  • 分配在JVM堆内存                                               │
 * │  • GC可以回收                                                    │
 * │  • I/O操作需要额外拷贝到native内存                               │
 * │  • 适合:小数据、频繁创建销毁                                     │
 * └─────────────────────────────────────────────────────────────────┘
 *
 * ┌─────────────────────────────────────────────────────────────────┐
 * │                    Direct Buffer                                 │
 * │  ByteBuffer.allocateDirect(size)                                 │
 * ├─────────────────────────────────────────────────────────────────┤
 * │  • 分配在native内存(堆外内存)                                   │
 * │  • 不受GC直接管理(有Cleaner回收)                               │
 * │  • I/O操作零拷贝                                                 │
 * │  • 分配和释放开销大                                              │
 * │  • 适合:大数据、长期使用、频繁I/O                                │
 * └─────────────────────────────────────────────────────────────────┘
 */
public class BufferPerformanceTest {

    public void compare() {
        int size = 1024 * 1024;  // 1MB
        int iterations = 1000;

        // Heap Buffer
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            ByteBuffer buffer = ByteBuffer.allocate(size);
            buffer.put(new byte[size]);
            buffer.flip();
        }
        long heapTime = System.nanoTime() - start;

        // Direct Buffer (预分配,重用)
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(size);
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            directBuffer.clear();
            directBuffer.put(new byte[size]);
            directBuffer.flip();
        }
        long directTime = System.nanoTime() - start;

        Log.d("Buffer", String.format(
            "Heap: %dms, Direct: %dms",
            heapTime / 1_000_000,
            directTime / 1_000_000));
    }
}

心跳机制设计

6.1 心跳的必要性

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    为什么需要应用层心跳?                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  1. TCP KeepAlive 不够用                                            │
│     • 默认2小时才探测(tcp_keepalive_time=7200s)                    │
│     • 移动网络NAT超时通常5-10分钟                                    │
│     • 无法检测应用层的"假死"状态                                     │
│                                                                      │
│  2. 移动网络的特殊性                                                 │
│     • 基站切换可能导致连接中断                                       │
│     • NAT/防火墙会清理空闲连接                                       │
│     • 运营商可能对长连接做特殊处理                                   │
│                                                                      │
│  3. 业务需求                                                         │
│     • 实时检测服务端状态                                             │
│     • 快速发现连接断开                                               │
│     • 触发自动重连                                                   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

6.2 智能心跳算法

java 复制代码
public class SmartHeartbeat {
    // 心跳间隔范围
    private static final long MIN_INTERVAL = 30 * 1000;   // 30秒
    private static final long MAX_INTERVAL = 300 * 1000;  // 5分钟
    private static final long STEP = 30 * 1000;           // 步长30秒

    private long currentInterval = MIN_INTERVAL;
    private int successCount = 0;
    private int failCount = 0;
    private long lastSuccessTime = 0;
    private NetworkType lastNetworkType;

    private final Handler handler = new Handler(Looper.getMainLooper());
    private final SocketClient client;

    public SmartHeartbeat(SocketClient client) {
        this.client = client;
    }

    /**
     * 智能心跳算法:
     *
     * 1. 初始使用最小间隔
     * 2. 连续3次成功后,增加间隔
     * 3. 失败1次后,立即回退到最小间隔
     * 4. 网络切换时,重置到最小间隔
     *
     * ┌─────────────────────────────────────────────────────────┐
     * │  成功  成功  成功  →  增加间隔                           │
     * │   30s   30s   30s →    60s                             │
     * │                                                         │
     * │  成功  成功  失败  →  重置间隔                           │
     * │   60s   60s  FAIL →    30s                             │
     * └─────────────────────────────────────────────────────────┘
     */
    public void start() {
        scheduleNextHeartbeat();
    }

    private void scheduleNextHeartbeat() {
        handler.postDelayed(this::sendHeartbeat, currentInterval);
    }

    private void sendHeartbeat() {
        long startTime = System.currentTimeMillis();

        client.sendHeartbeat(new HeartbeatCallback() {
            @Override
            public void onSuccess() {
                long rtt = System.currentTimeMillis() - startTime;
                onHeartbeatSuccess(rtt);
            }

            @Override
            public void onFailure() {
                onHeartbeatFailure();
            }
        });
    }

    private void onHeartbeatSuccess(long rtt) {
        failCount = 0;
        successCount++;
        lastSuccessTime = System.currentTimeMillis();

        // 连续成功3次,尝试增加间隔
        if (successCount >= 3) {
            successCount = 0;
            currentInterval = Math.min(currentInterval + STEP, MAX_INTERVAL);
            Log.d("Heartbeat", "增加心跳间隔至: " + currentInterval + "ms");
        }

        scheduleNextHeartbeat();
    }

    private void onHeartbeatFailure() {
        successCount = 0;
        failCount++;

        // 立即回退到最小间隔
        currentInterval = MIN_INTERVAL;
        Log.w("Heartbeat", "心跳失败,重置间隔至: " + currentInterval + "ms");

        // 连续失败3次,触发重连
        if (failCount >= 3) {
            failCount = 0;
            client.reconnect();
        } else {
            scheduleNextHeartbeat();
        }
    }

    public void onNetworkChanged(NetworkType newType) {
        if (lastNetworkType != newType) {
            lastNetworkType = newType;
            currentInterval = MIN_INTERVAL;
            successCount = 0;

            // 立即发送一次心跳
            handler.removeCallbacksAndMessages(null);
            sendHeartbeat();
        }
    }

    public void stop() {
        handler.removeCallbacksAndMessages(null);
    }
}

6.3 心跳包设计

java 复制代码
/**
 * 心跳包格式设计原则:
 * 1. 尽可能小(节省流量)
 * 2. 包含必要信息(时间戳用于RTT计算)
 * 3. 区分请求和响应
 */
public class HeartbeatPacket {

    /**
     * 简单心跳包(4字节)
     *
     * ┌─────────┬─────────┐
     * │  Type   │ Payload │
     * │ 1 byte  │ 3 bytes │
     * └─────────┴─────────┘
     *
     * Type: 0x01=Ping, 0x02=Pong
     * Payload: 可选,用于携带序号或时间戳
     */
    public static byte[] createPing(int sequence) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.put((byte) 0x01);
        buffer.put((byte) ((sequence >> 16) & 0xFF));
        buffer.put((byte) ((sequence >> 8) & 0xFF));
        buffer.put((byte) (sequence & 0xFF));
        return buffer.array();
    }

    public static byte[] createPong(int sequence) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.put((byte) 0x02);
        buffer.put((byte) ((sequence >> 16) & 0xFF));
        buffer.put((byte) ((sequence >> 8) & 0xFF));
        buffer.put((byte) (sequence & 0xFF));
        return buffer.array();
    }

    /**
     * 带时间戳的心跳包(12字节)
     *
     * ┌─────────┬───────────┬───────────┐
     * │  Type   │ Sequence  │ Timestamp │
     * │ 1 byte  │  3 bytes  │  8 bytes  │
     * └─────────┴───────────┴───────────┘
     */
    public static byte[] createTimestampPing(int sequence) {
        ByteBuffer buffer = ByteBuffer.allocate(12);
        buffer.put((byte) 0x01);
        buffer.put((byte) ((sequence >> 16) & 0xFF));
        buffer.put((byte) ((sequence >> 8) & 0xFF));
        buffer.put((byte) (sequence & 0xFF));
        buffer.putLong(System.currentTimeMillis());
        return buffer.array();
    }
}

断线重连策略

7.1 指数退避算法

java 复制代码
public class ReconnectManager {
    private static final long BASE_DELAY = 1000;      // 基础延迟 1秒
    private static final long MAX_DELAY = 60000;      // 最大延迟 60秒
    private static final double MULTIPLIER = 2.0;     // 倍数
    private static final double JITTER = 0.2;         // 抖动 ±20%

    private int attemptCount = 0;
    private boolean isReconnecting = false;
    private final Handler handler = new Handler(Looper.getMainLooper());
    private final SocketClient client;
    private final Random random = new Random();

    public ReconnectManager(SocketClient client) {
        this.client = client;
    }

    /**
     * 指数退避算法:
     *
     * delay = min(BASE_DELAY * MULTIPLIER^attempt, MAX_DELAY) * (1 ± JITTER)
     *
     * 示例序列:
     * 尝试1: 1s × (1±0.2) = 0.8-1.2s
     * 尝试2: 2s × (1±0.2) = 1.6-2.4s
     * 尝试3: 4s × (1±0.2) = 3.2-4.8s
     * 尝试4: 8s × (1±0.2) = 6.4-9.6s
     * 尝试5: 16s × (1±0.2) = 12.8-19.2s
     * 尝试6: 32s × (1±0.2) = 25.6-38.4s
     * 尝试7+: 60s × (1±0.2) = 48-72s (capped)
     */
    public void scheduleReconnect() {
        if (isReconnecting) return;
        isReconnecting = true;

        long delay = calculateDelay();
        Log.d("Reconnect", String.format(
            "第%d次重连,延迟%dms", attemptCount + 1, delay));

        handler.postDelayed(this::doReconnect, delay);
    }

    private long calculateDelay() {
        // 计算基础延迟
        double exponentialDelay = BASE_DELAY * Math.pow(MULTIPLIER, attemptCount);
        long delay = (long) Math.min(exponentialDelay, MAX_DELAY);

        // 添加抖动(避免雷群效应)
        double jitter = 1.0 + (random.nextDouble() * 2 - 1) * JITTER;
        delay = (long) (delay * jitter);

        return delay;
    }

    private void doReconnect() {
        attemptCount++;

        client.connect(new ConnectionCallback() {
            @Override
            public void onSuccess() {
                isReconnecting = false;
                attemptCount = 0;
                Log.d("Reconnect", "重连成功");
            }

            @Override
            public void onFailure(Exception e) {
                isReconnecting = false;
                Log.w("Reconnect", "重连失败: " + e.getMessage());
                scheduleReconnect();  // 继续尝试
            }
        });
    }

    public void reset() {
        attemptCount = 0;
        isReconnecting = false;
        handler.removeCallbacksAndMessages(null);
    }

    public void stop() {
        isReconnecting = false;
        handler.removeCallbacksAndMessages(null);
    }
}

7.2 完整的连接状态机

java 复制代码
public class ConnectionStateMachine {

    public enum State {
        DISCONNECTED,    // 断开
        CONNECTING,      // 连接中
        CONNECTED,       // 已连接
        RECONNECTING,    // 重连中
        SUSPENDED        // 挂起(如:网络不可用)
    }

    public enum Event {
        CONNECT,         // 用户发起连接
        CONNECT_SUCCESS, // 连接成功
        CONNECT_FAILURE, // 连接失败
        DISCONNECT,      // 用户断开
        NETWORK_LOST,    // 网络丢失
        NETWORK_AVAILABLE, // 网络恢复
        HEARTBEAT_TIMEOUT, // 心跳超时
        SERVER_CLOSE     // 服务端关闭
    }

    /**
     * 状态转换表:
     *
     * ┌─────────────────┬──────────────────┬─────────────────┐
     * │   Current State │      Event       │   Next State    │
     * ├─────────────────┼──────────────────┼─────────────────┤
     * │  DISCONNECTED   │     CONNECT      │   CONNECTING    │
     * │  CONNECTING     │  CONNECT_SUCCESS │   CONNECTED     │
     * │  CONNECTING     │  CONNECT_FAILURE │   RECONNECTING  │
     * │  CONNECTED      │   DISCONNECT     │   DISCONNECTED  │
     * │  CONNECTED      │   NETWORK_LOST   │   SUSPENDED     │
     * │  CONNECTED      │ HEARTBEAT_TIMEOUT│   RECONNECTING  │
     * │  CONNECTED      │   SERVER_CLOSE   │   RECONNECTING  │
     * │  RECONNECTING   │  CONNECT_SUCCESS │   CONNECTED     │
     * │  RECONNECTING   │  CONNECT_FAILURE │   RECONNECTING  │
     * │  RECONNECTING   │   DISCONNECT     │   DISCONNECTED  │
     * │  SUSPENDED      │ NETWORK_AVAILABLE│   RECONNECTING  │
     * │  SUSPENDED      │   DISCONNECT     │   DISCONNECTED  │
     * └─────────────────┴──────────────────┴─────────────────┘
     */

    private State currentState = State.DISCONNECTED;
    private final List<StateChangeListener> listeners = new ArrayList<>();

    public synchronized void handleEvent(Event event) {
        State previousState = currentState;
        State nextState = getNextState(currentState, event);

        if (nextState != null && nextState != currentState) {
            currentState = nextState;
            notifyStateChange(previousState, currentState, event);
        }
    }

    private State getNextState(State current, Event event) {
        switch (current) {
            case DISCONNECTED:
                if (event == Event.CONNECT) return State.CONNECTING;
                break;

            case CONNECTING:
                if (event == Event.CONNECT_SUCCESS) return State.CONNECTED;
                if (event == Event.CONNECT_FAILURE) return State.RECONNECTING;
                if (event == Event.DISCONNECT) return State.DISCONNECTED;
                break;

            case CONNECTED:
                if (event == Event.DISCONNECT) return State.DISCONNECTED;
                if (event == Event.NETWORK_LOST) return State.SUSPENDED;
                if (event == Event.HEARTBEAT_TIMEOUT) return State.RECONNECTING;
                if (event == Event.SERVER_CLOSE) return State.RECONNECTING;
                break;

            case RECONNECTING:
                if (event == Event.CONNECT_SUCCESS) return State.CONNECTED;
                if (event == Event.CONNECT_FAILURE) return State.RECONNECTING;
                if (event == Event.DISCONNECT) return State.DISCONNECTED;
                if (event == Event.NETWORK_LOST) return State.SUSPENDED;
                break;

            case SUSPENDED:
                if (event == Event.NETWORK_AVAILABLE) return State.RECONNECTING;
                if (event == Event.DISCONNECT) return State.DISCONNECTED;
                break;
        }
        return null;
    }

    public State getCurrentState() {
        return currentState;
    }

    public void addStateChangeListener(StateChangeListener listener) {
        listeners.add(listener);
    }

    private void notifyStateChange(State from, State to, Event event) {
        for (StateChangeListener listener : listeners) {
            listener.onStateChanged(from, to, event);
        }
    }

    public interface StateChangeListener {
        void onStateChanged(State from, State to, Event event);
    }
}

粘包与拆包

8.1 问题本质

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    TCP 是流式协议                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  应用层发送:  [消息A] [消息B] [消息C]                                │
│                                                                      │
│  TCP发送缓冲区可能的情况:                                           │
│                                                                      │
│  情况1(正常): [消息A] | [消息B] | [消息C]                          │
│                                                                      │
│  情况2(粘包): [消息A][消息B] | [消息C]                             │
│                                                                      │
│  情况3(拆包): [消息A的前半] | [消息A的后半][消息B] | [消息C]        │
│                                                                      │
│  情况4(混合): [消息A][消息B的前半] | [消息B的后半][消息C]           │
│                                                                      │
│  原因:                                                              │
│  1. TCP是面向流的,不保留消息边界                                    │
│  2. Nagle算法合并小包                                                │
│  3. 网络MTU限制导致分片                                              │
│  4. 接收缓冲区大小限制                                               │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

8.2 解决方案

java 复制代码
/**
 * 协议设计方案对比:
 *
 * 方案1:固定长度
 * ┌────────────────────────────────────┐
 * │ Data (固定100字节,不足补0)         │
 * └────────────────────────────────────┘
 * 优点:简单
 * 缺点:浪费带宽
 *
 * 方案2:分隔符
 * ┌─────────────┬────┐
 * │    Data     │ \n │
 * └─────────────┴────┘
 * 优点:简单,适合文本协议
 * 缺点:数据中不能包含分隔符
 *
 * 方案3:长度字段(推荐)
 * ┌─────────┬─────────────┐
 * │ Length  │    Data     │
 * │ 4 bytes │ N bytes     │
 * └─────────┴─────────────┘
 * 优点:灵活,高效
 * 缺点:实现稍复杂
 *
 * 方案4:TLV格式
 * ┌──────┬─────────┬─────────────┐
 * │ Type │ Length  │    Value    │
 * │ 2B   │ 4B      │ N bytes     │
 * └──────┴─────────┴─────────────┘
 * 优点:可扩展,自描述
 * 缺点:开销稍大
 */
public class PacketCodec {

    private static final int HEADER_SIZE = 4;  // 长度字段4字节
    private static final int MAX_PACKET_SIZE = 1024 * 1024;  // 最大1MB

    private ByteBuffer readBuffer = ByteBuffer.allocate(64 * 1024);
    private int expectedLength = -1;

    /**
     * 编码:添加长度头
     */
    public byte[] encode(byte[] data) {
        ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE + data.length);
        buffer.putInt(data.length);
        buffer.put(data);
        return buffer.array();
    }

    /**
     * 解码:处理粘包/拆包
     *
     * 返回完整的消息列表,不完整的数据保留在缓冲区
     */
    public List<byte[]> decode(byte[] receivedData) {
        List<byte[]> messages = new ArrayList<>();

        // 追加新数据到缓冲区
        ensureCapacity(receivedData.length);
        readBuffer.put(receivedData);
        readBuffer.flip();  // 切换到读模式

        while (true) {
            // 状态1:需要读取长度
            if (expectedLength == -1) {
                if (readBuffer.remaining() < HEADER_SIZE) {
                    break;  // 数据不足,等待更多数据
                }
                expectedLength = readBuffer.getInt();

                // 安全检查
                if (expectedLength < 0 || expectedLength > MAX_PACKET_SIZE) {
                    throw new IllegalStateException(
                        "Invalid packet length: " + expectedLength);
                }
            }

            // 状态2:需要读取数据
            if (readBuffer.remaining() < expectedLength) {
                break;  // 数据不足,等待更多数据
            }

            // 读取完整消息
            byte[] message = new byte[expectedLength];
            readBuffer.get(message);
            messages.add(message);

            // 重置状态,准备读取下一个消息
            expectedLength = -1;
        }

        // 压缩缓冲区,保留未处理的数据
        readBuffer.compact();

        return messages;
    }

    private void ensureCapacity(int additionalSize) {
        if (readBuffer.remaining() < additionalSize) {
            // 扩容
            ByteBuffer newBuffer = ByteBuffer.allocate(
                readBuffer.capacity() * 2 + additionalSize);
            readBuffer.flip();
            newBuffer.put(readBuffer);
            readBuffer = newBuffer;
        }
    }

    public void reset() {
        readBuffer.clear();
        expectedLength = -1;
    }
}

8.3 完整的协议实现

java 复制代码
/**
 * 完整的二进制协议设计:
 *
 * ┌──────────────────────────────────────────────────────────────┐
 * │                       Packet Structure                        │
 * ├──────┬──────┬──────┬──────┬──────┬───────────┬──────────────┤
 * │Magic │ Ver  │ Type │ Seq  │ Len  │  Payload  │   Checksum   │
 * │ 2B   │ 1B   │ 1B   │ 4B   │ 4B   │  N bytes  │     4B       │
 * └──────┴──────┴──────┴──────┴──────┴───────────┴──────────────┘
 *
 * Magic: 魔数,用于识别协议 (0xCAFE)
 * Ver: 协议版本
 * Type: 消息类型
 * Seq: 序列号
 * Len: Payload长度
 * Payload: 消息体
 * Checksum: CRC32校验
 */
public class Protocol {

    public static final short MAGIC = (short) 0xCAFE;
    public static final byte VERSION = 1;
    public static final int HEADER_SIZE = 12;  // Magic(2) + Ver(1) + Type(1) + Seq(4) + Len(4)
    public static final int CHECKSUM_SIZE = 4;

    // 消息类型
    public static final byte TYPE_HEARTBEAT_REQ = 0x01;
    public static final byte TYPE_HEARTBEAT_RSP = 0x02;
    public static final byte TYPE_AUTH_REQ = 0x10;
    public static final byte TYPE_AUTH_RSP = 0x11;
    public static final byte TYPE_MESSAGE = 0x20;
    public static final byte TYPE_ACK = 0x21;

    private final AtomicInteger sequenceGenerator = new AtomicInteger(0);
    private final CRC32 crc32 = new CRC32();

    public static class Packet {
        public byte version;
        public byte type;
        public int sequence;
        public byte[] payload;

        public Packet(byte type, byte[] payload) {
            this.version = VERSION;
            this.type = type;
            this.payload = payload;
        }
    }

    public byte[] encode(Packet packet) {
        int totalLength = HEADER_SIZE + packet.payload.length + CHECKSUM_SIZE;
        ByteBuffer buffer = ByteBuffer.allocate(totalLength);

        // 写入头部
        buffer.putShort(MAGIC);
        buffer.put(packet.version);
        buffer.put(packet.type);
        buffer.putInt(sequenceGenerator.incrementAndGet());
        buffer.putInt(packet.payload.length);

        // 写入载荷
        buffer.put(packet.payload);

        // 计算并写入校验和
        crc32.reset();
        crc32.update(buffer.array(), 0, totalLength - CHECKSUM_SIZE);
        buffer.putInt((int) crc32.getValue());

        return buffer.array();
    }

    public Packet decode(byte[] data) throws ProtocolException {
        if (data.length < HEADER_SIZE + CHECKSUM_SIZE) {
            throw new ProtocolException("数据包太短");
        }

        ByteBuffer buffer = ByteBuffer.wrap(data);

        // 验证魔数
        short magic = buffer.getShort();
        if (magic != MAGIC) {
            throw new ProtocolException("无效的魔数: " + Integer.toHexString(magic));
        }

        // 读取头部
        byte version = buffer.get();
        byte type = buffer.get();
        int sequence = buffer.getInt();
        int payloadLength = buffer.getInt();

        // 验证长度
        if (payloadLength < 0 || payloadLength > data.length - HEADER_SIZE - CHECKSUM_SIZE) {
            throw new ProtocolException("无效的载荷长度");
        }

        // 读取载荷
        byte[] payload = new byte[payloadLength];
        buffer.get(payload);

        // 验证校验和
        int receivedChecksum = buffer.getInt();
        crc32.reset();
        crc32.update(data, 0, data.length - CHECKSUM_SIZE);
        int calculatedChecksum = (int) crc32.getValue();

        if (receivedChecksum != calculatedChecksum) {
            throw new ProtocolException("校验和不匹配");
        }

        Packet packet = new Packet(type, payload);
        packet.version = version;
        packet.sequence = sequence;
        return packet;
    }

    public static class ProtocolException extends Exception {
        public ProtocolException(String message) {
            super(message);
        }
    }
}

SSL/TLS 安全通信

9.1 SSL/TLS 握手过程

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    TLS 1.2 握手过程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│     Client                                        Server             │
│        │                                             │               │
│        │ ────── ClientHello ──────────────────────► │               │
│        │        (版本、随机数、支持的加密套件)        │               │
│        │                                             │               │
│        │ ◄────── ServerHello ────────────────────── │               │
│        │         (选定的版本和加密套件)              │               │
│        │                                             │               │
│        │ ◄────── Certificate ────────────────────── │               │
│        │         (服务器证书链)                      │               │
│        │                                             │               │
│        │ ◄────── ServerKeyExchange ─────────────── │               │
│        │         (DH参数,可选)                      │               │
│        │                                             │               │
│        │ ◄────── ServerHelloDone ───────────────── │               │
│        │                                             │               │
│        │ ────── ClientKeyExchange ─────────────────►│               │
│        │        (预主密钥)                           │               │
│        │                                             │               │
│        │ ────── ChangeCipherSpec ──────────────────►│               │
│        │                                             │               │
│        │ ────── Finished ──────────────────────────►│               │
│        │                                             │               │
│        │ ◄────── ChangeCipherSpec ─────────────────│               │
│        │                                             │               │
│        │ ◄────── Finished ─────────────────────────│               │
│        │                                             │               │
│        │◄═══════════ 加密通信 ═══════════════════►│               │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

9.2 SSLSocket 实现

java 复制代码
public class SecureSocketClient {
    private SSLSocket sslSocket;
    private SSLContext sslContext;

    /**
     * 初始化SSL上下文
     *
     * 三种验证模式:
     * 1. 系统CA验证(默认)
     * 2. 自签名证书(需要添加到信任库)
     * 3. 证书固定(Certificate Pinning)
     */
    public void initSSL(Context context) throws Exception {
        sslContext = SSLContext.getInstance("TLS");

        // 方式1:使用系统默认信任库
        // sslContext.init(null, null, null);

        // 方式2:自定义信任库(包含自签名证书)
        TrustManagerFactory tmf = createTrustManagerFactory(context);
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
    }

    private TrustManagerFactory createTrustManagerFactory(Context context)
            throws Exception {
        // 加载自定义证书
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream certInput = context.getAssets().open("server.crt");
        Certificate ca = cf.generateCertificate(certInput);
        certInput.close();

        // 创建包含该证书的KeyStore
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        keyStore.setCertificateEntry("server", ca);

        // 创建TrustManager
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);

        return tmf;
    }

    public void connect(String host, int port) throws IOException {
        SSLSocketFactory factory = sslContext.getSocketFactory();
        sslSocket = (SSLSocket) factory.createSocket(host, port);

        // 配置SSL参数
        configureSSL(host);

        // 执行握手
        sslSocket.startHandshake();

        // 验证主机名
        verifyHostname(host);
    }

    private void configureSSL(String host) {
        // 设置支持的协议版本
        sslSocket.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});

        // 设置加密套件(排除弱加密)
        String[] strongCipherSuites = filterWeakCipherSuites(
            sslSocket.getSupportedCipherSuites());
        sslSocket.setEnabledCipherSuites(strongCipherSuites);

        // 启用SNI(Server Name Indication)
        SSLParameters params = sslSocket.getSSLParameters();
        params.setServerNames(Collections.singletonList(
            new SNIHostName(host)));
        sslSocket.setSSLParameters(params);
    }

    private String[] filterWeakCipherSuites(String[] suites) {
        return Arrays.stream(suites)
            .filter(s -> !s.contains("NULL"))
            .filter(s -> !s.contains("EXPORT"))
            .filter(s -> !s.contains("DES"))
            .filter(s -> !s.contains("RC4"))
            .filter(s -> !s.contains("MD5"))
            .toArray(String[]::new);
    }

    private void verifyHostname(String expectedHost) throws SSLPeerUnverifiedException {
        HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
        if (!verifier.verify(expectedHost, sslSocket.getSession())) {
            throw new SSLPeerUnverifiedException(
                "主机名验证失败: " + expectedHost);
        }
    }
}

9.3 证书固定(Certificate Pinning)

java 复制代码
/**
 * 证书固定:防止中间人攻击
 *
 * 原理:在客户端预置服务器证书的公钥或指纹,
 *       连接时验证服务器证书是否匹配。
 *
 * 固定方式:
 * 1. 固定证书:整个证书的SHA256
 * 2. 固定公钥:证书公钥的SHA256(推荐,证书续期不受影响)
 */
public class CertificatePinner {

    // 预置的公钥SHA256(Base64编码)
    private static final Set<String> PINNED_KEYS = new HashSet<>(Arrays.asList(
        "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",  // 主证书
        "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="   // 备用证书
    ));

    public TrustManager[] createPinningTrustManagers() {
        return new TrustManager[]{
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    // 客户端证书验证(如果需要)
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    if (chain == null || chain.length == 0) {
                        throw new CertificateException("空证书链");
                    }

                    // 先进行标准验证
                    try {
                        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                            TrustManagerFactory.getDefaultAlgorithm());
                        tmf.init((KeyStore) null);

                        for (TrustManager tm : tmf.getTrustManagers()) {
                            if (tm instanceof X509TrustManager) {
                                ((X509TrustManager) tm).checkServerTrusted(chain, authType);
                            }
                        }
                    } catch (Exception e) {
                        throw new CertificateException("标准验证失败", e);
                    }

                    // 证书固定验证
                    boolean pinMatched = false;
                    for (X509Certificate cert : chain) {
                        String publicKeyHash = getPublicKeyHash(cert);
                        if (PINNED_KEYS.contains(publicKeyHash)) {
                            pinMatched = true;
                            break;
                        }
                    }

                    if (!pinMatched) {
                        throw new CertificateException("证书固定验证失败");
                    }
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };
    }

    private String getPublicKeyHash(X509Certificate cert) {
        try {
            byte[] publicKeyBytes = cert.getPublicKey().getEncoded();
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(publicKeyBytes);
            return "sha256/" + Base64.encodeToString(hash, Base64.NO_WRAP);
        } catch (Exception e) {
            return "";
        }
    }
}

性能优化

10.1 Buffer 池化

java 复制代码
/**
 * ByteBuffer对象池
 *
 * 避免频繁创建和销毁Buffer对象,减少GC压力
 */
public class ByteBufferPool {

    private static final int[] BUCKET_SIZES = {
        512, 1024, 2048, 4096, 8192, 16384, 32768, 65536
    };

    private final Map<Integer, Queue<ByteBuffer>> heapPools = new ConcurrentHashMap<>();
    private final Map<Integer, Queue<ByteBuffer>> directPools = new ConcurrentHashMap<>();
    private final AtomicInteger allocatedHeap = new AtomicInteger(0);
    private final AtomicInteger allocatedDirect = new AtomicInteger(0);

    private static final int MAX_POOL_SIZE = 64;  // 每个桶最多缓存64个

    private static final ByteBufferPool INSTANCE = new ByteBufferPool();

    public static ByteBufferPool getInstance() {
        return INSTANCE;
    }

    private ByteBufferPool() {
        for (int size : BUCKET_SIZES) {
            heapPools.put(size, new ConcurrentLinkedQueue<>());
            directPools.put(size, new ConcurrentLinkedQueue<>());
        }
    }

    /**
     * 获取合适大小的Buffer
     */
    public ByteBuffer acquire(int minCapacity, boolean direct) {
        int bucketSize = findBucketSize(minCapacity);
        Map<Integer, Queue<ByteBuffer>> pools = direct ? directPools : heapPools;
        Queue<ByteBuffer> pool = pools.get(bucketSize);

        ByteBuffer buffer = null;
        if (pool != null) {
            buffer = pool.poll();
        }

        if (buffer == null) {
            // 池中没有可用的,创建新的
            buffer = direct
                ? ByteBuffer.allocateDirect(bucketSize)
                : ByteBuffer.allocate(bucketSize);

            if (direct) {
                allocatedDirect.incrementAndGet();
            } else {
                allocatedHeap.incrementAndGet();
            }
        }

        buffer.clear();
        buffer.limit(minCapacity);  // 限制到请求的大小
        return buffer;
    }

    /**
     * 归还Buffer到池中
     */
    public void release(ByteBuffer buffer) {
        if (buffer == null) return;

        int bucketSize = findBucketSize(buffer.capacity());
        if (bucketSize != buffer.capacity()) {
            // 不是标准大小,不回收
            return;
        }

        Map<Integer, Queue<ByteBuffer>> pools =
            buffer.isDirect() ? directPools : heapPools;
        Queue<ByteBuffer> pool = pools.get(bucketSize);

        if (pool != null && pool.size() < MAX_POOL_SIZE) {
            buffer.clear();
            pool.offer(buffer);
        }
        // 否则让GC回收
    }

    private int findBucketSize(int size) {
        for (int bucketSize : BUCKET_SIZES) {
            if (bucketSize >= size) {
                return bucketSize;
            }
        }
        // 超过最大桶,返回请求的大小(向上取整到4KB对齐)
        return ((size + 4095) / 4096) * 4096;
    }

    public String getStats() {
        return String.format(
            "Pool Stats: Heap=%d, Direct=%d",
            allocatedHeap.get(), allocatedDirect.get());
    }
}

10.2 零拷贝技术

java 复制代码
/**
 * 零拷贝原理:
 *
 * 传统拷贝路径(4次拷贝):
 * ┌────────┐    ┌────────┐    ┌────────┐    ┌────────┐    ┌────────┐
 * │  磁盘  │ ─► │ 内核   │ ─► │  用户  │ ─► │ Socket │ ─► │  网卡  │
 * │        │    │ Buffer │    │ Buffer │    │ Buffer │    │        │
 * └────────┘    └────────┘    └────────┘    └────────┘    └────────┘
 *            DMA拷贝      CPU拷贝      CPU拷贝      DMA拷贝
 *
 * 零拷贝路径(2次DMA拷贝):
 * ┌────────┐    ┌────────┐                              ┌────────┐
 * │  磁盘  │ ─► │ 内核   │ ─────────────────────────► │  网卡  │
 * │        │    │ Buffer │    sendfile() / mmap()       │        │
 * └────────┘    └────────┘                              └────────┘
 *            DMA拷贝                                  DMA拷贝
 */
public class ZeroCopyDemo {

    /**
     * 使用FileChannel.transferTo实现零拷贝
     */
    public void sendFile(String filePath, SocketChannel socketChannel)
            throws IOException {
        try (FileChannel fileChannel = FileChannel.open(
                Paths.get(filePath), StandardOpenOption.READ)) {

            long fileSize = fileChannel.size();
            long transferred = 0;

            while (transferred < fileSize) {
                // transferTo内部使用sendfile系统调用
                long bytes = fileChannel.transferTo(
                    transferred,
                    fileSize - transferred,
                    socketChannel);

                if (bytes <= 0) {
                    break;
                }
                transferred += bytes;
            }
        }
    }

    /**
     * 使用内存映射文件
     */
    public void sendFileWithMmap(String filePath, SocketChannel socketChannel)
            throws IOException {
        try (FileChannel fileChannel = FileChannel.open(
                Paths.get(filePath), StandardOpenOption.READ)) {

            long fileSize = fileChannel.size();

            // 映射文件到内存
            MappedByteBuffer mappedBuffer = fileChannel.map(
                FileChannel.MapMode.READ_ONLY, 0, fileSize);

            // 直接发送映射的缓冲区
            while (mappedBuffer.hasRemaining()) {
                socketChannel.write(mappedBuffer);
            }
        }
    }
}

10.3 连接池设计

java 复制代码
/**
 * Socket连接池
 *
 * 适用于短连接场景,复用TCP连接减少握手开销
 */
public class SocketPool {

    private final String host;
    private final int port;
    private final int maxConnections;
    private final long maxIdleTime;
    private final long connectionTimeout;

    private final Queue<PooledSocket> idleConnections = new ConcurrentLinkedQueue<>();
    private final AtomicInteger activeCount = new AtomicInteger(0);
    private final AtomicInteger totalCreated = new AtomicInteger(0);

    private final ScheduledExecutorService cleaner =
        Executors.newSingleThreadScheduledExecutor();

    public SocketPool(String host, int port, int maxConnections,
            long maxIdleTime, long connectionTimeout) {
        this.host = host;
        this.port = port;
        this.maxConnections = maxConnections;
        this.maxIdleTime = maxIdleTime;
        this.connectionTimeout = connectionTimeout;

        // 定期清理空闲连接
        cleaner.scheduleWithFixedDelay(
            this::cleanIdleConnections, 30, 30, TimeUnit.SECONDS);
    }

    /**
     * 获取连接
     */
    public PooledSocket acquire() throws IOException {
        // 尝试从空闲池获取
        PooledSocket pooled = idleConnections.poll();
        while (pooled != null) {
            if (pooled.isValid()) {
                activeCount.incrementAndGet();
                return pooled;
            }
            // 连接已失效,关闭它
            pooled.closeQuietly();
            pooled = idleConnections.poll();
        }

        // 空闲池没有可用连接,检查是否可以创建新的
        if (activeCount.get() + idleConnections.size() >= maxConnections) {
            throw new IOException("连接池已满");
        }

        // 创建新连接
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(host, port), (int) connectionTimeout);
        socket.setSoTimeout(30000);
        socket.setTcpNoDelay(true);

        totalCreated.incrementAndGet();
        activeCount.incrementAndGet();

        return new PooledSocket(socket, this);
    }

    /**
     * 归还连接
     */
    void release(PooledSocket pooled) {
        activeCount.decrementAndGet();

        if (pooled.isValid() && idleConnections.size() < maxConnections) {
            pooled.lastUsedTime = System.currentTimeMillis();
            idleConnections.offer(pooled);
        } else {
            pooled.closeQuietly();
        }
    }

    private void cleanIdleConnections() {
        long now = System.currentTimeMillis();
        Iterator<PooledSocket> iterator = idleConnections.iterator();

        while (iterator.hasNext()) {
            PooledSocket pooled = iterator.next();
            if (now - pooled.lastUsedTime > maxIdleTime || !pooled.isValid()) {
                iterator.remove();
                pooled.closeQuietly();
            }
        }
    }

    public void shutdown() {
        cleaner.shutdown();
        PooledSocket pooled;
        while ((pooled = idleConnections.poll()) != null) {
            pooled.closeQuietly();
        }
    }

    public static class PooledSocket implements Closeable {
        private final Socket socket;
        private final SocketPool pool;
        private long lastUsedTime;
        private boolean closed = false;

        PooledSocket(Socket socket, SocketPool pool) {
            this.socket = socket;
            this.pool = pool;
            this.lastUsedTime = System.currentTimeMillis();
        }

        public Socket getSocket() {
            return socket;
        }

        public InputStream getInputStream() throws IOException {
            return socket.getInputStream();
        }

        public OutputStream getOutputStream() throws IOException {
            return socket.getOutputStream();
        }

        boolean isValid() {
            return !closed && socket.isConnected() && !socket.isClosed();
        }

        @Override
        public void close() {
            if (!closed) {
                closed = true;
                pool.release(this);
            }
        }

        void closeQuietly() {
            try {
                socket.close();
            } catch (IOException ignored) {
            }
        }
    }
}

实战架构设计

11.1 完整的Socket客户端架构

java 复制代码
/**
 * 生产级Socket客户端架构
 *
 * ┌──────────────────────────────────────────────────────────────────┐
 * │                        Application Layer                         │
 * │                    (Business Logic / UI)                         │
 * └───────────────────────────┬──────────────────────────────────────┘
 *                             │
 * ┌───────────────────────────▼──────────────────────────────────────┐
 * │                      SocketClient (API)                          │
 * │  • connect() / disconnect()                                      │
 * │  • send(message) / addListener()                                 │
 * └───────────────────────────┬──────────────────────────────────────┘
 *                             │
 * ┌───────────────────────────▼──────────────────────────────────────┐
 * │                     Connection Manager                           │
 * │  • 状态管理 (State Machine)                                      │
 * │  • 重连策略 (Exponential Backoff)                                │
 * │  • 心跳管理 (Smart Heartbeat)                                    │
 * └───────────────────────────┬──────────────────────────────────────┘
 *                             │
 * ┌───────────────────────────▼──────────────────────────────────────┐
 * │                     Protocol Layer                               │
 * │  • 消息编解码 (Codec)                                            │
 * │  • 粘包处理 (Frame Decoder)                                      │
 * │  • 序列化 (JSON/Protobuf/Custom)                                 │
 * └───────────────────────────┬──────────────────────────────────────┘
 *                             │
 * ┌───────────────────────────▼──────────────────────────────────────┐
 * │                     Transport Layer                              │
 * │  • TCP/SSL Socket                                                │
 * │  • NIO Channel                                                   │
 * │  • I/O Thread                                                    │
 * └──────────────────────────────────────────────────────────────────┘
 */
public class SocketClient {

    private final SocketConfig config;
    private final ConnectionManager connectionManager;
    private final ProtocolCodec codec;
    private final MessageDispatcher dispatcher;
    private final ExecutorService callbackExecutor;

    private volatile Socket socket;
    private volatile DataInputStream inputStream;
    private volatile DataOutputStream outputStream;
    private volatile boolean running = false;

    private final List<SocketListener> listeners = new CopyOnWriteArrayList<>();

    public SocketClient(SocketConfig config) {
        this.config = config;
        this.codec = new ProtocolCodec();
        this.dispatcher = new MessageDispatcher();
        this.callbackExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "Socket-Callback");
            t.setDaemon(true);
            return t;
        });
        this.connectionManager = new ConnectionManager(this, config);
    }

    public void connect() {
        connectionManager.connect();
    }

    public void disconnect() {
        connectionManager.disconnect();
    }

    public void send(Message message, SendCallback callback) {
        if (!isConnected()) {
            if (callback != null) {
                callback.onFailure(new IOException("未连接"));
            }
            return;
        }

        try {
            byte[] data = codec.encode(message);
            synchronized (outputStream) {
                outputStream.write(data);
                outputStream.flush();
            }
            if (callback != null) {
                callback.onSuccess();
            }
        } catch (IOException e) {
            if (callback != null) {
                callback.onFailure(e);
            }
            connectionManager.onConnectionLost(e);
        }
    }

    public <T> void sendWithAck(Message message, Class<T> responseType,
            ResponseCallback<T> callback, long timeoutMs) {
        dispatcher.registerCallback(message.getSequence(), responseType,
            callback, timeoutMs);
        send(message, new SendCallback() {
            @Override
            public void onSuccess() {
                // 等待响应
            }

            @Override
            public void onFailure(Exception e) {
                dispatcher.cancelCallback(message.getSequence());
                callback.onFailure(e);
            }
        });
    }

    // 内部连接实现
    void doConnect() throws IOException {
        socket = new Socket();
        socket.setTcpNoDelay(config.isTcpNoDelay());
        socket.setKeepAlive(config.isKeepAlive());
        socket.setSoTimeout(config.getReadTimeout());
        socket.setReceiveBufferSize(config.getReceiveBufferSize());
        socket.setSendBufferSize(config.getSendBufferSize());

        InetSocketAddress address = new InetSocketAddress(
            config.getHost(), config.getPort());
        socket.connect(address, config.getConnectTimeout());

        inputStream = new DataInputStream(
            new BufferedInputStream(socket.getInputStream(), 8192));
        outputStream = new DataOutputStream(
            new BufferedOutputStream(socket.getOutputStream(), 8192));

        running = true;
        startReadLoop();
    }

    void doDisconnect() {
        running = false;
        closeQuietly(inputStream);
        closeQuietly(outputStream);
        closeQuietly(socket);
    }

    private void startReadLoop() {
        new Thread(() -> {
            while (running) {
                try {
                    byte[] data = codec.readFrame(inputStream);
                    if (data != null) {
                        Message message = codec.decode(data);
                        handleMessage(message);
                    }
                } catch (SocketTimeoutException e) {
                    // 读取超时,继续
                } catch (IOException e) {
                    if (running) {
                        connectionManager.onConnectionLost(e);
                    }
                    break;
                }
            }
        }, "Socket-Reader").start();
    }

    private void handleMessage(Message message) {
        // 先检查是否是等待响应的消息
        if (dispatcher.dispatch(message)) {
            return;
        }

        // 通知监听器
        callbackExecutor.execute(() -> {
            for (SocketListener listener : listeners) {
                try {
                    listener.onMessage(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public boolean isConnected() {
        return socket != null && socket.isConnected() && !socket.isClosed();
    }

    public void addListener(SocketListener listener) {
        listeners.add(listener);
    }

    public void removeListener(SocketListener listener) {
        listeners.remove(listener);
    }

    private void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ignored) {
            }
        }
    }

    // 接口定义
    public interface SocketListener {
        void onConnected();
        void onDisconnected(Throwable cause);
        void onMessage(Message message);
    }

    public interface SendCallback {
        void onSuccess();
        void onFailure(Exception e);
    }

    public interface ResponseCallback<T> {
        void onSuccess(T response);
        void onFailure(Exception e);
    }
}

11.2 消息分发器

java 复制代码
/**
 * 消息分发器:处理请求-响应模式
 */
public class MessageDispatcher {

    private final Map<Integer, PendingRequest<?>> pendingRequests =
        new ConcurrentHashMap<>();
    private final ScheduledExecutorService timeoutScheduler =
        Executors.newSingleThreadScheduledExecutor();

    public <T> void registerCallback(int sequence, Class<T> responseType,
            SocketClient.ResponseCallback<T> callback, long timeoutMs) {
        PendingRequest<T> request = new PendingRequest<>(responseType, callback);
        pendingRequests.put(sequence, request);

        // 设置超时
        request.timeoutFuture = timeoutScheduler.schedule(() -> {
            PendingRequest<?> removed = pendingRequests.remove(sequence);
            if (removed != null) {
                removed.callback.onFailure(new TimeoutException("响应超时"));
            }
        }, timeoutMs, TimeUnit.MILLISECONDS);
    }

    public void cancelCallback(int sequence) {
        PendingRequest<?> request = pendingRequests.remove(sequence);
        if (request != null && request.timeoutFuture != null) {
            request.timeoutFuture.cancel(false);
        }
    }

    @SuppressWarnings("unchecked")
    public boolean dispatch(Message message) {
        PendingRequest<?> request = pendingRequests.remove(message.getSequence());
        if (request == null) {
            return false;
        }

        if (request.timeoutFuture != null) {
            request.timeoutFuture.cancel(false);
        }

        try {
            Object response = parseResponse(message, request.responseType);
            ((SocketClient.ResponseCallback<Object>) request.callback).onSuccess(response);
        } catch (Exception e) {
            request.callback.onFailure(e);
        }

        return true;
    }

    private <T> T parseResponse(Message message, Class<T> type) {
        // 根据类型解析响应
        // 这里可以使用JSON、Protobuf等序列化方式
        return null;  // 简化示例
    }

    private static class PendingRequest<T> {
        final Class<T> responseType;
        final SocketClient.ResponseCallback<T> callback;
        ScheduledFuture<?> timeoutFuture;

        PendingRequest(Class<T> responseType, SocketClient.ResponseCallback<T> callback) {
            this.responseType = responseType;
            this.callback = callback;
        }
    }
}

调试与问题排查

12.1 常见问题诊断

java 复制代码
/**
 * Socket调试工具类
 */
public class SocketDiagnostics {

    /**
     * 诊断连接问题
     */
    public static DiagnosticResult diagnoseConnection(String host, int port) {
        DiagnosticResult result = new DiagnosticResult();

        // 1. DNS解析
        long dnsStart = System.currentTimeMillis();
        try {
            InetAddress[] addresses = InetAddress.getAllByName(host);
            result.dnsTime = System.currentTimeMillis() - dnsStart;
            result.resolvedAddresses = addresses;
            result.dnsSuccess = true;
        } catch (UnknownHostException e) {
            result.dnsTime = System.currentTimeMillis() - dnsStart;
            result.dnsError = e.getMessage();
            return result;
        }

        // 2. TCP连接
        for (InetAddress address : result.resolvedAddresses) {
            Socket socket = new Socket();
            long connectStart = System.currentTimeMillis();
            try {
                socket.connect(new InetSocketAddress(address, port), 5000);
                result.connectTime = System.currentTimeMillis() - connectStart;
                result.connectedAddress = address;
                result.connectSuccess = true;

                // 3. 获取本地端口和地址
                result.localAddress = socket.getLocalAddress();
                result.localPort = socket.getLocalPort();

                socket.close();
                break;
            } catch (IOException e) {
                result.connectTime = System.currentTimeMillis() - connectStart;
                result.connectError = e.getMessage();
            } finally {
                try { socket.close(); } catch (IOException ignored) {}
            }
        }

        return result;
    }

    /**
     * 测量RTT
     */
    public static long measureRTT(Socket socket, int samples) throws IOException {
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream());

        long totalRtt = 0;
        for (int i = 0; i < samples; i++) {
            long start = System.nanoTime();

            // 发送Ping
            out.writeByte(0x01);  // Ping type
            out.writeLong(start);
            out.flush();

            // 等待Pong
            byte type = in.readByte();
            long sentTime = in.readLong();

            long rtt = System.nanoTime() - start;
            totalRtt += rtt;

            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        }

        return totalRtt / samples / 1_000_000;  // 返回平均RTT(毫秒)
    }

    /**
     * 获取Socket状态信息
     */
    public static String getSocketInfo(Socket socket) {
        if (socket == null) return "Socket is null";

        StringBuilder sb = new StringBuilder();
        sb.append("=== Socket Info ===\n");
        sb.append("Connected: ").append(socket.isConnected()).append("\n");
        sb.append("Closed: ").append(socket.isClosed()).append("\n");
        sb.append("Bound: ").append(socket.isBound()).append("\n");
        sb.append("Input Shutdown: ").append(socket.isInputShutdown()).append("\n");
        sb.append("Output Shutdown: ").append(socket.isOutputShutdown()).append("\n");
        sb.append("Local: ").append(socket.getLocalSocketAddress()).append("\n");
        sb.append("Remote: ").append(socket.getRemoteSocketAddress()).append("\n");

        try {
            sb.append("TCP_NODELAY: ").append(socket.getTcpNoDelay()).append("\n");
            sb.append("SO_KEEPALIVE: ").append(socket.getKeepAlive()).append("\n");
            sb.append("SO_TIMEOUT: ").append(socket.getSoTimeout()).append("ms\n");
            sb.append("SO_RCVBUF: ").append(socket.getReceiveBufferSize()).append("\n");
            sb.append("SO_SNDBUF: ").append(socket.getSendBufferSize()).append("\n");
            sb.append("SO_LINGER: ").append(socket.getSoLinger()).append("\n");
        } catch (SocketException e) {
            sb.append("Error getting socket options: ").append(e.getMessage());
        }

        return sb.toString();
    }

    public static class DiagnosticResult {
        public boolean dnsSuccess;
        public long dnsTime;
        public String dnsError;
        public InetAddress[] resolvedAddresses;

        public boolean connectSuccess;
        public long connectTime;
        public String connectError;
        public InetAddress connectedAddress;
        public InetAddress localAddress;
        public int localPort;

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("=== Connection Diagnostic ===\n");
            sb.append("DNS: ").append(dnsSuccess ? "OK" : "FAILED");
            sb.append(" (").append(dnsTime).append("ms)");
            if (!dnsSuccess) sb.append(" Error: ").append(dnsError);
            sb.append("\n");

            if (resolvedAddresses != null) {
                sb.append("Resolved: ");
                for (InetAddress addr : resolvedAddresses) {
                    sb.append(addr.getHostAddress()).append(" ");
                }
                sb.append("\n");
            }

            sb.append("Connect: ").append(connectSuccess ? "OK" : "FAILED");
            sb.append(" (").append(connectTime).append("ms)");
            if (!connectSuccess) sb.append(" Error: ").append(connectError);
            sb.append("\n");

            if (connectSuccess) {
                sb.append("Connected to: ").append(connectedAddress).append("\n");
                sb.append("Local: ").append(localAddress).append(":").append(localPort);
            }

            return sb.toString();
        }
    }
}

12.2 网络日志记录

java 复制代码
/**
 * Socket数据包日志记录器
 */
public class PacketLogger {

    private static final String TAG = "PacketLogger";
    private static final SimpleDateFormat DATE_FORMAT =
        new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);

    private final boolean enabled;
    private final int maxPayloadLog;
    private final File logFile;
    private PrintWriter writer;

    public PacketLogger(boolean enabled, int maxPayloadLog, File logFile) {
        this.enabled = enabled;
        this.maxPayloadLog = maxPayloadLog;
        this.logFile = logFile;

        if (enabled && logFile != null) {
            try {
                writer = new PrintWriter(new FileWriter(logFile, true));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void logSend(byte[] data) {
        if (!enabled) return;
        log(">>>", data);
    }

    public void logReceive(byte[] data) {
        if (!enabled) return;
        log("<<<", data);
    }

    private void log(String direction, byte[] data) {
        String timestamp = DATE_FORMAT.format(new Date());
        String hex = bytesToHex(data, maxPayloadLog);
        String ascii = bytesToAscii(data, maxPayloadLog);

        String logLine = String.format("%s %s [%d bytes] %s | %s",
            timestamp, direction, data.length, hex, ascii);

        Log.d(TAG, logLine);

        if (writer != null) {
            writer.println(logLine);
            writer.flush();
        }
    }

    private String bytesToHex(byte[] data, int maxLen) {
        StringBuilder sb = new StringBuilder();
        int len = Math.min(data.length, maxLen);
        for (int i = 0; i < len; i++) {
            sb.append(String.format("%02X ", data[i]));
        }
        if (data.length > maxLen) {
            sb.append("...");
        }
        return sb.toString();
    }

    private String bytesToAscii(byte[] data, int maxLen) {
        StringBuilder sb = new StringBuilder();
        int len = Math.min(data.length, maxLen);
        for (int i = 0; i < len; i++) {
            char c = (char) data[i];
            sb.append(c >= 32 && c < 127 ? c : '.');
        }
        if (data.length > maxLen) {
            sb.append("...");
        }
        return sb.toString();
    }

    public void close() {
        if (writer != null) {
            writer.close();
        }
    }
}

12.3 常见问题与解决方案

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    常见问题排查指南                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  问题1:Connection refused                                          │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • 服务端未启动                                                      │
│  • 端口错误                                                          │
│  • 防火墙阻止                                                        │
│  排查:                                                              │
│  • telnet host port                                                 │
│  • netstat -an | grep port                                          │
│                                                                      │
│  问题2:Connection timed out                                        │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • 网络不通                                                          │
│  • 服务端不可达                                                      │
│  • 中间网络设备丢包                                                  │
│  排查:                                                              │
│  • ping host                                                        │
│  • traceroute host                                                  │
│                                                                      │
│  问题3:Connection reset                                            │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • 服务端异常关闭                                                    │
│  • 服务端发送RST                                                     │
│  • 网络设备重置连接                                                  │
│  排查:                                                              │
│  • 检查服务端日志                                                    │
│  • 抓包分析                                                          │
│                                                                      │
│  问题4:SocketTimeoutException (read)                               │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • 服务端处理慢                                                      │
│  • 网络延迟高                                                        │
│  • 超时设置过短                                                      │
│  解决:                                                              │
│  • 增加SO_TIMEOUT                                                   │
│  • 优化服务端性能                                                    │
│                                                                      │
│  问题5:Broken pipe                                                 │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • 向已关闭的连接写数据                                              │
│  • 对端已关闭读端                                                    │
│  解决:                                                              │
│  • 发送前检查连接状态                                                │
│  • 捕获异常并重连                                                    │
│                                                                      │
│  问题6:Too many open files                                         │
│  ─────────────────────────────────────                              │
│  原因:                                                              │
│  • Socket未正确关闭                                                  │
│  • 文件描述符泄漏                                                    │
│  解决:                                                              │
│  • 确保finally中关闭Socket                                          │
│  • 使用try-with-resources                                           │
│  • ulimit -n 增加限制                                               │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

总结

本文深入探讨了Android平台Socket编程的方方面面:

  1. 基础原理:从内核Socket结构到TCP状态机,理解底层机制
  2. 平台特性:Android特有的限制和解决方案
  3. 协议设计:心跳、重连、粘包处理等工程实践
  4. 安全通信:SSL/TLS和证书固定
  5. 性能优化:Buffer池化、零拷贝、连接池
  6. 架构设计:生产级Socket客户端的完整实现
  7. 问题排查:常见问题的诊断和解决

Socket编程看似简单,实则涉及网络协议、操作系统、并发编程等多个领域。希望本文能帮助读者建立完整的知识体系,在实际项目中游刃有余。


作者注:本文代码示例仅用于说明原理,生产环境请根据实际需求进行完善和测试。下期将发布socket通信框架,敬请期待

相关推荐
Kapaseker5 分钟前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 小时前
Andorid Google 登录接入文档
android
黄林晴2 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab15 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿18 小时前
Android MediaPlayer 笔记
android
Jony_18 小时前
Android 启动优化方案
android
阿巴斯甜18 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇18 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android
_小马快跑_1 天前
Kotlin | 从SparseArray、ArrayMap的set操作符看类型检查的不同
android