计算机网络——TCP协议 && 三次握手 && 四次挥手

1、TCP主要特色

  1. TCP是面向链接的运输层协议。应用程序在使用TCP协议以前必须先创建TCP链接。web

  2. 每一条TCP链接只能由两个端点(一对一)。缓存

  3. TCP提供可靠交付的服务。经过TCP链接传送的数据,无差错、不丢失、不重复、而且按序到达。服务器

  4. TCP提供全双工通讯网络

  5. 面向字节流socket


2、可靠性传输的工做原理

理想传输:
①传输信道不产生差错。
②无论发送方以多快的速度发送数据,接收方老是来得及处理收到的数据。tcp

1. 中止等待协议

分为三种状况:svg

  1. 无差错状况
    以下,A发送分组M1,发完就暂停发送,等待B确认。B收到M1后就向A发送确认。A收到B的确认后再向B发送下一个分组M2。

这里写图片描述

2.出现差错
  B接收M1时检测出了差错,就丢弃M1(没有通知A收到有差错分组),也多是M1传输过程当中丢失了。此时B不会发送确认消息。这时候可靠传输协议规定:A只要超过必定时间没收到确认消息,就认为刚刚发的分组丢失了,就会重发前面发送过的分组——超时重传。
  为了实现超时重传,要在每次发送完一个分组设置一个超时计时器。若是计时器到期前收到了对方确认,就撤销已设置的超时计时器。此处要注意三点:
  第一:A发完一个分组后,必须暂时保存已发送的分组的副本(为了超时重发),只有收到相应确认后才能清除暂时保留的分组副本。
  第二:分组和确认分组必须编号。这样才能确认哪一个发出的分组收到/没收到确认。
  第三:超时计时器设置的重传时间应当比数据在分组传输的平均往返时间更长一些。函数

  1. 确认丢失和确认迟到
      在下图a中,B发送的对M1的确认丢失了。A在设定的超时重传时间内没收到确认,它不知道出了啥岔子,他所能作的就是重传。而后B再次受到M1,B此时要作的是:①丢弃这个重复的M1分组,不向上层交付;②向A发送确认(我已经收到啦,别发了)。
      下图b,传输过程无差错,但B对M1分组的确认迟到了。A会收到重复的确认,对此A收到重复确认直接丢弃。B仍会才收到重复的M1分组(处理方式和上同样)。
    这里写图片描述

3、TCP报文段的首部格式

这里写图片描述

  • 源端口号和目的端口号:各占2个字节,表示数据是从哪一个进程来,要到哪一个进程去。atom

  • 序号:占4个字节,序号范围是[ 0 , 2 32 1 ],序号增长到 2 32 1 后,下一个序号又回到0。也就是说,序号采用 m o d 2 32 运算。TCP面向字节流,传输的字节流中每一个字节都按顺序编号。这里的序号指的是本报文段所发送的数据的第一个字节的序号。spa

  • 确认号:占 4 位,是指望收到对方下一个报文段的第一个数据字节的序号。
    如 确认号 = N,表示:到序号 N-1 为止的全部数据都正确收到。

  • 数据偏移:占4byte,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。它其实是指出 TCP 报文段的首部长度(首部中有长度不肯定的选项字段,所以这个字段是必须的)。“数据偏移”的单位是 32 位字(以4字节为计算单位)。4个byte的二进制数能表示的最大十进制数是 15 ,所以数据偏移的最大值是 60 字节,这也是 TCP 首部的最大长度(选项长度不能超过 40 字节)。

  • 保留:占 6 位,保留为从此使用,但目前应置为 0 。

  • 紧急 URG (URGent)当 URG=1 时,代表紧急指针字段有效。 它告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送。
      当 URG 置 1 时,发送应用进程就告诉对方的 TCP 有紧急数据要传送。因而发送方 TCP 就把紧急数据插到本报文段数据的最前面。这时候要与首部的紧急指针配合使用。

  • 确认 ACK (ACKnowledgment)仅当 ACK=1 时,确认号字段才有效。TCP 规定,在创建链接后全部传送的报文段必须把 ACK 置 1。

  • 推送 PSH (PuSH)提示接收端应用程序马上从TCP缓冲区把数据读走。此时,发送方 TCP 把 PSH 置 1,并建立一个报文段发送出去。接收方 TCP 收到 PSH=1 的报文段,就尽快地交付接受应用程序,而不是等到整个缓存区满了再向上交付。

  • 复位 RST (ReSeT)当 RST=1 时,代表 TCP 链接中出现严重差错,必须释放链接,再从新创建链接。RST 置 1 还用来拒绝一个非法的报文段或拒绝打开一个链接。RST也称为重建位或重置位。

  • 同步 SYN (SYNchronization):在链接创建是用来同步序号。当 SYN=1 而 ACK=0 时,代表这是一个链接请求报文。若对方赞成创建链接,则应在响应的报文段中使 SYN=1 和 ACK=1。

  • 终止 FIN (FINis)用来释放一个链接。 当 FIN=1 时,代表此报文段的发送方的数据已发送完毕,并要求释放运输链接。

  • 窗口:占 2 字节。窗口值是 [ 0 , 2 16 1 ] 之间的整数。它指的是发送本报文段的一方的接收窗口(而不是本身的发送窗口)。窗口值告诉对方:从本报文首部的确认号算起,接收方目前容许对方发送的数据量(接收方的数据缓存空间是有限的)。总之,窗口值明确指出了如今容许对方发送的数据量(窗口值是常常在动态变化着)。
      例如,我发出一个报文,确认号是 701,窗口值是 1000。这表示,从 701 号算起,我这里还有能够接受 1000 个字节数据(字节序号是 701 ~ 1700)的接受缓存空间。

  • 校验和 :占2字节(16位),由发送端填充,接收端检验报文是否损坏。这个检验不只包含TCP报文首部,也包含数据部分。

  • 紧急指针:占2字节(16位)。保存的是一个正的偏移量,序号+偏移量就是紧急数据的末尾在这段字节流中的编号。

  • 选项:长度可变,最长为40字节,当没有使用“选项”时,TCP首部长度为20字节。TCP最初只规定了一种选项,即最大报文段长度 MSS,它指的是TCP报文段中数据字段的最大长度


