7.1 基于TCP的半关闭
7.1 基于TCP的半关闭
为何需要半关闭
考虑以下情况:
客户端连接服务器后服务器向客户端发送文件,服务器发送完文件后断开与客户端的连接。但是仍需要接收客户端返回的状态信息(用Thanks表示)。
通过EOF 表示文件传输完成。而EOF只在断开输出流时才会向对方主机传输。
单方面断开连接的问题
使用close() 或 closesocket()断开连接后输入缓冲中的数据会被清除,并且无法调用输入相关的函数。也无法调用输出相关函数,但是输出缓冲中的数据仍会发送。
在上面的示例中,使用close()关闭后无法接收客户端的返回信息。
套接字和流
[!quote]
两台主机通过套接字建立 连接 后进入可交换数据的状态,又称为“流形成的状态”。
将数据看作是水。建立连接 的套接字的输入(输出)缓冲 和 对端的输出(输入)缓冲之间构成一条单向流通的管道。数据从己方的输出流向对端的输入。缓冲相当于是蓄水池,用于存储数据。
既然是基于TCP,TCP又代表了连接。那么连接的套接字形成的“流”还具有以下特点:
- 流出的水流一定能到达对端:发送的数据不会丢失
- 前面流出的水流一定比后面流出的水流先到:数据按发送顺序接收,不会乱序
针对优雅断开的shutdown函数
#include <sys/socket.h>
int shutdown(int sockfd, int how);| 参数 | 含义 | 说明 |
|---|---|---|
| sockfd | 需要断开的套接字文件描述符 | |
| how | 指定断开的流 | SHUT_RD:断开输入流 SHTU_WR:断开输出流 SHUT_RDWR:断开输入、输出流 |
断开相当于封闭管道的入口、出口,对于输入蓄水池还会排除蓄水池里的水(清空缓冲)
关于EOF 与 TCP断开连接过程
[!quote]
断开输出流时向对方主机传输EOF
TCP 断开连接的过程中并没有传输任何数据,那么EOF是如何发送的?
chatgpt:
EOF 是通过TCP 报文段中的FIN flag 实现的,在收到FIN 后内核会在接收该消息的socket 的输入缓冲写入EOF,使用recv()/read()读取输入缓冲,读取到EOF后返回0.
socket 输入缓冲中的EOF 表示缓冲不再接收数据并且缓冲中的所有数据已经被读取。
收到FIN(读取到EOF)后关闭输入流;发送FIN 关闭输出流
关于为什么Client 关闭了输入缓冲后仍可接收ACK,关闭了输出缓冲后仍可发送ACK:
ACK、FIN 属于TCP控制报文,由内核协议控制发送。输入流、输出流影响的是应用层数据。
基于半关闭的文件传输程序
服务端
- 准备要发送的文件
- 读取文件内容
- accept 连接
- 发送文件数据
- 关闭输出流
- 接收到回复后关闭socket
实现:
FILE *fp = NULL;
fp = fopen("1.txt", "rb");
if(!fp)
{
perror("fopen() error\n");
exit(-1);
}
//bind(), listen(), accept()
while(1)
{
read_cnt = fread((void*)message, 1, 2, fp);
if(read_cnt < 2)
{
write(client_fd, message, read_cnt);
break;
}
write(client_fd, message, 2);
}
shutdown(client_fd, SHUT_WR);
printf("shutdown\n");
read_cnt = read(client_fd, message, BUF_SIZE - 1);
message[read_cnt] = 0;
printf("Message from client: %s\n", message);
fclose(fp);
close(client_fd);
close(sock);| 行号 | 功能 | 说明 |
|---|---|---|
| 1-7 | 打开文件 | 如果打开出错,那么fopen 返回的指针为NULL。通过perror()获取出错的具体原因 |
| 9-18 | 读取文件数据到message,然后发送 | 用2当作读取的最大字节数。如果剩余数据量小于2,则取实际内容大小,否则就是大于等于最大字节数,直接读取最大字节数 |
| 20 | 关闭输出流 | |
| 22 | 读取输入流 |
效果:
ming@ubuntu:/media/sf_share/Network/build/Server$ ./Network 192.168.56.101 10086
shutdown
Message from client: Thank you关闭输出流后仍能收到来自client 的消息
客户端
实现:
int read_cnt = 0;
FILE *fp = fopen("receive.txt", "wb");
if(!fp)
{
printf("fopen() error");
exit(-1);
}
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
unix_error("connect() error");
printf("connected\n");
while( (read_cnt = read(sock, message, 1)) != 0 )
{
//message[read_cnt] = 0;
//printf("data:%s\n", message);
fwrite((void*)message, 1, read_cnt, fp);
}
puts("Received file data");
write(sock, "Thank you", 10);
fclose(fp);
close(sock);效果:
ming@ubuntu:/media/sf_share/Network/build$ ./Network 192.168.56.101 10086
connected
Received file data
ming@ubuntu:/media/sf_share/Network/build$ cat receive.txt
hello world!成功接收来自服务端的文件