Socket常规用法详解:从TCP/UDP基础到多路复用进阶

一、TCP通信流程

服务端实现步骤

  1. 创建Socketint socket(int domain, int type, int protocol)

    // Java示例
    ServerSocket serverSocket = new ServerSocket();
  2. 绑定地址int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    serverSocket.bind(new InetSocketAddress("0.0.0.0", 8080));
  3. 监听连接int listen(int sockfd, int backlog)

    serverSocket.listen(50); // 设置等待队列长度为50
  4. 接受连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

    Socket clientSocket = serverSocket.accept(); // 阻塞等待客户端连接
  5. 数据读写

    // 读取数据
    InputStream in = clientSocket.getInputStream();
    // 写入数据
    OutputStream out = clientSocket.getOutputStream();
  6. 关闭连接

    clientSocket.close();
    serverSocket.close();

客户端实现步骤

  1. 创建Socket

    Socket socket = new Socket();
  2. 连接服务器

    socket.connect(new InetSocketAddress("127.0.0.1", 8080));
  3. 数据读写:(同服务端)
  4. 关闭连接

    socket.close();

TCP通信流程图

图1

实践建议

  • 服务端建议使用线程池处理多个客户端连接
  • 设置合理的SO_RCVBUF/SO_SNDBUF缓冲区大小(通常8K-64K)
  • 使用try-with-resources确保Socket正确关闭

二、UDP通信流程

无连接模式特点

  • 无需建立连接,直接发送数据报
  • 每个数据包独立路由,可能乱序到达
  • 适合实时性要求高、允许丢包的场景

核心API使用

// 服务端
DatagramSocket serverSocket = new DatagramSocket(8080);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(packet); // 阻塞接收

// 客户端
DatagramSocket clientSocket = new DatagramSocket();
byte[] data = "Hello".getBytes();
DatagramPacket sendPacket = new DatagramPacket(
    data, data.length, InetAddress.getByName("127.0.0.1"), 8080);
clientSocket.send(sendPacket);

地址复用关键设置

// 允许地址复用(多个进程绑定相同端口)
socket.setReuseAddress(true);
// 对于多播需要设置TTL
socket.setTimeToLive(1);

实践建议

  • UDP单次传输建议控制在1472字节以内(MTU 1500 - IP头20 - UDP头8)
  • 应用层需要实现重传和排序机制
  • 使用connect()方法可关联默认目标地址

三、多路复用技术

select/poll模型

// select示例
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
select(sockfd+1, &readfds, NULL, NULL, NULL);

特点

  • 线性扫描文件描述符集合
  • 最大支持1024个连接(select)
  • 跨平台兼容性好

epoll/kqueue模型

// Java NIO示例
Selector selector = Selector.open();
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(); // 阻塞
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            // 处理新连接
        }
    }
}

优势对比

特性select/pollepoll/kqueue
时间复杂度O(n)O(1)
最大连接数有限制(1024)系统内存决定
触发方式水平触发支持边缘触发
内存拷贝每次调用需拷贝内核事件表

实践建议

  • Linux平台优先选择epoll
  • 高并发场景使用ET模式+非阻塞IO
  • 配合内存池减少GC压力

四、非阻塞IO实现要点

  1. 设置非阻塞模式

    channel.configureBlocking(false);
  2. 处理不完整读写

    while (buffer.hasRemaining()) {
        channel.write(buffer); // 可能只写入部分数据
    }
  3. 结合状态机设计

图2

最佳实践

  • 每个连接维护独立的上下文
  • 使用ByteBuffer池避免频繁分配
  • 合理设置超时时间(SO_TIMEOUT)

通过掌握这些核心用法,开发者可以构建从简单到复杂的不同网络应用。实际项目中建议结合Netty等成熟框架,避免重复造轮子。

添加新评论