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

上一节我们完成了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 下午4.08.34.png

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容