TCP-1-传输控制协议

引言

TCP提供一种面向连接的,可靠的字节流服务

image-20211229234957765
  • 面向连接的:指通信通过三次握手建立TCP连接
  • 可靠的:
    • 三次握手建立连接,四次挥手释放连接;
    • ARQ协议(Automatic Repeat-reQuest)即自动重传请求来保证数据传输的正确性;
    • 使用分组窗口和滑动窗口协议来保证接收方能够及时处理接收到的数据,进行流量控制;
    • 使用慢开始、拥塞避免、拥塞发生、快恢复进行拥塞控制
  • 字节流:不在字节流中添加边界标识符

如何唯一确定一个TCP连接?

答:源地址+源端口+目的地址+目的端口,源地址和目的地址的字段(32位)是在IP头部,源端口和目的端口的字段(16位)在TCP头部。

有一个IP的服务器监听一个端口,它的TCP最大连接数是多少?

答:首先,存在文件描述符限制,Socket都是文件,所以通过ulimit配置文件描述符的限制;其次,存在内存限制,每个TCP连接都要占用一定内存,操作系统的内存是有限的

TCP数据结构

源端口(Source Port, 2B = 16bits)和目的端口(Destination Port, 2B = 16bit)加上IP层源IP和目的IP唯一确定一个TCP连接,一个IP地址和一个端口号称为一个插口(socket)。

序列号(Sequence Number, 4B = 32bits)标识了TCP发送端到TCP接收端的数据流的一个字节,该字节代表着包含该序列号的报文段的数据报文段的第一个字节(想象两程序直接一个方向上流动的数据流,TCP给每个字节赋予了一个序列号)。如果TCP报文中flags标志位为SYN,该序列号表示初始化序列号(ISN),此时第一个数据应该是从序列号ISN+1开始(因为SYN标识消耗了一个序号,同理FIN标识也要占用一个序号)。序号是32bits的无符号数,序号到达最大值 2^32 - 1后又从0开始。

初始序列号可被视为一个32位的计数器,该计数器数值每4微妙加1,防止出现与其他连接的序列号重叠

确认序列号(Acknowledgment Number, 4B = 32bits)表示TCP发送确认的一端所期望接受的下一个数据分片的序列号。该序号在TCP分片中Flags标志位为ACK时生效。

TCP为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。

首部长度(Data Offset/Header Length, 4bits)也叫数据偏移,该字段表示 TCP 所传输的数据部分应该从 TCP 包的哪个位置开始计算,可以看作是 TCP 首部的长度。它表示TCP头包含了多少4字节的Words。因为4bits在十进制中能表示的最大值为15,32bits表示4个字节,那么Data Offset的最大可表示15*4=60个字节。所以TCP报头长度最大为60字节。如果options fields为0的话,报文头长度为20个字节。

预留字段(Reserved field, 6bits)值全为零。预留给以后使用。

标志位(Flags, 6bits)表示TCP包特定的连接状态。一个标签位占1bit,从低位到高位值依次为

  • FIN: 表示发送者已经发送完数据。通常用在发送者发送完数据的最后一个包中
  • SYN: 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此,SYN置1就表示这是一个连接请求或连接接受报文。
  • RST: 表示复位TCP连接
  • PSH: 通知接收端处理接收的报文,而不是将报文缓存到buffer中
  • ACK: 只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1
  • URG: 表示紧急指针字段有效

补充的标志还有ECE,CWR占用保留字段2bits

ECE: Explicit Congestion Notification。ECN回显。
CWR: Congestion Window Reduced。拥塞窗口减(发送方降低它的发送速率),CWR 标志与后面的 ECE 标志都用于 IP 首部的 ECN 字段,ECE 标志为 1 时,则通知对方已将拥塞窗口缩小。 详见RFC3168。

窗口大小(Window, 2B = 16bits)表示滑动窗口的大小,用来告诉发送端接收端的buffer space的大小。接收端buffer大小用来控制发送端的发送数据数率,从而达到流量控制。最大值为2^16 = 65535B。若窗口为 0,则表示可以发送窗口探测,以了解最新的窗口大小,但这个数据必须是 1 个字节。

校验和(Checksum, 2B = 16bits)奇偶校验是对整个的 TCP 报文段(包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得)由发送端计算和存储,并由接收端进行验证。

紧急指针(Urgent pointer, 2B = 16bits)只有在 URG 控制位为 1 时有效。该字段的数值表示本报文段中紧急数据的指针。从数据部分的首位到紧急指针所在的位置为止是紧急数据。

选项和填充(Option和pading)可变长度。表示TCP可选选项以及填充位。当选项不足32bits时,填充字段加入额外的0填充。最常见的可选字段:

  • 最长报文大小MSS(Maximum Segment Size)在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,表示本端所能接受的最大报文段的长度(不包含TCP与IP头部)。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍

    1. 最大段大小的均值为1460,加上40字节(TCP头部和IP头部)组成1500字节 最大传输单元典型值
    2. 由于IPv6的头部比IPv4的头部长20字节,最大报文大小也减少20字节
  • TCP选择确认(SACK)选项

  • 窗口缩放选项

  • 时间戳选项和防回绕序列号

  • 用户超时选项

  • 认证选项

数据(Data)长度可变。用来存储上层协议的数据信息。可以为空。比如在连接建立和连接中止时。

