tcp协议的作用是确保数据的完整性和可靠性,防止丢包。

tcp报文

image.png

报文结构

  1. 4字节端口号 16位源端口号,16位目的端口号
  2. 4字节序列号,即seq
  3. 4字节ACK确认序列号,即ACK
  4. 4位数据偏移(也叫首部长度),代表了TCP报文头部的长度,即数据从哪里开始,每一位的单位是32位,而4位(1111)转化成10进制为15,即15个32位,即15*4字节,即报文头部长度最多位60字节。
  5. 12位标志位

    • URG紧急标志位 配合紧急指针使用
    • ACK确认标志位
    • PSH推送标志位 即希望对方立刻向上交付,而不是等待这一次的数据全部缓存完再一起交付
    • RST复位标志位 表示tcp连接出现很重大的错误,需要释放再重新进行连接,或者用来拒绝非法报文或打开一个连接
    • SYN同步标志位 SYN=1就表示这是一个连接请求或连接接受报文。
    • FIN结束标志位

      加上上面的数据偏移一共2个字节

  6. 2字节窗口,表示己方期望接收的数据大小,从ACK确认序列开始计算,假如发送的报文中确认序列号为2000,窗口为2000,即期望接收seq为2000-3999的报文,大小在2000以内。

  7. 2字节校验和
  8. 2字节紧急指针,仅在URG=1时才有意义,如果有紧急数据,就会排在这一次发送的数据的前面,紧急指针指出紧急数据在数据中的位置,在紧急数据处理完时,就恢复正常操作顺序
  9. 选项,长度可变,最大为40字节

因此报文头部最长60字节,最短20字节。

Seq(sequence number序列号)

用来标定tcp数据包的顺序,使得资源可以按正确的顺序还原,如果发生丢包,可以知道是哪一个包丢了。
第一个包的seq是随机数(假设第一个是0),因为一个包的大小大概为1400字节左右(先假设是1400字节),那么下一个包的编号就是1400,每一个包中都有两个seq,一个是自己的编号,一个是下一个包的编号,这样就可以按正确的顺序排列包。
image.png

ACK(acknowledge确认)与发送速率

由于不同的线路的网络状况不同,因此发送的速率要求就不同,发送速率过快会导致丢包更容易发生,因此tcp协议规定了慢启动机制,一开始发送的较慢,在发送的过程中确认对方的接收状态,然后调整发送的速率。一般一开始发送速率是10个包,每收到两个tcp数据包,接收方就会发回一个确认报文。
ACK确认报文包含两个内容

  1. 期待下一个收到的包的编号
  2. 接收方的接收窗口的容量大小(就是能接收多少个包)

举个例子
image.png

  1. 左边发送的包中,随机生成了seq为1的包,大小为100字节,并且期待对方发回一个编号为1的包(ACK)
  2. 对方接收到之后,发回一个编号为1的包,大小为200字节,并且期待收到一个101编号的包(因为上一个大小是100,1+100)
  3. 左边收到右边的包后,发送一个101号的包过去,并且期待收到一个201编号的包(上一个是1,大小为200,1+200)
  4. 对方收到后,发回201编号的包,大小为50,并且期待收到一个151编号的包

可以得到的信息

  1. 因为tcp的发送和接收都是双向的,因此seq、ack的信息也是双向的,通过seq标定己方发送的包,通过ack来标定期待对方发送的包
  2. 接收方每一次收到期待的包,都会更新下一次发送包的ack信息,比如接收方如果现在ack信息为101,表示期待收到101的包,如果发送方发送了101编号的包,并且下一个编号为201,那么接收方的ack信息就会更新成201

    丢包的处理

    接上方,如果接收方收到了101号包,就会更新ack信息为201,如果没有收到101号包,那么下一次发送包的ack信息就还是101号,tcp协议规定,如果连续收到三个相同ack(基于重复累计确认的重传),或者超时了还没有收到任何ack(超时重传),那么就会重新发送ack信息中的包,这就保证了,丢包后数据的重新发送。

    具体过程

    上面列举了最重要的seq和ack的工作原理,来看一下具体的建立的过程。

    建立连接

    三次握手
    image.png

  3. 第一次握手 客户端(C)向服务端(S)第一次发送连接请求,会发送一个携带连接信息的报文SYN设置为1,随机生成一个seq=x,这个时候C进入SYN-SENT(同步已发送)状态

注意:第一次握手的报文是不携带数据的,因为tcp规定SYN为1的报文不能携带数据。

  1. 第二次握手 S收到C的请求连接的报文后,会返回一个用来确认的报文,这个报文的SYN、ACK标志位都为1(因此也不能携带数据),并且随机生成一个seq=y,ack确认序列为第一次握手的报文的x+1,这时S进入SYN-RCVD(同步已接收)状态
  2. 第三次握手 C收到S的确认报文后进入ESTABLISHED(已建立连接)状态,会再次返回一个确认报文,这个报文的ACK标志位为1,ack确认序列为y+1,这时已经可以携带数据,如果携带数据,那么这个报文的seq为x+1,当然也可以不携带数据,这个时候不会消耗序列号,因此下一个报文的seq为x+1。S收到这个确认报文后也进入ESTABLISHED(已建立连接)状态

    注意:上述行为是tcp规定的,ACK确认报文可以携带数据,也可以不携带数据,不携带数据的时候,就不会消耗序列号。
    为什么需要三次握手?

  3. 为了确认双方的接收、发送数据的功能正常

  4. 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。例如,第一次建立连接的报文在某个网络节点停滞了,从而S没有收到这个请求连接的报文,也就不会发回确认报文,超过一定时长C会重发请求连接的报文,而第二个报文没有停滞,假如不使用三次握手(即S返回确认报文就建立连接)这时C和S就已经建立连接了。如果在这次连接结束后,第一个请求连接的报文又传到了S,S会认为连接建立,一直等待C的数据,而C认为没有发送过请求报文,不予理睬,会导致S一直等待。而使用三次握手,需要C再次返回确认报文,这样S就不会自认为建立了连接了。

    关闭连接

    四次挥手
    由于tcp是全双工通信的,即每一方都同时具有发送和接收的能力,因此每一方都要告知对方自己已经发送完毕,并且返回确认报文
    假如C先发送完毕

  5. 第一次挥手 C发送一个FIN(finish)标志位为1的报文,seq为x,告诉S我已经发送完了,并进入FIN_WAIT_1(中止等待1)状态

  6. 第二次挥手 S发送一个确认报文,ACK标志位为1,ack确认序列为x+1,seq为y,告诉C我知道你发送完了,并进入CLOSE_WAIT(等待关闭)状态,客户端收到报文,进入FIN_WAIN_2(中止等待2)状态
  7. 第三次挥手 假如现在S也接收完了数据,会发送一个FIN标志位为1的报文,ACK标志位为1,seq为w,ack确认序列为x+1,进入LAST_ACK(最后确认)状态
  8. 第四次握手 C接收到S发来的FIN报文,发送一个确认报文,ACK标志位为1,seq为x+1,ack确认序列为w+1,并且进入TIME_WAIT(时间等待)状态,S接收到这个确认报文,进入CLOSED(已关闭)状态,经过2MSL时间后,C进入CLOSED(已关闭)状态

注意:从 TIME_WAIT 进入 CLOSED 需要经过 2MSL,其中 MSL 就叫做 最长报文段寿命(Maxinum Segment Lifetime),根据 RFC 793 建议该值这是为 2 分钟,也就是说需要经过 4 分钟,才进入 CLOSED 状态。