前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解TCP的重置功能和实现连接结束功能

详解TCP的重置功能和实现连接结束功能

作者头像
望月从良
发布2019-09-18 17:10:43
1.4K0
发布2019-09-18 17:10:43
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

上一节我们完成了TCP三次握手原则,当双方通过三次握手交换了各自用于传递信息的参数后,双方进入数据分发模式,在TCP协议上说双方都进入了ESTABLISHED状态。基于早期质量低下的数据传输网络,连接建立只不过是开始,在通讯过程中保持稳定和通畅是TCP协议的重要内容。

由于TCP协议目的是保持长时间数据传输的稳定,因此它必须有效应对在连接过程中出现的突然中断情况。突然中断最常见的叫”半开“过程,也就是一方已经已经断开连接而另一方并不知情,它还以为对方正常在跟它传输数据。为了面对这种情况,TCP引入了Reset功能,上一节我们编码完成三次握手时,如果抓包观察就会发现,我们代码并没有发出reset数据包,但是抓包却发现我方发出了reset数据包,这是因为一旦某一方发现对方没有按照“套路出牌”时他就会像对方发送reset消息。

在上节我们的编码实现中,我们像对方发送SYN数据包时,对方回应了ACK数据包,由于我们直接绕开底层TCP模块,操作系统底层TCP模块便会觉得迷惑,两种原因会让TCP模块发出reset数据包,一种是当收到SYN数据包时,TCP模块发现并没有对应的进程使用相应端口对数据进行接收,于是他就会发生reset数据包,我们上一节属于这种情况,二是收到ACK包时对方回复的关键参数不对。

对方接收到reset数据包时也不会直接断开连接,而是检验对方发来的reset是否合理,如果接收方发现reset数据包是合理的,它会根据自己当前状态来做出多种不同应对。如果接收方处于监听状态,那么它会保持当前状态不变,如果接收方向对方发出了SYN+ACK包,但还没有收到对方的ACK包却收到reset包,那么它会退回到监听状态,其他情况下接收方会把当前连接中断掉。

为了防止我们程序绕过操作系统TCP底层模块进行三次握手而导致它向对方发送rest数据包的问题,在mac上我们可以指定让TCP模块对指定的IP和端口不发生RST数据包,其方法如下: 1, 首先通过sudo /etc/pf.conf打开编辑文件 2, 在文件中添加一行: block drop proto tcp from 192.168.2.243 to 220.181.43.8 flags R/R 其中192.168.2.243是发出方的ip,可以换成你运行程序的ip,220.181.43.8是对方ip,你可以换成想要进行tcp交互的ip。

  1. 执行命令 sudo pfctl - f /etc/pf.conf
  2. 执行命令 sudo pfctl -e 让设置的命令生效。

执行上述步骤后,运行我们上一节的代码,在wireshark抓包将不会再看到底层TCP模块发送reset数据包给对方。在TCP数据传输管理过程中协议还需要控制连接中的“闲置”过程,也就是双方保持连接但没有数据发送或接收的时候。如果长时间没有数据传输,协议需要确保双方依然处于正常连接状态,于是操作系统上的TCP协议栈实现都会向对方发送一个不含任何数据的空消息,然后对方回复一个ACK数据包,这种用于表明“依然在线”的消息包叫做“keepalive”机制。

该机制并非属于TCP协议规定而是TCP协议具体实现方自行加入的机制。这种机制有很多争论,但支持方认为服务器有必要使用keepalive方式确保连接的有效性,因为服务器要同时接收很多客户端的连接,因此每个连接都意味着对服务器资源的损耗,如果连接失效服务器要及时断开连接,以便把资源留给其他客户端。

当所有数据发送完毕,双方就进入连接中断阶段。问题在于TCP中断连接的过程比想象要复杂,这点我们在前面也提及过。当通讯的一方向对方发出关闭连接请求时,这只意味着它不再向对方发送数据,但它不能立马下线,因为对方可能有数据要发送给自己,因此它必须等待对方传输完所有数据后才能下线。

因此在一方发起连接终结时,会向对方发送一个FIN包,这个数据包甚至有可能还会携带发送给对方的数据。接收到FIN数据包的一方会向对方发送FIN+ACK数据包,然后对方再次发送ACK包,整个通讯流程才算结束。

接下来我们在上一节的基础上添加关闭连接的功能,相应代码如下:

public class TCPThreeHandShakes extends Application{
....
//增加协议状态
    private static int CONNECTION_IDLE = 0;
    private static int CONNECTION_INIT = 1;
    private static int CONNECTION_SUCCESS = 2;
    private static int CONNECTION_FIN_INIT = 3;
    private static int CONNECTION_FIN_SUCCESS = 4;
    private int  tcp_state = CONNECTION_IDLE;
....
 //向服务器发起关闭流程
   public void beginClose() throws Exception {
//       this.seq_num += 1;
       createAndSendPacket(null, "FIN,ACK");
       this.tcp_state = CONNECTION_FIN_INIT;
   }

 @Override
    public void handleData(HashMap<String, Object> headerInfo) {
       short src_port = (short)headerInfo.get("src_port");
       System.out.println("receive TCP packet with port:" + src_port);
       boolean ack =  false, syn = false, fin = false;
       if (headerInfo.get("ACK") != null) {
           System.out.println("it is a ACK packet");
           ack = true;
       }
       if (headerInfo.get("SYN") != null) {
           System.out.println("it is a SYN packet");
           syn = true;
       }
       if (headerInfo.get("FIN") != null) {
           System.out.println("it is a FIN packet");
           fin = true;
       }
       if (ack && syn) {
           int seq_num = (int)headerInfo.get("seq_num");
           int ack_num = (int)headerInfo.get("ack_num");
           System.out.println("tcp handshake from othersize with seq_num" + seq_num + " and ack_num: " + ack_num);
           this.seq_num += 1;
           this.ack_num = seq_num + 1;
           try {
            if (this.tcp_state == CONNECTION_INIT) {
                this.tcp_state = CONNECTION_SUCCESS;
                System.out.println("three hanshake complete");
            }
            createAndSendPacket(null, "ACK");
            //启动关闭流程
            beginClose();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       }
      //收到服务器发回的fin+ack包,正式关闭连接
      if (ack && fin) {
          System.out.println("receive fin packet and close connection");
          if (this.tcp_state == CONNECTION_FIN_INIT) {
                this.tcp_state = CONNECTION_FIN_SUCCESS;
                System.out.println("three hanshake shutdown");
                 try {
                       int seq_num = (int)headerInfo.get("seq_num");
                       int ack_num = (int)headerInfo.get("ack_num");
                       System.out.println("tcp handshake closing from othersize with seq_num" + seq_num + " and ack_num: " + ack_num);
                       this.seq_num += 1;
                       this.ack_num = seq_num + 1;
                        createAndSendPacket(null, "ACK");
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
            }
      }

   }
}

在上面代码中,我们增加 一个函数beginClose()用于向对方发送ACK+FIN数据包告知对方关闭当前连接。这个函数在我们完成三次握手后被调用,当我们向对方发送ACK+FIN数据包后,对方也会向我们发送ACK+FIN数据包,最后我们再次向对方发送一个ACK包,由此完成TCP关闭连接流程,上面代码运行后抓包显示如下:

从抓包结果可见我们成功完成了三次握手以及连接关闭的整个循环。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档