1. 前言
前面我们学习了如何利用tcp连接实现客户端与服务端通信,也实践了三次握手与API的各种关系,那么,TCP连接断开的时候是什么样的呢?之前我们总会调用close()来关闭TCP连接,但是调用close函数会单方面切断所有的连接,无法进行读写,此时如果另一端有未发送完成的数据,那么本端强制close的话就会导致数据的丢失,因此我们需要一种更优雅的关闭连接的方式。
2. 半关闭
很多时候,我们都无法直接关闭TCP连接,例如在服务器向客户端传输文件时,若是服务器在文件发送完成之后立即调用close关闭连接,那么一方面客户端无法知道自己何时接收完成(一般处理我们都会死循环一直读取,但是我们不知道何时结束这个循环),另一方面服务器也不知道客户端接收的数据是否完整(服务端只知道自己发送完成,却不知道客户端的接收情况是什么样的,是否需要重新发送部分数据)。因此此时若是服务器发送完成后告诉客户端“我发送完成了”,客户端接收完成后告诉服务器“我接收完成了”,这样服务器与客户端都能够优雅的关闭且无需担忧数据丢失问题。
下面我们介绍TCP的半关闭API:
/*** @brief 关闭套接字上的部分或全部连接* @param[in] fd 要关闭的套接字* @param[in] how 关闭方式* @return 0:成功 -1:失败* @note SHUT_RD: 关闭输入流,套接套接字只能发送数据无法接收,SHUT_WR: 关闭输出流,套接字只能接收数据,无法发送数据SHUT_RDWR: 关闭连接,效果类似于调用close函数*/int shutdown (int __fd, int __how)
还是以发文件为例,当服务器发送完成数据后,此时关闭输出流,此时客户端会受到`EOF`,因此客户端知道自己无数据可接收,结束收文件循环后,向服务器发送标识“bye”告知已经接收完成,关闭连接,服务器收到此消息后也关闭连接。
3. 实例
// serverint sock_server = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sock_server){std::cout << "init sock_server failed" << std::endl;return -1;}FILE *file_server = fopen("../file_server.txt", "rb");if (file_server == NULL){std::cout << "init file fd failed" << std::endl;return -1;}struct sockaddr_in addr_server;memset(&addr_server, 0, sizeof(addr_server));addr_server.sin_family = AF_INET;addr_server.sin_addr.s_addr = inet_addr("127.0.0.1");addr_server.sin_port = htons(10086);if (-1 == bind(sock_server, (struct sockaddr*)&addr_server, sizeof(struct sockaddr))){std::cout << "bind sock_server failed" << std::endl;return -1;}if (-1 == listen(sock_server, 1)){std::cout << "listen failed" << std::endl;return -1;}int sock_client = 0;struct sockaddr_in addr_client;socklen_t len = 0;if (-1 == (sock_client = accept(sock_server, (struct sockaddr *)&addr_client, &len))){std::cout <<"accept failed" << std::endl;return -1;}std::cout << "accept client link, sock_client:" << sock_client << std::endl;int read_len = 0;char buf[MAX_BUFSIZE] = {0};while (1){read_len = fread(buf, 1, MAX_BUFSIZE, file_server);if (read_len < MAX_BUFSIZE){write(sock_client, buf, strlen(buf));break;}write(sock_client, buf, MAX_BUFSIZE);}std::cout << "transfer file done" << std::endl;shutdown(sock_client, SHUT_WR);memset(buf, 0, sizeof(buf));read(sock_client, buf, MAX_BUFSIZE);std::cout << "message from client: " << buf << std::endl;close(sock_server);close(sock_client);fclose(file_server);// clientint sock_client = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sock_client){std::cout << "init sock_client failed" << std::endl;return -1;}FILE *file_client = fopen("file_client.txt", "wb");if (file_client == NULL){std::cout << "init file fd failed" << std::endl;return -1;}struct sockaddr_in addr_server;memset(&addr_server, 0, sizeof(addr_server));addr_server.sin_family = AF_INET;addr_server.sin_addr.s_addr = inet_addr("127.0.0.1");addr_server.sin_port = htons(10086);if (-1 == connect(sock_client, (struct sockaddr *)&addr_server, sizeof(struct sockaddr))){std::cout << "connect to server failed" << std::endl;return -1;}int read_len = 0;char buf[MAX_BUFSIZE] = {0};while (read_len = read(sock_client, buf, MAX_BUFSIZE)){fwrite(buf, 1, read_len, file_client);}std::cout << "file receive done" << std::endl;write(sock_client, "bye", strlen("bye"));fclose(file_client);close(sock_client);
