一、建立连接和退出连接
TCP 会通过三次握手和四次挥手保证安全地建立连接和退出连接。
三次握手一般由 主机A 发送 SEQ=1000 的 TCP 报文给 主机B,主机B 收到 主机A 的 TCP 报文之后发送 SEQ=2000、ACK=1001 的 TCP 报文表示收到了来自 主机A 的连接请求,然后 主机A 再发送一个 SEQ=1001、ACK=2001 的 TCP 报文表示收到了来自 主机B SEQ=2001 的 TCP 报文。
三次握手是为了防止 SEQ=2000 的包丢失导致 主机B 单方面认为连接成功而传输数据造成的数据丢失。
四次挥手一般由 主机A 发送 FIN 置位、SEQ=5000 的 TCP 报文给 主机B,主机B 收到 TCP 报文之后发送 SEQ=7500、ACK=5001 的 TCP 报文给 主机A 表示收到了断开请求,但是并不马上断开。这个阶段 主机B 可能还会发送数据给 主机A,当 主机B 想要断开时会给 主机A 发送 FIN 置位、SEQ=7501、ACK=5001 的 TCP 报文表示可以断开连接了,然后 主机A 发送 SEQ=5001、ACK=7502 的 TCP 报文表示收到断开请求,主机A此时并不会立即消除套接字,而是进入 Time-wait 状态等待一段时间避免 ACK=7502 的包丢包。
四次挥手是为了应对 主机A 想断开连接,但是 主机B 还需要发送数据的情况。
三次握手和四次挥手都具有很强的稳健性,即便丢包也不会造成太大的影响。如果当方面断开连接而不通过四次挥手,被断开连接的一方在调用读或者写函数时可以发现异常。
二、优雅地退出
两台主机通过套接字建立连接后进入可交换数据的状态,这种状态可以看作一种流。主机A 和 主机B 建立连接,那么 主机A 和 主机B 分别有输入流和输出流两个流。为了应对 主机B 传输完数据想要关闭输出流但是又想要接收数据的情景,可以使用 shutdown
关闭输出流,而不关闭输入流。上边提到的四次握手虽然也可以让 主机B 收到 主机A 的数据但是 主机B 并没有被关闭。
如果 主机A 关闭了输出流,保留输入流,主机B 会收到一个 FIN 报文,并且在调用 read
或 recv
函数时会返回 0 表示对方的输出流已经关闭但是并没有断开连接。
函数原型
1
2
3
4
5
6
7
#include <sys/socket.h>
int shutdown(int sock, int howto);
#include <winsock2.h>
int shutdown(SOCKET sock, int howto);
howto 可能的参数:
- SHUT_RD:断开输入流
- SHUT_WR:断开输出流
- SHUT_RDWR:同时断开 I/O 流
代码