4、三次握手

  TCP 是面向链接的协议。TCP 运输链接的创建和释放是每次面向链接的通讯中必不可少的过程。所以,运输链接就有三个阶段,即:链接创建、数据传输和链接释放
  在 TCP 链接创建过程当中主要解决如下三个问题:
  1. 要使每一方能确知对方的存在。
  2. 要容许双方协商一些参数(如最大窗口值)。
  3. 可以对运输实体资源(如缓存大小、链接表中的项目等)进行分配。


1. 三次握手的具体步骤

这里写图片描述

  1. 主机 A 运行TCP客户端程序,主机 B 运行TCP服务器程序。最初两端的TCP进程都处于 CLOSED(关闭)状态。
  2. B 的 TCP 服务器进程先建立传输控制块 TCB,准备接受客户进程的链接请求。而后服务器进程就处于 LISTEN(收听) 状态,等待客户的请求。若有,即作出响应。

  3. A 的 TCP 客户进程也是首先建立传输控制块 TCB,而后向 B 发出链接请求报文,报文首部中的同步位 SYN=1,同时选择一个初始序号 seq=x。TCP 规定,SYN 报文段(即 SYN=1 的报文段)不能携带数据,但要消耗掉一个序号。此时,TCP 客户进程进入 SYN-SENT(同步已发送)状态。

  4. B 收到链接请求报文段后,如赞成创建链接,则向 A 发送确认。在确认报文段中应把 SYN 位和 ACK 位都置为 1,确认号是 ack=x+1,同时也要为本身选择一个初始序号 seq=y。这个报文段也不能携带数据,也一样消耗一个序号。此时 TCP 服务器进程进入 SYN-RCVD(同步收到) 状态。

  5. TCP 客户进程收到 B 的确认后,还要向 B给出确认确认报文段的 ACK 置 1,确认号 ack=y+1,而本身的序号 seq=x+1。TCP规定:ACK 报文段能够携带数据。但若是不携带则不消耗序号,在这种状况下,下一个数据报文段的序号还是 seq=x+1。这时 TCP 链接已创建, A进入 ESTABLISHED(已创建链接) 状态。

  6. 当 B 收到 A 的确认后,也进入ESTABLISHED(已创建链接) 状态。


