精华内容
下载资源
问答
  • TCP三次握手和四次挥手的全过程
    2022-02-09 20:18:43

    三次握手和四次挥手是各个公司常见的考点,也具有一定的水平区分度,希望大家能带着如下问题进行阅读,收获会更大:

    1. 请画出三次握手和四次挥手的示意图
    2. 为什么连接的时候是三次握手?
    3. 什么是半连接队列?
    4. ISN(Initial Sequence Number)是固定的吗?
    5. 三次握手过程中可以携带数据吗?
    6. 如果第三次握手丢失了,客户端服务端会如何处理?
    7. SYN攻击是什么?
    8. 挥手为什么需要四次?
    9. 四次挥手释放连接时,等待2MSL的意义?

    TCP三次握手和四次挥手的全过程

         TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:

    位码即tcp标志位,有6种表示:

    SYN(synchronous建立连接)

    ACK(acknowledgement 表示响应、确认)

    PSH(push表示有DATA数据传输)

    FIN(finish关闭连接)

    RST(reset表示连接重置)

    URG(urgent紧急指针字段值有效)

    三次握手:

    概念: 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息.

    刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态,进行三次握手:

            第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号x,此时客户端处于 SYN_SEND 状态。首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。

    第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号y。同时会把客户端的 x + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

    在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。

    第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 y+ 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。

    确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。

    发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。

    在socket编程中,客户端执行connect()时,将触发三次握手

          握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

          确认号:其数值等于发送方的发送序号+1(即接收方期望接收的下一个序列号)。

     

    四次挥手:

            建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

            TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。

            刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:

    第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。

    即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。

    第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。

    即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。

    第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

    即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。

    第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

    即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。

    收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。

    在socket编程中,任何一方执行close()操作即可产生挥手操作。

     

    TCP的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?

    弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

    第一次握手:客户端发送网络包,服务端收到了。

    这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

    第二次握手:服务端发包,客户端收到了。

    这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

    第三次握手:客户端发包,服务端收到了。

    这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

    因此,需要三次握手才能确认双方的接收与发送能力是否正常。

    试想如果是用两次握手,则会出现下面这种情况:

    如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。

    什么是半连接队列?

    服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。

    当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

    这里在补充一点关于SYN-ACK 重传次数的问题:

    服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。

    注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…

    ISN(Initial Sequence Number)是固定的吗?

    当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。

    三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

    三次握手过程中可以携带数据吗?

    其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据

    为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

    也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

    SYN攻击是什么?

    服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

    netstat -n -p TCP | grep SYN_RECV

    常见的防御 SYN 攻击的方法有如下几种:

    • 缩短超时(SYN Timeout)时间
    • 增加最大半连接数
    • 过滤网关防护
    • SYN cookies技术

    挥手为什么需要四次?

    因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

    2MSL等待状态

    TIME_WAIT状态也成为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

    对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

    这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

    四次挥手释放连接时,等待2MSL的意义?

    MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

    为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

    两个理由:

    • 保证客户端发送的最后一个ACK报文段能够到达服务端。

    这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。

    • 防止“已失效的连接请求报文段”出现在本连接中。

    客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

    为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSE状态?

    理论上,四个报文都发送完毕,就可以直接进入CLOSE状态了,但是可能网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

     参考: https://zhuanlan.zhihu.com/p/86426969

    更多相关内容
  • 因为TCP提供的是可靠传输服务,因此它在传输之前必须要进行传输的可靠性测试和一些信息的同步,反观UDP就不用这些握手操作。三次握手正好使双方都能测试传输的可靠性,同时也能进行信息同步,三...

    目录

    TCP报文格式

    关于三次握手与四次挥手面试官想考我们什么?

    三次握手

    四次挥手

    TCP连接为什么是三次握手?断开为什么是四次挥手?

    TCP三次握手过程

    4次断开

    解释原因:

    TCP的状态变迁图:

    TCP/IP详解10-传输层:TCP:传输控制协议_Lincoln_cz的博客-CSDN博客

    实战

    TCP的状态 (SYN, FIN, ACK, PSH, RST, URG)https://www.cnblogs.com/azraelly/archive/2012/12/25/2832393.html

    简单记(编辑中)

    三次握手:

    我和同事在同一层楼,但是不同部门,不怎么认识,今天需要找他沟通,不知道他坐哪,但是可以搜工号,用邮件(SYN)约他:

    我---------------------同事

    ---------------------->邮件(SYN):有空?沟通一下。 SYN(J)

    <--------------------邮件(SYN):我坐在B区16号。    SYN(K)  +  ACK(J+1)

     知道他在哪个位置了,直接喊话:

    --------------------->我在A区8号呀,过来聊一下。       ACK(K+1)

    考点:

    一、三次握手目的:

    1、确认双方收发能力正常。(确认同事有空有能力沟通)

    2、首要目的是同步序列号(SYN),为后面的可靠传送做准备。

    3、如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成到。

    二、为啥只有三次握手才能确认双方的接受与发送能力是否正常,而两次却不可以

    只有三次握手,双方才能确认对端收发功能都OK。

    第一次:客户端发,服务端收到了。

    ------>客户端知:客发OK。服务端知:服收OK+客发OK


    第二次:服务端发,客户端收到了

    ----->客户端知:服发OK(不然也不会收到服务信息) + 客收OK。服务端知:服发OK


    第三次:客户端发,服务端收到了。

    ----->客户端知:服收OK(不然也会给客回此次)。服务端知:客收OK(不然也不会回此次)

    三次加起来:

    客户端知:客发OK + 服发OK;服收OK + 客收OK;

    服务端知:服收OK+服发OK;客发OK + 客收OK;

    四次挥手

    白话

    转自:“三次握手,四次挥手”这么讲,保证你忘不了:https://zhuanlan.zhihu.com/p/374998210

    目的:

    1、TCP是收发两个方向的通道,确保方向的通道都完成关闭。

    2、确保两个方向的通道里的数据都能发完全了再关闭。

    为什么断开连接需要四次挥手
    我们都知道,TCP连接是全双工通信的,而断开时双方都需要确定两个问题:自己是否还有数据要发送,对端是否还有数据要发送,而四次挥手正好在双方同步了这两个问题。

    关于三次握手与四次挥手面试官想考我们什么?

    在面试中,三次握手和四次挥手可以说是问的最频繁的一个知识点,下面是比较重要的点,是比较被面试官给问到:

    三次握手

    当面试官问你为什么需要有三次握手、三次握手的作用、讲讲三次三次握手的时候,我想很多人会这样回答:

    首先很多人会先讲下握手的过程:

    1、第一次握手:客户端给服务器发送一个 SYN 报文。

    2、第二次握手:服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。

    3、第三次握手:客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。

    4、服务器收到 ACK 报文之后,三次握手建立完成。

    作用是为了 确认双方的接收与发送能力是否正常 + 同步序列号。

    这里我顺便解释一下为啥只有三次握手才能确认双方的接受与发送能力是否正常,而两次却不可以
    第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
    第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
    第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

    因此,需要三次握手才能确认双方的接收与发送能力是否正常

    这样回答其实也是可以的,但我觉得,这个过程的我们应该要描述的更详细一点,因为三次握手的过程中,双方是由很多状态的改变的,而这些状态,也是面试官可能会问的点。所以我觉得在回答三次握手的时候,我们应该要描述的详细一点,而且描述的详细一点意味着可以扯久一点。加分的描述我觉得应该是这样:

    刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后

    1、第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_Send 状态。

    2、第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

    3、第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。

    4、服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方以建立起了链接。

    黑客基础知识——SYN泛洪攻击原理及防御

    三次握手的作用

    三次握手的作用也是有好多的,多记住几个,保证不亏。例如:

    1、确认双方收发能力正常。

    2、指定自己的初始化序列号,为后面的可靠传送做准备。

    3、如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成到。

    三次握手建立连接的首要目的是(SYN)同步序列号。只有同步了序列号才有可靠的传输,TCP 协议的许多特性都是依赖序列号实现的,比如流量控制重传等等,这也是三次握手中的报文被称为 SYN 的原因,因为 SYN 的全称就叫做 Synchronize SequenceNumbers。
    链接:https://www.jianshu.com/p/74c19f436c2d

    单单这样还不足以应付三次握手,面试官可能还会问一些其他的问题,例如:

    1、(ISN)是固定的吗

    三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。

    如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

    2、什么是半连接队列

    服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

    这里在补充一点关于 SYN-ACK 重传次数的问题: 服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超 过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s, 2s, 4s, 8s, ....

    3、三次握手过程中可以携带数据吗

    其实第三次握手的时候,是可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

    为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了。

    而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。

    关于三次握手的,https 的认证过程能知道一下最好,不过我就不说了,留着写 http 面试相关时的文章再说。

    四次挥手

    四次挥手也一样,千万不要对方一个 FIN 报文,我方一个 ACK 报文,再我方一个 FIN 报文,我方一个 ACK 报文。然后结束,最好是说的详细一点,要把每个阶段的状态记好,我上次面试就被问了几个了:

    刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:

    1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时服务端处于CLOSED_WAIT1状态。

    2、第二次握手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT2状态。

    3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

    4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态

    5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

    (主动关闭(active close)的一方,被动关闭(passive close)的一方,网络上类似的图有很多,但是有的细节不够,有的存在误导。有的会把两条线分别标记成Client和Server。给读者造成困惑。)

    这里特别需要主要的就是TIME_WAIT这个状态了,这个是面试的高频考点,就是要理解,为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。

    至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功就是 ACK 报文,此时处于 CLOSED 状态。

    最后,在放在三次握手与四次挥手的图

    关于三次握手与四次挥手面试官想考我们什么?--- 不看后悔系列 - 知乎

    这里我给出每个状态所包含的含义,有兴趣的可以看看。

    LISTEN - 侦听来自远方TCP端口的连接请求;
    SYN-SENT -在发送连接请求后等待匹配的连接请求;
    SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
    ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;
    FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
    FIN-WAIT-2 - 从远程TCP等待连接中断请求;
    CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
    CLOSING -等待远程TCP对连接中断的确认;
    LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
    TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;
    CLOSED - 没有任何连接状态;

    TCP连接为什么是三次握手?断开为什么是四次挥手?

    1.三次握手
    TCP连接换成四次握手行不行?为什么?换成两次握手行不行?为什么? 这是我面试时遇到的原题

    首先来说一下三次握手,为什么需要三次握手呢?因为TCP提供的是可靠传输服务,因此它在传输之前必须要进行传输的可靠性测试和一些信息的同步,反观UDP就不用这些握手操作。三次握手正好使双方都能测试传输的可靠性,同时也能进行信息同步,三次握手过程如下:

    在这里插入图片描述

    那么三次握手到底是在握什么呢?抓包测试一下(抓包测试请看另一篇):

    在这里插入图片描述
    三次握手的作用
    第一次握手:client同步自己的发送序号seq和接收窗口win(还有其他可选信息),同时测试自己的发送能力是否正常
    第二次握手:server发送确认号ack,证明了client发送正常。同时同步自己的发送信息seq和win,还要测试自己的发送能力是否正常
    第三次握手:client发送确认号ack,证明了server发送正常
    至此,发送信息同步完成,可靠性测试完成

    两次握手是否可以
    可以发现,前两次握手都需要发送同步信息,因此前两次肯定是必不可少的,唯一有可能的就是第三次,但是从上面的分析来看,如果少了第三次握手,那么client知道自己的发送能力正常,发送信息也同步完成,但是server不能确定自己的发送能力是否正常,也不知道自己的发送信息是否同步完成,因此第三次握手也是必不可少的。

    PS:SYN泛洪攻击原理就是恶意终端故意不发送第三个握手包,操作系统在发送完第二次握手包后,会分配一些资源给这个半连接,同时将这个半连接放入半连接队列,半连接过多时会导致server半连接队列满,正常用户的请求得不到相应,同时过多的半连接占用过多的资源还会影响server性能。

    四次握手是否可以
    有的人可能会想,第三次握手万一丢失了呢?是不是需要第四次握手来对第三次进行确认?第三次握手确实可能会丢失,假如采用第四次来对第三次进行确认,那么第四次需要第五次来确人… …可以发现无论是四次五次或是更多次效果都是一样的,这样的协议既不可能实现也没有任何的效率可言。于是TCP采用了超时重传的策略来保证传输的可靠性,server在发送完第二次握手包之后,就会开启一个定时器,超时后如果没有收到第三次握手包,它就会重新发送SYN+ACK,重复以上步骤,如果失败三次后则说明连接失败,数据传输时采用同样的策略。

    四次挥手
    TCP四次挥手流程:

    在这里插入图片描述

    抓包测试:

    在这里插入图片描述

    为什么断开连接需要四次挥手
    我们都知道,TCP连接是全双工通信的,而断开时双方都需要确定两个问题:自己是否还有数据要发送,对端是否还有数据要发送,而四次挥手正好在双方同步了这两个问题。

    第一次挥手:client告诉server自己的数据已全部发送,client可以回收发送缓冲区,server可以回收接收缓冲区
    第二次挥手:server告诉client自己收到了关闭信息
    第三次挥手:server告诉client自己的数据已全部发送,server可以回收发送缓冲区,client可以回收接收缓冲区
    第四次挥手:client告诉server自己收到了关闭信息
    可以发现,四次挥手同样一次都不能少,如果少了其中任何一次,总有一方不能可靠的通知对方自己的数据已发送完毕,因此,连接不可能可靠的断开,TCP也就成了不可靠的协议。

    原文链接:https://blog.csdn.net/qq_40843865/article/details/103989969

    #################################################################################################

    TCP三次握手过程


    1 主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B ,向主机B 请求建立连接,通过这个数据段,
    主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我.
    2 主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:
    我已经收到你的请求了,你可以传输数据了;你要用哪个序列号作为起始数据段来回应我
    3 主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了

    这样3次握手就完成了,主机A和主机B 就可以传输数据了.
    3次握手的特点
    没有应用层的数据
    SYN这个标志位只有在TCP建产连接时才会被置1
    握手完成后SYN标志位被置0


    4次断开


    1 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求
    2 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1
    3 由B 端再提出反方向的关闭请求,将FIN置1
    4 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.
    由TCP的三次握手和四次断开可以看出,TCP使用面向连接的通信方式,大大提高了数据通信的可靠性,使发送数据端
    和接收端在数据正式传输前就有了交互,为数据正式传输打下了可靠的基础


    名词解释
    ACK TCP报头的控制位之一,对数据进行确认.确认由目的端发出,用它来告诉发送端这个序列号之前的数据段
    都收到了.比如,确认号为X,则表示前X-1个数据段都收到了,只有当ACK=1时,确认号才有效,当ACK=0时,确认号无效,这时会要求重传数据,保证数据的完整性.
    SYN 同步序列号,TCP建立连接时将这个位置1
    FIN 发送端完成发送任务位,当TCP完成数据传输需要断开时,提出断开连接的一方将这位置1

    解释原因:

    TCP建立连接要进行3次握手,而断开连接要进行4次,这是由于TCP的半关闭造成的,因为TCP连接是全双工的(
    即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭,这个单方向的关闭就叫半关闭.
    关闭的方法是一方完成它的数据传输后,就发送一个FIN来向另一方通告将要终止这个方向的连接.当一端收到一个FIN,它必须
    通知应用层TCP连接已终止了这个方向的数据传送,发送FIN通常是应用层进行关闭的结果.

    为什么不能两次握手能进行连接?

    我们知道,3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

    TCP 的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?

                建立连接的过程是利用客户服务器模式,假设主机 A 为客户端,主机 B 为服务器端。

               1 ) TCP 的三次握手过程:主机 A 向 B 发送连接请求;主机 B 对收到的主机 A 的报文段进行确认;主机 A 再次对主机 B 的确认进行确认。

               2 )采用三次握手是:为了防止失效的连接请求报文段突然又传送到主机 B ,因而产生错误。

                     失效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机 B ,主机 B 以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机 B 的资源浪费

    TCP的状态变迁图:

    TCP/IP详解10-传输层:TCP:传输控制协议_Lincoln_cz的博客-CSDN博客

    请问如何提升TCP三次握手的性能?

    面试官:请问如何提升TCP三次握手的性能? - 简书

    实战

    error 111: (111) Connection refused 原因分析

     【网络】连接服务器失败(错误原因:Connection refused)| error 111: (111) Connection refused 原因分析_bandaoyu的note-CSDN博客

    抓包结果:连接服务器失败(错误原因:Connectionrefused)error111抓包结果-其它文档类资源-CSDN下载

    40449	23.888712	192.169.136.113	192.169.136.111	TCP	60	6811 → 33580 [ACK] Seq=1 Ack=1 Win=501 Len=0
    40450	23.888763	192.169.136.112	192.169.136.111	TCP	167	6803 → 54332 [PSH, ACK] Seq=1 Ack=1 Win=501 Len=113
    40451	23.888781	192.169.136.112	192.169.136.111	TCP	167	6807 → 53000 [PSH, ACK] Seq=1 Ack=1 Win=501 Len=113
    40452	23.888786	192.169.136.112	192.169.136.111	TCP	71	6807 → 53000 [PSH, ACK] Seq=114 Ack=1 Win=501 Len=17
    40453	23.888794	192.169.136.112	192.169.136.111	TCP	167	6811 → 54634 [PSH, ACK] Seq=1 Ack=1 Win=501 Len=113
    40454	23.888798	192.169.136.112	192.169.136.111	TCP	71	6803 → 54332 [PSH, ACK] Seq=114 Ack=1 Win=501 Len=17
    40455	23.888807	192.169.136.113	192.169.136.111	TCP	167	6807 → 34066 [PSH, ACK] Seq=1 Ack=1 Win=501 Len=113
    40456	23.888808	192.169.136.112	192.169.136.111	TCP	71	6811 → 54634 [PSH, ACK] Seq=114 Ack=1 Win=501 Len=17
    40457	23.888829	192.169.136.113	192.169.136.111	TCP	71	6807 → 34066 [PSH, ACK] Seq=114 Ack=1 Win=501 Len=17
    40458	23.888873	192.169.136.113	192.169.136.111	TCP	167	6811 → 33580 [PSH, ACK] Seq=1 Ack=1 Win=501 Len=113
    40459	23.888878	192.169.136.113	192.169.136.111	TCP	71	6811 → 33580 [PSH, ACK] Seq=114 Ack=1 Win=501 Len=17
    40460	23.889119	192.169.136.112	192.169.136.111	TCP	60	6803 → 54332 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40461	23.889140	192.169.136.112	192.169.136.111	TCP	60	6811 → 54634 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40474	23.895604	182.200.29.46	182.200.29.24	TCP	60	8500 → 37873 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40475	23.895700	182.200.29.46	182.200.29.24	TCP	60	8500 → 50231 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40476	23.895727	182.200.29.47	182.200.29.24	TCP	60	8500 → 39099 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40479	23.895766	182.200.29.47	182.200.29.24	TCP	60	8500 → 50907 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40480	23.895812	182.200.29.48	182.200.29.24	TCP	60	8500 → 39059 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40481	23.895837	182.200.29.48	182.200.29.24	TCP	60	8500 → 34037 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
    40487	23.905299	192.169.29.124	192.169.29.125	TCP	63	49240 → 6818 [PSH, ACK] Seq=1 Ack=1 Win=176 Len=9
    40488	23.905393	192.169.29.124	192.169.29.125	TCP	60	49240 → 6818 [ACK] Seq=10 Ack=10 Win=176 Len=0
    40490	23.911898	182.200.100.84	182.200.100.95	TCP	74	2181 → 55892 [PSH, ACK] Seq=1 Ack=1 Win=21 Len=20
    40491	23.912584	182.200.100.84	182.200.100.95	TCP	74	2181 → 43776 [PSH, ACK] Seq=1 Ack=1 Win=21 Len=20
    40496	23.917271	192.169.29.126	192.169.29.125	TCP	63	46832 → 6822 [PSH, ACK] Seq=1 Ack=1 Win=443 Len=9
    40497	23.917286	192.169.29.126	192.169.29.125	TCP	63	34540 → 6820 [PSH, ACK] Seq=1 Ack=1 Win=201 Len=9
    40498	23.917361	192.169.29.126	192.169.29.125	TCP	60	46832 → 6822 [ACK] Seq=10 Ack=10 Win=443 Len=0
    40499	23.917368	192.169.29.126	192.169.29.125	TCP	60	34540 → 6820 [ACK] Seq=10 Ack=10 Win=201 Len=0
    40505	23.925733	182.200.100.84	182.200.100.95	TCP	66	37308 → 2181 [PSH, ACK] Seq=1 Ack=1 Win=22 Len=12
    40506	23.925876	182.200.100.84	182.200.100.95	TCP	60	37308 → 2181 [ACK] Seq=13 Ack=21 Win=22 Len=0
    40507	23.929033	192.169.136.113	192.169.136.111	TCP	60	6803 → 54244 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40508	23.930029	192.169.136.113	192.169.136.111	TCP	60	6811 → 33580 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40509	23.930038	192.169.136.113	192.169.136.111	TCP	60	6807 → 34066 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40510	23.930106	192.169.136.112	192.169.136.111	TCP	60	6807 → 53000 [ACK] Seq=131 Ack=18 Win=501 Len=0
    40512	23.933592	172.17.29.124	172.17.29.126	TCP	63	6804 → 59380 [PSH, ACK] Seq=1 Ack=1 Win=1711 Len=9
    40513	23.933598	172.17.29.125	172.17.29.126	TCP	63	6807 → 50964 [PSH, ACK] Seq=1 Ack=1 Win=11180 Len=9

    TCP的状态 (SYN, FIN, ACK, PSH, RST, URG)https://www.cnblogs.com/azraelly/archive/2012/12/25/2832393.html

    在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.

    其中,对于我们日常的分析有用的就是前面的五个字段。

     它们的含义是:

    SYN表示建立连接,

    FIN表示关闭连接,

    ACK表示响应,

    PSH表示有 DATA数据传输,

    RST表示连接重置。

     其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同时为1,它表示的就是建立连接之后的响应,

     如果只是单个的一个SYN,它表示的只是建立连接。

    TCP的几次握手就是通过这样的ACK表现出来的。

     但SYN与FIN是不会同时为1的,因为前者表示的是建立连接,而后者表示的是断开连接。

    RST一般是在FIN之后才会出现为1的情况,表示的是连接重置。

     一般地,当出现FIN包或RST包时,我们便认为客户端与服务器端断开了连接;而当出现SYN和SYN+ACK包时,我们认为客户端与服务器建立了一个连接。

    PSH为1的情况,一般只出现在 DATA内容不为0的包中,也就是说PSH为1表示的是有真正的TCP数据包内容被传递。

    TCP的连接建立和连接关闭,都是通过请求-响应的模式完成的。

     概念补充-TCP三次握手:

    TCP(Transmission Control Protocol)传输控制协议

    TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:

     位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)

    第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

     第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;

     第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

     完成三次握手,主机A与主机B开始传送数据。

     在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

     第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据. 摘自中国云安网(www.yunsec.net) 原文:幸运快3开奖号-幸运快3最准的计划 幸运快3全天计划单期海外分公司

     TCP报文格式

    推荐看这个视频:【深度讲解+面试回答】tcp/ip协议三次握手、四次挥手,通俗易懂,亲自解答_哔哩哔哩_bilibili

    展开全文
  • 2、为什么连接的时候是三次握手? 3、什么是半连接队列? 4、ISN(Initial Sequence Number)是固定的吗? 5、三次握手过程中可以携带数据吗? 6、如果第三次握手丢失了,客户端服务端会如何处理? 7、SYN攻击是什么?...

    三次握手,四次分手是面试预热问题,这个问题还是很考验大家对底层通讯的思考!!

    面试会有如下问题:

    1、请画出三次握手和四次挥手的示意图
    2、为什么连接的时候是三次握手?
    3、什么是半连接队列?
    4、ISN(Initial Sequence Number)是固定的吗?
    5、三次握手过程中可以携带数据吗?
    6、如果第三次握手丢失了,客户端服务端会如何处理?
    7、SYN攻击是什么?
    8、挥手为什么需要四次?
    9、四次挥手释放连接时,等待2MSL的意义?

    1. 三次握手

    三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。

    刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
    进行三次握手:

    第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN©。此时客户端处于 SYN_SEND 状态。

    首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。

    第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

    在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。

    第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。

    确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。

    发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。

    在这里插入图片描述

    1.1 为什么需要三次握手,两次不行吗?

    弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

    第一次握手:客户端发送网络包,服务端收到了。
    这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
    第二次握手:服务端发包,客户端收到了。
    这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
    第三次握手:客户端发包,服务端收到了。
    这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
    因此,需要三次握手才能确认双方的接收与发送能力是否正常。

    1.2 什么是半连接队列?
    服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。

    当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

    这里在补充一点关于SYN-ACK 重传次数的问题:
    服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
    注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…

    1.3 ISN(Initial Sequence Number)是固定的吗?
    当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。

    三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

    1.4 三次握手过程中可以携带数据吗?
    其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据

    为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

    也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

    1.5 SYN攻击是什么?
    服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

    netstat -n -p TCP | grep SYN_RECV
    

    常见的防御 SYN 攻击的方法有如下几种:

    缩短超时(SYN Timeout)时间
    增加最大半连接数
    过滤网关防护
    SYN cookies技术

    2. 四次挥手

    建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

    TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作。

    刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:

    第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
    即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
    第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
    即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
    第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
    即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
    第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
    即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
    收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。

    在socket编程中,任何一方执行close()操作即可产生挥手操作。
    在这里插入图片描述
    2.1 挥手为什么需要四次?
    因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

    2.2 2MSL等待状态
    TIME_WAIT状态也成为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

    对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

    这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

    2.3 四次挥手释放连接时,等待2MSL的意义?
    MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

    为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

    两个理由:
    保证客户端发送的最后一个ACK报文段能够到达服务端。
    这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。
    防止“已失效的连接请求报文段”出现在本连接中。
    客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
    2.4 为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSE状态?
    理论上,四个报文都发送完毕,就可以直接进入CLOSE状态了,但是可能网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

    在这里插入图片描述

    展开全文
  • tcp三次握手

    2021-08-29 15:06:21
    其实三次握手在内核的实现中,并不只是简单的状态的流转,还包括半连接队列、syncookie、全连接队列、重传计时器等关键操作。如果能深刻理解这些,你对线上把握和理解将更进一步。如果有面试官问起你三次握手,相信...

    在后端相关岗位的入职面试中,三次握手的出场频率非常的高,甚至说它是必考题也不为过。一般的答案都是说客户端如何发起 SYN 握手进入 SYN_SENT 状态,服务器响应 SYN 并回复 SYNACK,然后进入 SYN_RECV,...... ,吧啦吧啦诸如此类。

    但我今天想给出一份不一样的答案。其实三次握手在内核的实现中,并不只是简单的状态的流转,还包括半连接队列、syncookie、全连接队列、重传计时器等关键操作。如果能深刻理解这些,你对线上把握和理解将更进一步。如果有面试官问起你三次握手,相信这份答案一定能帮你在面试官面前赢得非常多的加分。

    在基于 TCP 的服务开发中,三次握手的主要流程图如下。

    服务器中的核心代码是创建 socket,绑定端口,listen 监听,最后 accept 接收客户端的请求。

    //服务端核心代码
    int main(int argc, char const *argv[])
    {
     int fd = socket(AF_INET, SOCK_STREAM, 0);
     bind(fd, ...);
     listen(fd, 128);
     accept(fd, ...);
     ...
    }
    

    客户端的相关代码是创建 socket,然后调用 connect 连接 server。

    //客户端核心代码
    int main(){
     fd = socket(AF_INET,SOCK_STREAM, 0);
     connect(fd, ...);
     ...
    }
    

    围绕这个三次握手图,以及客户端、服务端的核心代码,我们来深度探索一下三次握手过程中的内部操作。我们从和三次握手过程关系比较大的 listen 讲起!

    友情提示:本文中内核源码会比较多。如果你能理解得了更好,如果觉得理解起来有困难,那直接重点看本文中的描述性的文字,尤其是加粗部分的即可。另外文章最后有一张总结图归纳和整理了全文内容。

    一、服务器 listen

    我们都知道,服务器在开始提供服务之前都需要先 listen 一下。但 listen 内部究竟干了啥,我们平时很少去琢磨。

    今天就让我们详细来看看,直接上一段 listen 时执行到的内核代码。

    //file: net/core/request_sock.c
    int reqsk_queue_alloc(struct request_sock_queue *queue,
         unsigned int nr_table_entries)
    {
     size_t lopt_size = sizeof(struct listen_sock);
     struct listen_sock *lopt;
    
     //计算半连接队列的长度
     nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
     nr_table_entries = ......
    
     //为半连接队列申请内存
     lopt_size += nr_table_entries * sizeof(struct request_sock *);
     if (lopt_size > PAGE_SIZE)
      lopt = vzalloc(lopt_size);
     else
      lopt = kzalloc(lopt_size, GFP_KERNEL);
    
     //全连接队列头初始化
     queue->rskq_accept_head = NULL;
    
     //半连接队列设置
     lopt->nr_table_entries = nr_table_entries;
     queue->listen_opt = lopt;
     ......
    }
    

    在这段代码里,内核计算了半连接队列的长度。然后据此算出半连接队列所需要的实际内存大小,开始申请用于管理半连接队列对象的内存(半连接队列需要快速查找,所以内核是用哈希表来管理半连接队列的,具体在 listen_sock 下的 syn_table 下)。最后将半连接队列挂到了接收队列 queue 上。

    另外 queue->rskq_accept_head 代表的是全连接队列,它是一个链表的形式。在 listen 这里因为还没有连接,所以将全连接队列头 queue->rskq_accept_head 设置成 NULL。

    当全连接队列和半连接队列中有元素的时候,他们在内核中的结构图大致如下。

    在服务器 listen 的时候,主要是进行了全/半连接队列的长度限制计算,以及相关的内存申请和初始化。全/半连接队列初始化了以后才可以响应来自客户端的握手请求。

    二、客户端 connect

    客户端通过调用 connect 来发起连接。在 connect 系统调用中会进入到内核源码的 tcp_v4_connect。

    //file: net/ipv4/tcp_ipv4.c
    int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
    {
     //设置 socket 状态为 TCP_SYN_SENT
     tcp_set_state(sk, TCP_SYN_SENT);
    
     //动态选择一个端口
     err = inet_hash_connect(&tcp_death_row, sk);
    
     //函数用来根据 sk 中的信息,构建一个完成的 syn 报文,并将它发送出去。
     err = tcp_connect(sk);
    }
    

    在这里将完成把 socket 状态设置为 TCP_SYN_SENT。再通过 inet_hash_connect 来动态地选择一个可用的端口后,进入到 tcp_connect 中。

    //file:net/ipv4/tcp_output.c
    int tcp_connect(struct sock *sk)
    {
     tcp_connect_init(sk);
    
     //申请 skb 并构造为一个 SYN 包
     ......
    
     //添加到发送队列 sk_write_queue 上
     tcp_connect_queue_skb(sk, buff);
    
     //实际发出 syn
     err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
        tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
    
     //启动重传定时器
     inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
          inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
    }
    

    在 tcp_connect 申请和构造 SYN 包,然后将其发出。同时还启动了一个重传定时器,该定时器的作用是等到一定时间后收不到服务器的反馈的时候来开启重传。在 3.10 版本中首次超时时间是 1 s,一些老版本中是 3 s。

    总结一下,客户端在 connect 的时候,把本地 socket 状态设置成了 TCP_SYN_SENT,选了一个可用的端口,接着发出 SYN 握手请求并启动重传定时器

    三、服务器响应 SYN

    在服务器端,所有的 TCP 包(包括客户端发来的 SYN 握手请求)都经过网卡、软中断,进入到 tcp_v4_rcv。在该函数中根据网络包(skb)TCP 头信息中的目的 IP 信息查到当前在 listen 的 socket。然后继续进入 tcp_v4_do_rcv 处理握手过程。

    //file: net/ipv4/tcp_ipv4.c
    int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
    {
     ...
     //服务器收到第一步握手 SYN 或者第三步 ACK 都会走到这里
     if (sk->sk_state == TCP_LISTEN) {
      struct sock *nsk = tcp_v4_hnd_req(sk, skb);
     }
    
     if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
      rsk = sk;
      goto reset;
     }
    }
    

    在 tcp_v4_do_rcv 中判断当前 socket 是 listen 状态后,首先会到 tcp_v4_hnd_req 去查看半连接队列。服务器第一次响应 SYN 的时候,半连接队列里必然是空空如也,所以相当于什么也没干就返回了。

    //file:net/ipv4/tcp_ipv4.c
    static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
    {
     // 查找 listen socket 的半连接队列
     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
              iph->saddr, iph->daddr);
     ...
     return sk;
    }
    

    在 tcp_rcv_state_process 里根据不同的 socket 状态进行不同的处理。

    //file:net/ipv4/tcp_input.c
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
         const struct tcphdr *th, unsigned int len)
    {
     switch (sk->sk_state) {
      //第一次握手
      case TCP_LISTEN:
       if (th->syn) { //判断是 SYN 握手包
        ...
        if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
         return 1;
     ......
    }  
    

    其中 conn_request 是一个函数指针,指向 tcp_v4_conn_request。服务器响应 SYN 的主要处理逻辑都在这个 tcp_v4_conn_request 里

    //file: net/ipv4/tcp_ipv4.c
    int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
    {
     //看看半连接队列是否满了
     if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
      want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
      if (!want_cookie)
       goto drop;
     }
    
     //在全连接队列满的情况下,如果有 young_ack,那么直接丢
     if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
      NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
      goto drop;
     }
     ...
     //分配 request_sock 内核对象
     req = inet_reqsk_alloc(&tcp_request_sock_ops);
    
     //构造 syn+ack 包
     skb_synack = tcp_make_synack(sk, dst, req,
      fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL);
    
     if (likely(!do_fastopen)) {
      //发送 syn + ack 响应
      err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
        ireq->rmt_addr, ireq->opt);
    
      //添加到半连接队列,并开启计时器
      inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
     }else ...
    }
    

    在这里首先判断半连接队列是否满了,如果满了的话进入 tcp_syn_flood_action 去判断是否开启了 tcp_syncookies 内核参数。如果队列满,且未开启 tcp_syncookies,那么该握手包将直接被丢弃!

    接着还要判断全连接队列是否满。因为全连接队列满也会导致握手异常的,那干脆就在第一次握手的时候也判断了。如果全连接队列满了,且有 young_ack 的话,那么同样也是直接丢弃。

    young_ack 是半连接队列里保持着的一个计数器。记录的是刚有 SYN 到达,没有被 SYN_ACK 重传定时器重传过 SYN_ACK,同时也没有完成过三次握手的 sock 数量。

    接下来是构造 synack 包,然后通过 ip_build_and_send_pkt 把它发送出去。

    最后把当前握手信息添加到半连接队列,并开启计时器。计时器的作用是如果某个时间之内还收不到客户端的第三次握手的话,服务器会重传 synack 包。

    总结一下,服务器响应 ack 的主要工作是判断下接收队列是否满了,满的话可能会丢弃该请求,否则发出 synack。申请 request_sock 添加到半连接队列中,同时启动定时器

    四、客户端响应 SYNACK

    客户端收到服务器端发来的 synack 包的时候,也会进入到 tcp_rcv_state_process 函数中来。不过由于自身 socket 的状态是 TCP_SYN_SENT,所以会进入到另一个不同的分支中去。

    //file:net/ipv4/tcp_input.c
    //除了 ESTABLISHED 和 TIME_WAIT,其他状态下的 TCP 处理都走这里
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
         const struct tcphdr *th, unsigned int len)
    {
     switch (sk->sk_state) {
      //服务器收到第一个ACK包
      case TCP_LISTEN:
       ...
      //客户端第二次握手处理 
      case TCP_SYN_SENT:
       //处理 synack 包
       queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
       ...
       return 0;
    }
    

    tcp_rcv_synsent_state_process 是客户端响应 synack 的主要逻辑。

    //file:net/ipv4/tcp_input.c
    static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
          const struct tcphdr *th, unsigned int len)
    {
     ...
    
     tcp_ack(sk, skb, FLAG_SLOWPATH);
    
     //连接建立完成 
     tcp_finish_connect(sk, skb);
    
     if (sk->sk_write_pending ||
       icsk->icsk_accept_queue.rskq_defer_accept ||
       icsk->icsk_ack.pingpong)
      //延迟确认...
     else {
      tcp_send_ack(sk);
     }
    } 
    

    tcp_ack()->tcp_clean_rtx_queue()。

    //file: net/ipv4/tcp_input.c
    static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets,
           u32 prior_snd_una)
    {
     //删除发送队列
     ...
    
     //删除定时器
     tcp_rearm_rto(sk);
    }
    
    //file: net/ipv4/tcp_input.c
    void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
    {
     //修改 socket 状态
     tcp_set_state(sk, TCP_ESTABLISHED);
    
     //初始化拥塞控制
     tcp_init_congestion_control(sk);
     ...
    
     //保活计时器打开
     if (sock_flag(sk, SOCK_KEEPOPEN))
      inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
    }
    

    客户端修改自己的 socket 状态为 ESTABLISHED,接着打开 TCP 的保活计时器。

    //file:net/ipv4/tcp_output.c
    void tcp_send_ack(struct sock *sk)
    {
     //申请和构造 ack 包
     buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
     ...
    
     //发送出去
     tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
    }
    

    在 tcp_send_ack 中构造 ack 包,并把它发送了出去。

    客户端响应来自服务器端的 synack 时清除了 connect 时设置的重传定时器,把当前 socket 状态设置为 ESTABLISHED,开启保活计时器后发出第三次握手的 ack 确认。

    五、服务器响应 ACK

    服务器响应第三次握手的 ack 时同样会进入到 tcp_v4_do_rcv。

    //file: net/ipv4/tcp_ipv4.c
    int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
    {
     ...
     if (sk->sk_state == TCP_LISTEN) {
      struct sock *nsk = tcp_v4_hnd_req(sk, skb);
     }
    
     if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
      rsk = sk;
      goto reset;
     }
    }
    

    不过由于这已经是第三次握手了,半连接队列里会存在上次第一次握手时留下的半连接信息。所以 tcp_v4_hnd_req 的执行逻辑会不太一样。

    //file:net/ipv4/tcp_ipv4.c
    static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
    {
     ...
     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
              iph->saddr, iph->daddr);
     if (req)
      return tcp_check_req(sk, skb, req, prev, false);
     ...
    }
    

    inet_csk_search_req 负责在半连接队列里进行查找,找到以后返回一个半连接 request_sock 对象。然后进入到 tcp_check_req 中。

    //file:net/ipv4/tcp_minisocks.c
    struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
          struct request_sock *req,
          struct request_sock **prev,
          bool fastopen)
    {
     ...
     //创建子 socket
     child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
     ...
    
     //清理半连接队列
     inet_csk_reqsk_queue_unlink(sk, req, prev);
     inet_csk_reqsk_queue_removed(sk, req);
    
     //添加全连接队列
     inet_csk_reqsk_queue_add(sk, req, child);
     return child;
    }
    

    5.1 创建子 socket

    icsk_af_ops->syn_recv_sock 对应的是 tcp_v4_syn_recv_sock 函数。

    //file:net/ipv4/tcp_ipv4.c
    const struct inet_connection_sock_af_ops ipv4_specific = {
     ......
     .conn_request      = tcp_v4_conn_request,
     .syn_recv_sock     = tcp_v4_syn_recv_sock,
    
    //三次握手接近就算是完毕了,这里创建 sock 内核对象
    struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
          struct request_sock *req,
          struct dst_entry *dst)
    {    
     //判断接收队列是不是满了
     if (sk_acceptq_is_full(sk))
      goto exit_overflow;
    
     //创建 sock && 初始化
     newsk = tcp_create_openreq_child(sk, req, skb);
    

    **注意,在第三次握手的这里又继续判断一次全连接队列是否满了,如果满了修改一下计数器就丢弃了。如果队列不满,那么就申请创建新的 sock 对象。

    5.2 删除半连接队列

    把连接请求块从半连接队列中删除。

    //file: include/net/inet_connection_sock.h 
    static inline void inet_csk_reqsk_queue_unlink(struct sock *sk, struct request_sock *req,
     struct request_sock **prev)
    {
     reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);
    }
    

    reqsk_queue_unlink 中把连接请求块从半连接队列中删除。

    5.3 添加全连接队列

    接着添加到全连接队列里边来。

    //file:net/ipv4/syncookies.c
    static inline void inet_csk_reqsk_queue_add(struct sock *sk,
          struct request_sock *req,
          struct sock *child)
    {
     reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
    }
    

    在 reqsk_queue_add 中将握手成功的 request_sock 对象插入到全连接队列链表的尾部。

    //file: include/net/request_sock.h
    static inline void reqsk_queue_add(...)
    {
     req->sk = child;
     sk_acceptq_added(parent);
    
     if (queue->rskq_accept_head == NULL)
      queue->rskq_accept_head = req;
     else
      queue->rskq_accept_tail->dl_next = req;
    
     queue->rskq_accept_tail = req;
     req->dl_next = NULL;
    }
    

    5.4 设置连接为 ESTABLISHED

    //file:net/ipv4/tcp_input.c
    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
         const struct tcphdr *th, unsigned int len)
    {
     ...
     switch (sk->sk_state) {
    
      //服务端第三次握手处理
      case TCP_SYN_RECV:
    
       //改变状态为连接
       tcp_set_state(sk, TCP_ESTABLISHED);
       ...
     }
    }
    

    将连接设置为 TCP_ESTABLISHED 状态。

    服务器响应第三次握手 ack 所做的工作是把当前半连接对象删除,创建了新的 sock 后加入到全连接队列中,最后将新连接状态设置为 ESTABLISHED

    六、服务器 accept

    最后 accept 一步咱们长话短说。

    //file: net/ipv4/inet_connection_sock.c
    struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
    {
     //从全连接队列中获取
     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
     req = reqsk_queue_remove(queue);
    
     newsk = req->sk;
     return newsk;
    }
    

    reqsk_queue_remove 这个操作很简单,就是从全连接队列的链表里获取出第一个元素返回就行了。

    //file:include/net/request_sock.h
    static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue)
    {
     struct request_sock *req = queue->rskq_accept_head;
    
     queue->rskq_accept_head = req->dl_next;
     if (queue->rskq_accept_head == NULL)
      queue->rskq_accept_tail = NULL;
    
     return req;
    }
    

    所以,accept 的重点工作就是从已经建立好的全连接队列中取出一个返回给用户进程。

    本文总结

    在后端相关岗位的入职面试中,三次握手的出场频率非常得高。其实在三次握手的过程中,不仅仅是一个握手包的发送和 TCP 状态的流转。还包含了端口选择,连接队列创建与处理等很多关键技术点。通过今天一篇文章,我们深度去了解了三次握手过程中内核中的这些内部操作。

    全文洋洋洒洒上万字,其实可以用一幅图总结起来。

    • 1. 服务器 listen 时,计算了全/半连接队列的长度,还申请了相关内存并初始化。

    • 2. 客户端 connect 时,把本地 socket 状态设置成了 TCP_SYN_SENT,选择一个可用的端口,发出 SYN 握手请求并启动重传定时器。

    • 3. 服务器响应 ack 时,会判断下接收队列是否满了,满的话可能会丢弃该请求。否则发出 synack,申请 request_sock 添加到半连接队列中,同时启动定时器。

    • 4. 客户端响应 synack 时,清除了 connect 时设置的重传定时器,把当前 socket 状态设置为 ESTABLISHED,开启保活计时器后发出第三次握手的 ack 确认。

    • 5. 服务器响应 ack 时,把对应半连接对象删除,创建了新的 sock 后加入到全连接队列中,最后将新连接状态设置为 ESTABLISHED。

    • 6. accept 从已经建立好的全连接队列中取出一个返回给用户进程。

    另外要注意的是,如果握手过程中发生丢包(网络问题,或者是连接队列溢出),内核会等待定时器到期后重试,重试时间间隔在 3.10 版本里分别是 1s 2s 4s ...。在一些老版本里,比如 2.6 里,第一次重试时间是 3 秒。最大重试次数分别由 tcp_syn_retries 和 tcp_synack_retries 控制。

    如果你的线上接口正常都是几十毫秒内返回,但偶尔出现了 1 s、或者 3 s 等这种偶发的响应耗时变长的问题,那么你就要去定位一下看看是不是出现了握手包的超时重传了。

    以上就是三次握手中一些更详细的内部操作。如果你能在面试官面前讲出来内核的这些底层逻辑,我相信面试官一定会对你刮目相看的!

    展开全文
  • TCP 是一个可以双向传输的全双工协议,所以需要经过三次握手才能建立连接。三次握手在一个 HTTP 请求中的平均时间占比在 10% 以上,在网络状况不佳、高并发或者遭遇SYN 泛洪攻击等场景中,如果不能正确地调整三次...
  • TCP三次握手连接

    2016-08-12 11:22:31
    TCP三次握手连接TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手...
  • FIN-WAIT-1:等待远程 TCP连接中断请求,或先前的连接中断请求的确认。 FIN-WAIT-2:从远程 TCP 等待连接中断请求。 CLOSE-WAIT:等待从本地用户发来的连接中断请求。 CLOSING:等待远程 TCP连接...
  • TCP 三次握手和四次挥手

    千次阅读 2022-04-13 23:42:13
    讲解 TCP 三次握手和四次握手之前,我们先了解一下 TCP 和 UDP 这两个重量级的传输层协议。 用户数据报协议 UDP(User Datagram Protocol): UDP 在传送数据之前不需要先建立连接,远程主机在收到 UDP 报文后,...
  • TCP三次握手: 为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。 第一次握手:Client将标志位SYN置为1,随机...
  • @TCP连接的三次握手和四次挥手 TCP连接的三次握手和四次挥手 1.1 什么是TCP的三次握手 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。 目的: 1.确认双方的接收...
  • TCP连接的建立与释放

    2021-08-30 22:09:47
    TCP是一个面向连接的协议。无论哪一方向另一方发送数据前,都必须先在双方之间建立一条连接。 这种两端间连接的建立与无连接协议如UDP不同。一端使用UDP向另一端发送数据报时,无需任何预先的握手。 建立连接 ...
  • 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性...
  • TCP三次握手

    2021-03-31 11:44:41
    TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。在socket编程中,这一过程由客户端执行connect来触发。 流程...
  • TCP三次握手详解

    2019-10-01 22:21:11
    three-way handshake)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。...
  • 推荐视频: tcpip,accept,11个状态,细枝末节的秘密,还有哪些你不知道 手写一个用户态网络协议栈...一般的答案都是说客户端如何发起 SYN 握手进入 SYN_SENT 状态,服务器响应 SYN 并回复 SYNACK,然后进入 SYN_RE
  • 0. 前言 在面试中,计算机网络的 TCP 三次握手和四次挥手是很常见的问题,但是在...讲解 TCP 三次握手和四次握手之前,我们先了解一下 TCP 和 UDP 这两个重量级的传输层协议。 用户数据报协议 UDP(User Datagram .
  • 关于 TCP 三次握手和四次挥手,满分回答在此Veal98发布于 今天 12:060. 前言在面试中,计算机网络的 TCP 三次握手和四次挥手是很常见的问题,但是在实际面试中,面试官会更愿意听到怎样的回答呢?详细程度是怎样的...
  • 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性...
  • TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须...
  • 建立连接: 理解:窗口和滑动窗口 TCP的流量控制 TCP使用窗口机制进行流量控制 什么是窗口? 连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端 接收方发送的确认信息中包含...
  • 为什么连接的时候是三次握手? 什么是半连接队列? ISN(Initial Sequence Number)是固定的吗? 三次握手过程中可以携带数据吗? 如果第三次握手丢失了,客户端服务端会如何处理? SYN攻击是什么? 挥手为什么需要四...
  • TCP连接如何断开连接

    2021-03-07 04:32:24
    这就是TCP连接终止的前一半工作。 3:子进程终止时,内核将给父进程递交SIGCHLD信号。 4:客户上没有发生任何特殊之事。客户TCP接受来自服务器TCP的FIN并响应一个ACK,然后问题是客户进程此时阻塞在fgets调用上,...
  • 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性...
  • TCP的三次握手与四次挥手详解 首先,我们要带着问题来思考 请画出三次握手和四次挥手的示意图 为什么连接的时候是三次握手? 什么是半连接队列? ISN(Initial Sequence Number)是固定的吗? 三次握手过程中可以携带...
  • 三次握手和四次挥手是各个公司常见的考点,也具有一定的水平区分度,也被一些面试官作为热身题。很多小伙伴说这个问题刚开始回答的挺好,但是后面越回答越冒冷汗,最后就歇菜了。 见过比较典型的面试场景是这样的: ...
  • 三次握手和四次挥手是各个公司常见的考点,也具有一定的水平区分度,也被一些面试官作为热身题。很多小伙伴说这个问题刚开始回答的挺好,但是后面越回答越冒冷汗,最后就歇菜了。 见过比较典型的面试场景是这样的:...
  • TCP连接建立过程

    千次阅读 2020-12-09 17:39:20
    TCP连接建立过程 浏览器访问网站,通过域名解析找到ip地址后会与服务器端建立连接。其中TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,它的特点是数据在传输...
  • TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;  第二次握手:服务器收到syn包...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,942
精华内容 1,576
关键字:

发起tcp连接首次握手的操作是

友情链接: TDOA_GDOP_5BSN.rar