为什么正确的关闭TCP连接

收到的数据不完整,

先看一个例子,如下

#include "Acceptor.h"
#include "InetAddress.h"
#include "TcpStream.h"

#include <thread>
#include <unistd.h>

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.
}

int main(int argc, char* argv[])
{
  if (argc < 3)
  {
    printf("Usage:\n  %s filename port\n", argv[0]);
    return 0;
  }

  int port = atoi(argv[2]);
  Acceptor acceptor((InetAddress(port)));
  printf("Accepting... Ctrl-C to exit\n");
  int count = 0;
  while (true)
  {
    TcpStreamPtr tcpStream = acceptor.accept();
    printf("accepted no. %d client\n", ++count);

    std::thread thr(sender, argv[1], std::move(tcpStream));
    thr.detach();
  }
}

此程序再port端口监听,并发送一个文件,f发送下面文件

[root@localhost tpc]# ll ttcp
-rwxr-xr-x. 1 root root 1236464 May  6 17:40 ttcp

试验1

[root@localhost tpc]# ./sender ttcp 1234
Accepting... Ctrl-C to exit
accepted no. 1 client
Sleeping 10 seconds.
Start sending file ttcp
Finish sending file ttcp
All done.

[root@localhost ~]# nc localhost 1234 | wc -c 
1236464

可以看出能够完整收到数据

试验2,在nc命令行下输入一点数据

[root@localhost tpc]# ./sender ttcp 1234
Accepting... Ctrl-C to exit
accepted no. 1 client
Sleeping 10 seconds.
Start sending file ttcp
Finish sending file ttcp
All done.

[root@localhost ~]# nc localhost 1234 | wc -c 
nishighds
Ncat: Connection reset by peer.
581156

可以看出收到的数据小了,

注意

如果协议栈的接受缓冲区还有数据,你没有去读,直接close,那么就会导致TCP协议栈发送RST分节,强行的断开连接;如果这时协议栈的发送缓冲区还有数据,对方没收到,那么数据就丢失了,close太早,SO_LINGER此选项无助解决此问题。

正确的做法

sender:shudown(write),对方read返回0,这样的话没有数据send,就会close.反过来导致sender的read返回0.

总结起来read返回0才去close。read返回0表示遇到EOF,此时close是安全的。

这里需要注意是read返回0,才会close关闭连接,如果遇到恶心的客户端,不close,则read会阻塞;因此可能需要加一个超时控制,如果在shutdown()之后若干秒后还没有read返回0,需要自己去close连接。

上面的办法是根据shutdown(write),read返回0判断数据有没有读完,更好办法设计协议的时候把长度包含进去。这样接收方能主动判断数据是否收完毕,收完就可以断开连接。
因为Netcat是无格式的协议,所以需要这种比较绕的方法,
正确关闭TCP连接,

发送完后,关闭写端,一直read直到返回0(EOF).表明对方已经关闭连接;这时调用close,这样一来逻辑就正确了;对于接收方比较简单,read返回0就可以了。read返回0才去close, read返回0表明遇到了EOF,这时候clsoe是安全的。一个因果关系。

这里写图片描述

总结 陈硕老师的课程。

然后分享一个陈硕老师的链接
https://www.zhihu.com/question/25016042/answer/29798924

TCP是个可靠协议,为什么常见的还要在应用层加个ACK响应?
答:TCP协议栈的ACK表现对方协议栈已经收到了你的数据,但是并不代表对方应用程序已经处理了你的数据,对方可能已经阻塞了或者死锁了,那么数据还是保存在接受缓冲区里,会给你一个TCP的ACK。应用层看不见这个ACK,所以需要加一个TCP的ACK。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值