2. 第三次握手的意义

  为何 A 还要发送一次确认呢?这主要是为了防止已失效的链接请求报文段忽然又传送到了 B,于是产生错误。

  正常状况,A 发送链接请求,但因请求链接报文丢失而未收到确认。因而 A 再重传一次链接请求。后来收到确认,创建链接。数据传输完毕后,就释放链接。A 共发了两个链接请求报文段,第一个丢失,第二个到达了 B。没有“已失效的链接请求报文段”
  现假定一种异常状况,即 A 发送的第一个链接请求报文段并无丢失,而是在某些网络节点长时间滞留了,以致于延误到链接释放后某个时间才到 B。B 收到后觉得是 A 又一次发起了链接请求。因而向 A 发出确认报文。假设没有第三次握手,那么只要 B 发出确认,新的链接就创建了。
  因为 A 如今并无发出链接请求,因此不会搭理 B 的确认,也不会向 B 传输数据。但 B 却认为链接已创建,就一直等待 A 发来数据,这样 B 的资源就白白浪费了。
  采用三次握手后,上述状况就不会发生。A 不会向 B 的确认发出确认。B 收不到确认就知道 A 并无要求创建链接。


5、四次挥手

1. 四次挥手具体步骤

这里写图片描述

  1. 数据传输结束后,通讯双方均可释放链接。如今 A 和 B 都处于 ESTABLISHED 状态。

  2. A 的应用程序先向其 TCP 发出链接释放报文段,并中止发送数据。主动关闭 TCP 链接。A 把链接释放报文段首部的终止控制位 FIN 置 1,其序号 seq=u,他等于前面已传送过的数据的最后一个字节的序号加 1。这时 A 进入 ==FIN-WAIT-1(终止等待1)== 状态。注意,即便 FIN 报文段不携带数据也消耗一个序号。

  3. B收到链接释放报文段后即发出确认,确认号是 ack=u+1,而这个报文段本身的序号是 v,等于 B 前面已传送的数据的最后一个字节的序号加 1.而后 B 就进入 CLOSE-WAIT(关闭等待) 状态。TCP 服务器进程这时应通知高层应用进程,由于从 A 到 B 这个方向的链接就释放了,此时 TCP 处于半关闭(half-close)状态,即 A 已经没有数据要发送了,但 B 若发送数据,A 仍要接受。

  4. A 收到 来自 B 的确认后,就进入 FIN-WAIT-2(终止等待2),等待 B 发出的链接释放报文段。

  5. 若 B 已没有要向 A 发送的数据了,其应用进程就要通知 TCP 释放链接。这时候 B 发出的链接释放报文必须使 FIN=1。现假定 B 的序号为 w(在半关闭状态 B 可能又发送了一些数据)。B 还必须重复发送上次已发送过的确认号 seq=u+1。此时 B 进入 LAST-ACK(最后确认) 状态,等待 A 确认。

  6. A 在收到 B 的链接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置 1,确认号 ack=w+1,本身的序号是 seq=u+1。而后进入 TIME-WAIT(时间等待) 状态。此时,TCP 链接还没释放掉,必须通过时间等待计时器设置的时间 2MSL 后,A 才进入到 CLOSED 状态。(时间 MSL 叫最长报文寿命


2. 为何 A 在 TIME-WAIT 状态必须等待 2MSL 的时间?

  第一,为了保证 A 发送的最后一个 ACK 报文段可以到达 B。这个 ACK 报文段有可能丢失,于是使处于 LAST-ACK 状态的 B 收不到对已发送的 FIN+ACK 报文段的确认。B 就会超时重传这个 FIN+ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的 FIN+ACK报文段。接着 A 重传一次确认,从新启动 2MSL 计时器。最后,A 和 B 都正常进入到 CLOSED 状态。

  第二,防止出现上面提到的“已失效的链接请求报文段”出如今本链接中。A 在发送完最后一个 ACK 报文段,在通过 2MSL,就可使本链接持续的时间内所产生的全部报文段都从网络中消失。


3. 解决 TIME_WAIT 状态引发的 bind失败的方法

  若是在断开链接过程当中,若是是服务器主动断开链接,服务器就会进入 TIME_WAIT 状态,此时想重启服务器就会失败。这种状况是很可怕的,此时就要避免发生这种状况。

#include <sys/socket.h>

int setsockopt(int socket, int level, int option_name,
                           const void *option_value, 
                           size_t option_len);

  使用 setsockopt()函数设置 socket 描述符的选项 SO_REUSEADDR 为 1,表示容许建立端口号相同的多个 socket 描述符。

代码以下:

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));