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. 实例
// server
int 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);
// client
int 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);