流式套接字基于tcp,面向连接、可靠传输、无消息边界;通过三次握手建立连接,数据收发依赖内核读写缓冲区,关闭需四次挥手,close()受引用计数影响而shutdown()可单向终止。

流式套接字(Stream Socket)在 Linux 下基于 TCP 协议,核心特点是面向连接、可靠传输、字节流无边界。它不像数据报套接字那样以“包”为单位收发,而是把通信看作一连串连续的字节流——就像读写普通文件一样,但背后由内核协议栈保障顺序、重传、流量控制和拥塞控制。
三次握手建立连接:客户端与服务端如何同步状态
流式套接字通信前必须完成 TCP 三次握手:
- SYN:客户端调用 connect() 后,向服务端发送 SYN 报文,携带初始序列号(ISN),进入 SYN_SENT 状态;
- SYN-ACK:服务端收到后,若监听套接字处于就绪状态(即已 bind() + listen()),内核为其创建半连接队列项,回复 SYN-ACK(含自身 ISN 和对客户端 ISN+1 的确认);
- ACK:客户端收到后回复 ACK,双方进入 ESTABLISHED 状态;服务端收到该 ACK 后,将连接从半连接队列移到全连接队列,等待 accept() 取出。
注意:listen() 的第二个参数指定全连接队列长度(如 listen(sockfd, 5)),队列满时新 ACK 可能被丢弃,导致连接超时或重传。
数据收发本质:read/write 与缓冲区的协作机制
应用层调用 read() 或 write() 并不直接操作网络,而是与内核中的两个关键缓冲区交互:
- 发送缓冲区(sk->sk_write_queue):调用 write() 时,数据先拷贝至此;TCP 栈按 MSS 分段、加包头、计算校验和,并择机发出;若缓冲区满(如对端接收窗口为 0),write() 会阻塞(默认行为)或返回 EAGAIN(非阻塞模式);
- 接收缓冲区(sk->sk_receive_queue):网卡收到 TCP 段后,内核校验、重组、去重、排序,再放入此处;read() 仅从该缓冲区拷贝数据到用户空间,不涉及实际网络动作。
因此,“发送成功”只代表数据进了内核发送队列,不代表对方已收到;“读不到数据”可能是缓冲区空,也可能是对端尚未发、或网络延迟大——流式套接字不提供消息边界,read() 返回字节数可能小于请求长度(尤其在网络不稳定或非阻塞模式下)。
优雅关闭连接:四次挥手与 close()/shutdown() 的区别
TCP 连接是全双工的,关闭需分别终止两个方向:
- close():对一个套接字调用后,内核释放资源并尝试发送 FIN;若发送缓冲区还有未发数据,会先发完再 FIN;但它是“双刃剑”——若多个文件描述符指向同一 socket(如 fork 后父子进程共享),close() 只减引用计数,直到计数归零才真正关闭连接;
- shutdown(sockfd, SHUT_WR):立即单向关闭写端,发送 FIN,且不受引用计数影响;之后对该套接字 write() 会返回 EPIPE;对端收到 FIN 后可继续发数据,本端仍可用 read() 接收,直到对方也关闭或返回 0(表示 EOF);
- 典型场景:HTTP 长连接中,服务端处理完请求后调用 shutdown(fd, SHUT_WR) 告知客户端“响应已发完”,然后继续 read() 等待客户端下个请求,避免过早断连。
常见误区与调试提示
初学者易忽略的细节:
- 未检查 read() 返回值:返回 0 表示对端已关闭连接(FIN 到达),不是错误;返回 -1 且 errno == EINTR 是被信号中断,应重试;
- 忽略 Nagle 算法影响:小数据频繁 write() 可能被内核缓存合并(启用 TCP_NODELAY 可禁用);
- 地址复用问题:服务端重启时若端口仍处于 TIME_WAIT 状态,bind() 失败;设置 SO_REUSEADDR 可绕过(仅对监听套接字有效);
- 调试工具推荐:ss -tuln 查看监听状态,tcpdump port XXX 抓包分析握手与数据流,strace -e trace=connect,sendto,recvfrom 跟踪系统调用行为。











