套接字连接与优雅地退出

Posted by KalosAner on January 15, 2025

一、建立连接和退出连接

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 报文,并且在调用 readrecv 函数时会返回 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 流

代码

file_client.c

file_server.c

file_client_win.c

file_server_win.c