欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TCP协议

程序员文章站 2022-07-01 17:19:04
...

TCP的报头

TCP协议

  1. Source Port,Dest Port:源端口和目的端口,和IP层的源IP和目的IP来唯一确定一个socket。
  2. Sequence Number:包的***,主要用来解决乱序的问题。
  3. Acknowledgement Numbe:就是ACK——用于确认收到,用来解决不丢包的问题,也会用来进行窗口更新。
  4. Window:滑动窗口,通过接受方来控制发送方,达到流量控制的目的。
  5. TCP Flags:TCP的标志比特位。

TCP状态机

TCP是一个面向连接、可靠的字节流协议,其实在网络传输中是没有连接的概念,TCP所谓的连接,提现在它的两端所维护的一个状态机上。

TCP协议

TCP建立过程

TCP协议

三次握手过程,主要是通信的双方要互相通知对方自己的初始化的Sequence Number(缩写为ISN:Inital Sequence Number)——所以叫SYN,全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序。

  • 讨论:为什么是三次握手过程,而不是两次握手?
  1. 首先,必须至少两次才能建立连接。而且最后一个ACK始终有可能丢失,奇数次建立链接和偶数次建立连接是不同的
  2. 讨论为什么不是偶数次,试想如果偶数次发送最后一个ACK,那么此时服务器端建立了连接,客户端还没有建立连接。假设此时客户端下线,那么服务端并不知道,只能保持这个连接,极大地浪费服务端的资源。
  3. 如果是奇数次连接,就不会出现上述情况。所以奇数次握手是为了保护服务端。
  • 连接建立超时
    TCP的三次握手协议等价于两次等待,第一次等待是客户端发送了SYN后,等到ACK和对端SYN报文,第二次等待是服务端发送了ACK和SYN后,等待客户端的ACK。一般来讲,等待就意味着可能超时。
    如果是客户端发送SYN以后的等待超时,那么客户端每隔几秒重发SYN报文,直到经过75秒。
    如果是服务器发送SYN以后等到超时,服务器会以1S,2S,4S,8S,16S,32S为间隔重发,整个过程一共63秒,这对服务器资源是一种极大的浪费。

断连过程

TCP协议

  • 为什么是四次挥手

由于TCP是全双工的,允许在一段关闭发送通道,表示不再有数据的发送,而接受数据的通道是可以继续使用的。

  • socket的优雅关闭和暴力关闭,close和shutdown的区别
  1. close实际做的事是将socket_fd的内部引用计数值减1,如果内部引用计数不用0,则不做任何其他操作;当引用计数为0时,释放掉sock_fd所占资源,关闭对应的TCP连接。
  2. shutdown不管sock_fd的引用计数值是多少,只是单纯的向对方发送一个FIN报文,断开TCP连接。

当调用shutdown时,只是关闭了TCP连接的某个方向,具体方向视参数而定,对于sock_fd本身所占用的资源,不做任何工作。

当调用close时,如果引用计数变为0,那么会释放sock_fd所占用的资源,而TCP连接是sock_fd所占用资源的一部分,所以隐含了TCP的断连过程。

在客户端close一个sock_fd以后,会将这个sock_fd标记为INVALID SOCKET,但是服务器端收到FIN报文后仍然可以通过其所在端的sock_fd发送数据,但是当客户端TCP协议栈收到数据后,发现这是一个无效的socket,因此会返回一个RST报文。

TIME_WAIT状态

TIME_WATI状态主要是产生在TCP连接主动断开方,并且在发送最后一个ACK以后达到这个状态,并且TIME_WAIT会持续2MSL的时间,结束后,处于CLOSED状态。

  • 讨论:TIME_WAIT状态的必要性
  1. 保证最后一个ACK能够到达被动断开方。如果不保持这个状态,假设最后一个ACK丢包,那么超时后被动断开方又发送一个FIN,这是因为连接已经终止,这样不够优雅。
  2. 最重要的原因是,让这个连接所有的数据都在网络中消失。防止产生一个相同四元组的连接,

TCP的数据交互

TCP是可靠的传输协议,那么必须要有重传的机制。TCP的重传机制是基于ACK的。我们知道ACK表示的意思是对已经收到数据的确认,并且还表示接下来想要收到的数据的SEQ。

超时与重传

TCP的基础重传策略是基于定时器的超时机制的,每当TCP发送了一个报文时,会为这个报文设置一个大小为RTO(重传超时时间)的定时器。

通过RTT(一个数据包从发送出去到ACK回来的时间)动态改变RTO的时间。

  • RTT的测量:
    TCP协议

在图(a)中,如果按照第一次发送和收到ACK之间的时间差,那么显然,由于第一次发送的报文的ACK丢失,所以,ACK其实去重传报文的ACK,这个时间差显然太大。如果按照重传和ACK之间的时间差,那么会出现(b)中的情况,又会出现时间差太小的情况。

因此,为了简化RTT的测量:

  1. 忽略重传的报文,只计算正常的报文。
  2. 在一个TCP连接上同时只能有一个计时器在测量RTT。

补充

  1. TCP还有另外的重传机制,例如连续接受三次ACK.
  2. SACK汇报已经接受到的***

流量控制

TCP头里有一个字段叫Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

TCP的滑动窗口协议主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性。同时滑动窗口机制还体现了TCP面向字节流的设计思路。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。

发送窗口:

TCP协议

发送缓冲区一共有4段:已经发送并得到对端ACK的”,“已经发送但还未收到对端ACK的”,“未发送但对端允许发送的”,“未发送且对端不允许发送”。“已经发送但还未收到对端ACK的”和“未发送但对端允许发送的”这两部分数据称之为发送窗口(中间两部分)

