1. 前言

前面我们学习了如何利用tcp连接实现客户端与服务端通信,也实践了三次握手与API的各种关系,那么,TCP连接断开的时候是什么样的呢?之前我们总会调用close()来关闭TCP连接,但是调用close函数会单方面切断所有的连接,无法进行读写,此时如果另一端有未发送完成的数据,那么本端强制close的话就会导致数据的丢失,因此我们需要一种更优雅的关闭连接的方式。

2. 半关闭

很多时候,我们都无法直接关闭TCP连接,例如在服务器向客户端传输文件时,若是服务器在文件发送完成之后立即调用close关闭连接,那么一方面客户端无法知道自己何时接收完成(一般处理我们都会死循环一直读取,但是我们不知道何时结束这个循环),另一方面服务器也不知道客户端接收的数据是否完整(服务端只知道自己发送完成,却不知道客户端的接收情况是什么样的,是否需要重新发送部分数据)。因此此时若是服务器发送完成后告诉客户端“我发送完成了”,客户端接收完成后告诉服务器“我接收完成了”,这样服务器与客户端都能够优雅的关闭且无需担忧数据丢失问题。
下面我们介绍TCP的半关闭API:

  1. /**
  2. * @brief 关闭套接字上的部分或全部连接
  3. * @param[in] fd 要关闭的套接字
  4. * @param[in] how 关闭方式
  5. * @return 0:成功 -1:失败
  6. * @note SHUT_RD: 关闭输入流,套接套接字只能发送数据无法接收,
  7. SHUT_WR: 关闭输出流,套接字只能接收数据,无法发送数据
  8. SHUT_RDWR: 关闭连接,效果类似于调用close函数
  9. */
  10. int shutdown (int __fd, int __how)
  1. 还是以发文件为例,当服务器发送完成数据后,此时关闭输出流,此时客户端会受到`EOF`,因此客户端知道自己无数据可接收,结束收文件循环后,向服务器发送标识“bye”告知已经接收完成,关闭连接,服务器收到此消息后也关闭连接。

3. 实例

  1. // server
  2. int sock_server = socket(AF_INET, SOCK_STREAM, 0);
  3. if (-1 == sock_server)
  4. {
  5. std::cout << "init sock_server failed" << std::endl;
  6. return -1;
  7. }
  8. FILE *file_server = fopen("../file_server.txt", "rb");
  9. if (file_server == NULL)
  10. {
  11. std::cout << "init file fd failed" << std::endl;
  12. return -1;
  13. }
  14. struct sockaddr_in addr_server;
  15. memset(&addr_server, 0, sizeof(addr_server));
  16. addr_server.sin_family = AF_INET;
  17. addr_server.sin_addr.s_addr = inet_addr("127.0.0.1");
  18. addr_server.sin_port = htons(10086);
  19. if (-1 == bind(sock_server, (struct sockaddr*)&addr_server, sizeof(struct sockaddr)))
  20. {
  21. std::cout << "bind sock_server failed" << std::endl;
  22. return -1;
  23. }
  24. if (-1 == listen(sock_server, 1))
  25. {
  26. std::cout << "listen failed" << std::endl;
  27. return -1;
  28. }
  29. int sock_client = 0;
  30. struct sockaddr_in addr_client;
  31. socklen_t len = 0;
  32. if (-1 == (sock_client = accept(sock_server, (struct sockaddr *)&addr_client, &len)))
  33. {
  34. std::cout <<"accept failed" << std::endl;
  35. return -1;
  36. }
  37. std::cout << "accept client link, sock_client:" << sock_client << std::endl;
  38. int read_len = 0;
  39. char buf[MAX_BUFSIZE] = {0};
  40. while (1)
  41. {
  42. read_len = fread(buf, 1, MAX_BUFSIZE, file_server);
  43. if (read_len < MAX_BUFSIZE)
  44. {
  45. write(sock_client, buf, strlen(buf));
  46. break;
  47. }
  48. write(sock_client, buf, MAX_BUFSIZE);
  49. }
  50. std::cout << "transfer file done" << std::endl;
  51. shutdown(sock_client, SHUT_WR);
  52. memset(buf, 0, sizeof(buf));
  53. read(sock_client, buf, MAX_BUFSIZE);
  54. std::cout << "message from client: " << buf << std::endl;
  55. close(sock_server);
  56. close(sock_client);
  57. fclose(file_server);
  58. // client
  59. int sock_client = socket(AF_INET, SOCK_STREAM, 0);
  60. if (-1 == sock_client)
  61. {
  62. std::cout << "init sock_client failed" << std::endl;
  63. return -1;
  64. }
  65. FILE *file_client = fopen("file_client.txt", "wb");
  66. if (file_client == NULL)
  67. {
  68. std::cout << "init file fd failed" << std::endl;
  69. return -1;
  70. }
  71. struct sockaddr_in addr_server;
  72. memset(&addr_server, 0, sizeof(addr_server));
  73. addr_server.sin_family = AF_INET;
  74. addr_server.sin_addr.s_addr = inet_addr("127.0.0.1");
  75. addr_server.sin_port = htons(10086);
  76. if (-1 == connect(sock_client, (struct sockaddr *)&addr_server, sizeof(struct sockaddr)))
  77. {
  78. std::cout << "connect to server failed" << std::endl;
  79. return -1;
  80. }
  81. int read_len = 0;
  82. char buf[MAX_BUFSIZE] = {0};
  83. while (read_len = read(sock_client, buf, MAX_BUFSIZE))
  84. {
  85. fwrite(buf, 1, read_len, file_client);
  86. }
  87. std::cout << "file receive done" << std::endl;
  88. write(sock_client, "bye", strlen("bye"));
  89. fclose(file_client);
  90. close(sock_client);