【网络编程实践】2.3.2 TCP连接错误关闭示例

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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值