发送窗口更新规则:

  1. 当ACK=36且window size = 14,左边沿滑动到35后面,右边沿滑动到49后面。
  2. 当ACK=36且window size = 10,左边沿滑动到35后面,右边沿不动。
  3. 当发送了两个字节后,中间蓝绿区域中间的线移动到47后面。

接受窗口

TCP协议
接收方每次向发送方发送ACK时,都会向发送方汇报己方的接收窗口大小,发送方会根据接收方的窗口大小(实际上会去拥塞窗口和接收窗口的最小值)来控制发送数据的大小,以保证接收方可以及时处理。
TCP协议

接受窗口为0

  1. 坚持定时器
    当接收窗口大小为0时,由于不能接收到任何数据,不能接收数据也就意味着不能发送ACK,那么当服务器处理了部分数据,接收窗口不为0时如何通知发送方。

    发送方会定时使用一个坚持定时器来周期性地向接收方查询,以便发现窗口是否增大,这种报文叫窗口探查,该报文包含一个字节的伪数据,这一个字节的数据是必须的,因为TCP不会对一个不带数据的报文进行ACK确认。接收方收到这个报文后,返回一个ACK,ACK中带有接收窗口大小。

  2. 糊涂窗口综合症
    如果服务器返回的接受窗口只有很小的字节,按照发送原则,只能发送很小的字节。但是我们的TCP/IP头部都有40个字节,这样发送效率太低。所以,接受方要有足够大的空间才通知发送方,而发送方有比较多的数据才发送。

  • 对于接受方
    1. 只有可用空间大于等于一个MSS时才通知发送方。
    2. 接收缓冲区有超过一半为空时才通知发送方。
  • 对于发送方
    1. 要发送的报文长度大于一个MSS才发送。
    2. 可以发送至少是接收方通告窗口大小一般的报文段才发送。
    3. 没有还没被确认的数据,也就是收到之前发送数据的所有ACK。

发送方的策略就是著名的Nagle算法。但是对于一些实时交互应用,Nagle算法需要关闭。

拥塞处理

流量控制依赖于发送端和接收端,和网络中的状态无关。拥塞控制用于在网络状态不好,发生丢包的时候处理。

慢启动(指数增长)

  1. 对于刚建立好的连接,先初始化拥塞窗口cwnd=MSS,即一个报文段的大小,初始化慢启动门限ssthresh,一般为65535,都以字节为单位。
  2. 每收到一个ACK,拥塞窗口就增加一个MSS。
  3. 当cwnd>=ssthresh时,启动拥塞避免算法。

拥塞避免算法(线性增长)

每收到cwnd/MSS个ACK,cwnd增加MSS个字节

拥塞发生时算法

分为两种情况,一为未收到ACK,即超时重传;二为收到3次重复的ACK

  1. 超时重发
    1. ssthredsh = cwnd / 2
    2. cwnd = MSS
    3. 进入慢启动的过程
      TCP协议
  2. 收到3次重复的ACK(说明网络并不是那么糟糕)
    1. cwnd = cwnd / 2
    2. ssthresh = cwnd
    3. 进入快速恢复算法
  • 快速恢复算法
    1. 设置cwnd = cwnd + 3 * MSS,3个MSS对应3个重复的ACK。
    2. 以后每收到一个重复的ACK,cwnd = cwnd + MSS。
    3. 如果收到的是一个新的ACK,那么cwnd = ssthresh,进入拥塞避免算法。
      TCP协议
  • 讨论:快速重传为什么选择三次重复ACK来确定,而不是两次或者四次

重复ACK次数越多,由丢包引起重复的概率就越大,但是次数越多,丢包响应速度就越慢。3次是一种很好的折中。

TCP避免分段

MSS

TCP数据包每次能够传输的最大数据分段。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。

分片

在IP层有一个数据报要进行传输,但是数据的大小比链路上的MTU还要大,那么这是就必须将数据分成若干片进行传输。通常UDP是要进行分片的,因为UDP在传输的时候,本身不会将报文分段,报文长度可能会远远超过链路的MTU,所以UDP的传输依赖IP层的分片进行。

分片的重组

IP数据报进行分片以后,由到达目的端的IP层来进行重新组装,其目的是使分片和重新组装过程对运输层(TCP/UDP)是透明的。由于每一分片都是一个独立的包,当这些数据报的片到达目的端时有可能会失序,但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。

由于IP分片数据再对端重组,但是一旦IP分片丢失任何一片,整个数据包将不能重组,只能被丢弃,所以,一旦TCP报文段进行分片,丢包率会大大上升,导致很多重传问题。所以TCP会尽量在自己的协议层分片。

总结

TCP不适用IP层分片,UDP使用IP层分片。ICMP报文控制MSS的大小。

名词解释

  1. RTT:一个数据包从发送出去到ACK回来的时间
  2. RTO:超时重传时间,根据RTT计算
  3. MSL:TimeWait等待时间
  4. MTU:链路最大传输字节数
  5. MSS:TCP最大传输字节数量,MTU-MSS

API

listen

int listen(int sockfd, int backlog);

Linux对正在建立连接的socket有两个队列,一个是正在建立连接的队列,一个是已经建立连接的队列。

  • 收到SYN,建立未完成的sock,加入正在建立连接的队列
  • 收到ACK,完成上面未完成的sock,加入已经建立连接的队列

backlog和somaxconn的最小值决定了已经建立连接的队列长度。

accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept只是从已建立连接的队列摘下来一个sock结构体,然后建立文件描述符sock关联起来。accept会一直阻塞,直到有一个sock返回

connect

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
  • 调用connect函数会发起三次握手建连过程
  • 通常阻塞直到建连成功或者发生错误
  • 非阻塞connect可以防止多次重发syn,等待75秒

对比

TCP协议