TCP三次握手

  1. 第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;

  2. 第二次握手:服务器收到syn包,确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(seq=x+1, ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

第三次握手是可以带数据的,前两次的握手不能带数据

TCP四次挥手

  1. 第一次挥手:客户端进程发出连接释放请求FIN(seq=u),并且停止发送数据。客户端进入FIN-WAIT-1(终止等待1)状态。FIN报文段即使不携带数据,也要消耗一个序号

  2. 第二次挥手:

    • 服务器收到连接释放请求,发出确认报文ACK(ack=u+1),并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。整个TCP处于半关闭状态,即客户端已经没数据需要发送,但服务器若发送数据,客户端依然要接受。

    • 客户端收到服务器的确认请求后ACK后,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

  3. 第三次挥手:

    • 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

    • 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

  4. 第四次挥手:服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

套接字API还提供半关闭操作,使用shutdown()代替close()可以实现TCP半关闭

MSL与MTT的区别?

TCP状态转换

箭头表示因报文段传输、接收以及计时器超时而引发的状态转换

红色箭头表示客户端行为

虚线箭头表示服务器行为

TIME_WAIT状态又称为2MSL等待状态,在此状态TCP会等待2倍的最大段生存期(Maximum Segment Lifetime, MSL)的时间。这样做的目的是:

  1. 让TCP重新发送最终的ACK已避免丢失的情况。重新发送最终的ACK并不是因为TCP重传了ACK,而是因为通信另一方重传了它的FIN。TCP总是重传FIN,直到它收到一个最终的ACK。
  2. 当TCP处于TIME_WAIT状态的时候,通信双方将该连接(四元组)定义为不可重新使用。只有当2MSL等待结束,或一条新链接使用的初始序列号超过了连接之前的实例所使用的最高序列号时,或允许使用时间戳选项来区分之前连接实例的报文段以避免混淆时,这条连接才能重新使用。

当一个连接处于TIME_WAIT状态时,任何延迟到达的报文都会被丢弃。

FIN_WAIT_2状态可以一直保持这个状态,也就是对端一直处于CLOSE_WAIT状态,在Linux中可以通过调整变量net.ipv4.tcp_fin_timeout来设置计时器的秒数(默认60s)

问题解答

  1. 为什么在TCP首部的开始便是源和目的的端口号?

    答:一个ICMP差错报文必须至少返回引起差错的IP数据报中除了IP首部的前8个字节。当TCP收到一个ICMP差错报文时,它需要检查两个端口号以决定差错对应于哪个连接。因此,端口号必须包含在TCP首部的前8个字节里

  2. 首部校验和是怎么计算的?

  3. 为什么连接的时候是三次握手,关闭的时候却是四次握手?

    答:当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,只有等到Server端所有报文都发送完,才能发送FIN报文,因此不能一起发送。

  4. 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

    答:网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间,一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

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

    答:3次握手完成三个重要的功能

    • 三次握手才可以阻止重复历史连接的初始化(主要原因)
    • 三次握手可以同步双方的初始序列号
    • 三次握手才可以避免资源浪费

    两次握手可能存在死锁的情况:计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

  6. 如果已经建立了连接,但是客户端突然出现故障了怎么办?

    答:TCP设有保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

  7. TCP与UDP的区别

    image-20211230001452558

    目标和源端口:告诉UDP协议应该把报文发往哪个进城

    包长度:保存了UDP首部的长度和数据的长度之和

    校验和:校验和是为了提供可靠的UDP首部和数据

    答:如下

    • 连接
      • TCP是面向链接的传输层协议,传输数据前先要建立连接
      • UDP是不需要连接,即刻传输数据
    • 服务对象
      • TCP是一对一的两点服务,即一条连接只有两个端点
      • UDP支持一对一、一对多、多对多的交互方式
    • 可靠性
      • TCP是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达
      • UDP是尽最大努力交付,不保证可靠交付(什么叫尽最大努力??)
    • 拥塞控制、流量控制
      • TCP有拥塞控制和流量控制机制,保证数据的可靠性
      • UDP则没有
    • 首部开销
      • TCP首部长度较长、会有一定的开销,没有使用选项字段是20字节
      • UDP首部只有8个字节,并且固定不变
    • 传输方式
      • TCP是流式传输、没有边界,但保证顺序和可靠
      • UDP是一个包一个包的发送,是有边界的,可能丢失或乱序
    • 分片不同
      • TCP的数据大小如果大于MSS大小,则会在传输层进行分片。中途丢失分片,也只会重传分片
      • UDP的数据大小如果大于MTU大小,则会在IP层分片并在IP层组装。如果中途丢失分片。则需要传输所有,所以UDP包大小最好小于MTU
    • 应用场景
      • TCP面向链接,保证数据可靠。如 FTP文件传输、HTTP/HTTPS
      • UDP面向无连接,可随时发送。如 DNS、SNMP、视频/音频等多媒体通信、广播通信
  8. 为什么UDP头部有包长度 字段,而TCP头部长度则没有

    答:TCP数据的长度 = IP总长度 - IP首部 - TCP头部。UDP为啥不用这个公式,可能是为了保证数据包长度为4字节的整数倍

  9. 如何在Linux系统中查看TCP状态

    答:通过命令 netstat -antp

    image-20211230002245413

参考链接