send()/write() 成功返回只意味着内核接收了数据,并准备在某些时候发送它们。内核接收数据后,还要把数据包发送到网卡,并在网络中各个网卡遍历,最终到达远程主机。远程主机的内核确认到数据,拥有该 socket 的进程从中读取数据,此时数据才真正到达应用程序。
也就是说,当最后一次 send 函数执行完之后,并不意味着对方已经接收到完整的数据了,如果此时我们需要关闭连接,应再确认一下对方是否已经将数据接收完毕。如果贸然的使用 close 关闭连接,可能会使得数据接收不完整。
示例:sender 程序,利用tcp连接发送文件。recipes/tpc/sender.cc
部分代码展示:
void sender(const char* filename, TcpStreamPtr stream)
{
FILE* fp = fopen(filename, "rb");
if (!fp)
return;
printf("Sleeping 10 seconds.\n");
sleep(10);
printf("Start sending file %s\n", filename);
char buf[8192];
size_t nr = 0;
while ( (nr = fread(buf, 1, sizeof buf, fp)) > 0)
{
stream->sendAll(buf, nr);
}
fclose(fp);
printf("Finish sending file %s\n", filename);
// Safe close connection
printf("Shutdown write and read until EOF\n");
stream->shutdownWrite();
while ( (nr = stream->receiveSome(buf, sizeof buf)) > 0)
{
// do nothing
}
printf("All done.\n");
// TcpStream destructs here, close the TCP socket.
}
其中 21 ~ 26 行是安全关闭套接字的方法。如果我们将这几行代码注释,模拟不安全的关闭套接字程序,然后进行测试,就有可能测出数据丢失的情况。
示例一:在服务器上开启 sender 服务,发送一个 1k 大小的文件。
在客户端,使用 nc 连接服务端,接收文件。
以上是正常接收的示例,下面模拟数据未读取完的情况:
示例二:这次服务端发送的数据为 1M bytes。
如果我们在客户端等待时,输入一些数据,可以模拟tcp发送数据不完整的情况。 (客户端nc 有用户输入的数据,发送到服务端后 sender 没有读取,使得 sender 在close() 的时候,没有进行 FIN 分解,而是进行了 RST 分解 直接断开了连接。)
抓包分析,tcp数据发送不完整的原因:
参考资料:
使用tcpdump抓包服务器 54321 端口。在等待的 10 秒内抓到 5 条记录。
- 三次握手。(客户端194.41–syn–> ser; ser --syn/ack–> 194.41; 194.41–syn–>ser)。三次握手完成。
- (客户端发送数据“”1213)。
- 抓包 194.41 =》 ser:seq 1:6 length 5
- 抓包 ser =》194.41:ack 1
客户端
截取其中一段记录,发现了tcp的重传。编号4的seq 987393 没有收到ack应答,然后开始重传。
服务端 sender 将文件发送完之后,close(fd)关闭连接。而由于客户端通过nc发送的数据还没有读,因此会发送RST报文丢弃数据立刻终止连接,而不是发送FIN报文等待所有数据都发送完才正常断开连接(发送RST,还是FIN 取决于close()时,local tcp buffer中是否还有未读完的数据)。
在客户端收到 RST 包时,丢弃所有数据,然后立刻断开连接。(注:以下抓包数据与上图不是同一次通信)
# 服务端抓包
23:36:03.743349 IP 113.200.194.41.24433 > iZbp1cvmupjyjb3awd568qZ.54321: Flags [.], ack 590433, win 1029, length 0
23:36:03.743412 IP iZbp1cvmupjyjb3awd568qZ.54321 > 113.200.194.41.24433: Flags [.], seq 591873:594753, ack 6, win 229, length 2880
23:36:03.775069 IP 113.200.194.41.24433 > iZbp1cvmupjyjb3awd568qZ.54321: Flags [.], ack 593313, win 1029, length 0
23:36:03.775120 IP iZbp1cvmupjyjb3awd568qZ.54321 > 113.200.194.41.24433: Flags [.], seq 594753:597633, ack 6, win 229, length 2880
23:36:03.807031 IP 113.200.194.41.24433 > iZbp1cvmupjyjb3awd568qZ.54321: Flags [.], ack 596193, win 1029, length 0
23:36:03.807144 IP iZbp1cvmupjyjb3awd568qZ.54321 > 113.200.194.41.24433: Flags [.], seq 597633:600513, ack 6, win 229, length 2880
23:36:03.807615 IP iZbp1cvmupjyjb3awd568qZ.54321 > 113.200.194.41.24433: Flags [R.], seq 600513, ack 6, win 229, length 0
23:36:03.839012 IP 113.200.194.41.24433 > iZbp1cvmupjyjb3awd568qZ.54321: Flags [.], ack 599073, win 1029, length 0
23:36:03.839082 IP iZbp1cvmupjyjb3awd568qZ.54321 > 113.200.194.41.24433: Flags [R], seq 3632433974, win 0, length 0
# 客户端抓包
23:36:03.716609 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [P.], seq 590433:591873, ack 6, win 64240, length 1440
23:36:03.716628 IP 192.168.214.128.56440 > 【服务端ip地址】54321: Flags [.], ack 591873, win 65535, length 0
23:36:03.743675 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [P.], seq 591873:594753, ack 6, win 64240, length 2880
23:36:03.743709 IP 192.168.214.128.56440 > 【服务端ip地址】54321: Flags [.], ack 594753, win 65535, length 0
23:36:03.779618 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [P.], seq 594753:596193, ack 6, win 64240, length 1440
23:36:03.779678 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [P.], seq 596193:597633, ack 6, win 64240, length 1440
23:36:03.779709 IP 192.168.214.128.56440 > 【服务端ip地址】54321: Flags [.], ack 597633, win 65535, length 0
23:36:03.807526 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [P.], seq 597633:599073, ack 6, win 64240, length 1440
23:36:03.807573 IP 【服务端ip地址】54321 > 192.168.214.128.56440: Flags [R.], seq 599073, ack 6, win 64240, length 0
引用:关于close 和shutdown的过程
1 server端,close调用之后如果在local TCP buffer内如果还有数据没有读取会给对方,则主动直接发送RST给client, 否则发送FIN,这取决于调用close时刻local TCP buffer的状态, 跟对端是不是继续发送数据无关。
.
2 shutdown关闭读不会给对方发FIN, 只有关闭写才会发FIN, 而且跟local TCP buffer状态没关系,只发送FIN包,从不发送RST
.
总结:shutdown从不发送RST给对端(只发送FIN),只有close在接收缓存区没有被读完的条件下,才发送给对端RST。