TCPIP 网络编程-06-套接字断开

close 是关闭套接字,调用之后无法再通过 writeread 读写数据。但是通过查看 TCP 的四次挥手我们知道

Snipaste_2023-10-13_22-40-02

close 之后,对端可能还会发送一部分数据,这部分数据其实是丢失的。所以就引入了半关闭,只能发送数据但是不能接收 或者 只能接收数据但是不能发送。

这里提到了一个需求:客户端向服务器发起请求,然后服务端将文件传输给客户端之后,客户端接收完文件,然后告知服务端接收完毕了,客户端自动退出。

这里有一个疑问,客户端怎么知道接收完文件了?

  1. read 函数返回-1表示接收结束,但是read 返回-1 表示对端关闭了连接,那么需求中接收完文件还要发送响应就不行了
  2. 写一个标志位,告知这个标志位就表示文件结束。但是这样的话就必须让文件中没有与标志符一样的内容

这个时候就用到了半关闭

1
2
3
4
5
6
7
8
9
10
11
/**
* @brief 半关闭
* @param
* sock 要被关闭的套接字
* howto 传递断开的方式
* SHUT_RD: 断开输入流
* SHUT_WR: 断开输出流
* SHUT_RDWR: 同时断开输入输出流
* @return 成功返回 0;失败返回 -1
*/
int shutdown(int sock, int howto);

知道了 shutdown 函数之后,看看是如何实现的 示例地址:https://github.com/XBoom/network-ip.git 中的 apps/socket/09/

运行结果如下

1
2
3
4
[root]#./server 5555
[09/server.c:57 main info] server recv recv all file

[root]#./client 127.0.0.1 5555

另外通过抓包可以看出跟平时的四次挥手不一样的地方(5555 表示服务端)

image-20240111225428108

  1. 首先通过三次握手(1~3)
  2. 服务端第一次发送数据给客户端,客户端确认(4~5)
  3. 服务端第二次发送数据给客户端,客户端确认(6、8)
  4. 在服务端第二次发送数据的时候,同时发送了FIN 结束(7),也就是调用的 shutdown(client_sock, SHUT_WR);
  5. 客户端接着向服务端发送数据,客户端发送响应(9-10),发现客户端调用的 shutdown(server_sock, SHUT_RD); 并没有作用,因为已经收到了服务端的 FIN 报文
  6. 发送结束之后,客户端通过 close 发起 FIN

对于两个 shutdown 可以看成下图这样

image-20240111225821497

shotdown 分别对应的是两个流,当一端关闭之后就不需要再次关闭了。而一条流的关闭也不会影响另外一条流

那么如果改变一下服务端的关闭流程,先关闭读,再关闭写。改成如下

1
2
3
4
5
6
7
8
9
10
//关闭读
shutdown(client_sock, SHUT_RD);

memset(buff, 0 , sizeof(buff));
while(read(client_sock, buff, sizeof(buff)))
{
LOG_INFO("server recv %s", buff);
}
//关闭写
shutdown(client_sock, SHUT_WR);

程序运行结果如下,好像服务端没有什么

1
2
3
4
[root]#./server 5555

[root]#./client 127.0.0.1 5555
[root]#

看看交互报文

image-20240111231443159

  1. 服务端发送完数据之后,关闭了读 shutdown(client_sock, SHUT_RD);
  2. 客户端接收完数据之后也关闭了读,导致客户端与服务端都阻塞等待

参考文档

  1. 《TCPIP 网络编程》