文章目录
TCP协议段格式
-
源/目的端口号:从哪个进程来到哪个进程去;
-
32位序号/32位确认序号:分别代表TCP报文当中每个字节数据的编号以及对对方的确认,是TCP保证可靠性的重要字段;
-
4位TCP报头长度:表示该TCP报头的长度,以4字节为单位;
-
6个标志位:
-
- URG: 紧急指针是否有效;
-
- ACK: 确认号是否有效;
-
- PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走;
-
- RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段;
-
- SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段;
-
- FIN: 通知对方, 本端要关闭了,我们把携带FIN标识的报文称为结束报文段;
-
16位窗口大小:保证TCP可靠性机制和效率提升机制的重要字段;
-
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分;
-
16位紧急指针: 标识哪部分数据是紧急数据
确认应答(ACK)机制
确认应答机制依靠的就是报头中的32位序号/32位确认序号;
确认应答机制并不是保证双方全部信息的可靠性,而是依靠收到对端的应答来确认,自己上次发给对端的信息被可靠的收到;
确认应答机制是保证TCP可靠性的机制之一。
TCP将每个字节的数据都进行了编号. 即为序列号
TCP是面相字节流的,将TCP的发送缓冲区和接收缓冲区都想象成一个字符数组(但此时的数组和其他数组不同的是他的起始下标从0开始)
在通信的时候,本质就是将数据从发送方的缓冲区拷贝到接受方的缓冲区;
发送方发送数据时报头当中所填的序号,就是发送的数据当中,首个字节数据在发送缓冲区当中对应的下标;
接收方接收到数据进行响应时,响应报头中的确认序号是接收缓冲区中接收到的最后一个有效数据的下一个位置所对应的下标;
每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发
超时重传机制
通信时如果A向B发出的消息,在一定时间内如果A没有收到B的回复,那么A就会重新发送消息;
丢包的两种情况:
- 发送的报文丢失,B没有收到,无法响应;
- B收到报文,作出响应,响应报文丢包了,无法响应;
因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.去重的效果. 这时候我们可以利用前面提到的序列号
- 当出现丢包时,发送方是无法辨别是发送的数据报文丢失了,还是对方发来的响应报文丢失了,因为这两种情况下发送方都收不到对方发来的响应报文,此时发送方就只能进行超时重传;
- 如果是对方的响应报文丢失而导致发送方进行超时重传,此时接收方就会再次收到一个重复的报文数据,但此时也不用担心,接收方可以根据报头当中的32位序号来判断曾经是否收到过这个报文,从而达到报文去重的目的;
- 需要注意的是,当发送缓冲区当中的数据被发送出去后,操作系统不会立即将该数据从发送缓冲区当中删除或覆盖,而会让其保留在发送缓冲区当中,以免需要进行超时重传,直到收到该数据的响应报文后,发送缓冲区中的这部分数据才可以被删除或覆盖;
超时的时间如何确定?
- 超时时间设置过长,会导致丢包后对方长时间收不到对应的数据,会影响整体的重传效率;
- 超时时间设置过短,会导致对方收到大量的重复报文,并且发送大量重复报文会也是对网络资源的浪费;
最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回"
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间
以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。重发之后仍得不到应答,则变为2 * 500ms、4 * 500ms 、… 、2^n * 500ms;
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
三次握手
双方在进行TCP通信之前需要先建立连接,建立连接的这个过程我们称之为三次握手。
TCP连接过程:
客户端向服务器发送一个有SYN的报文(表示客服端想和服务器建立链接);
当服务器接受到该报文时,会向客户端发送一个带有SYN+ACK的报文(其中SYN表示服务器想和客户端建立链接,ACK表示服务器收到了客户端的请求);
当客户端接收到该报文时,会再给服务端发送一个ACK, 发送此ACK后, 客户端就已经建立好了连接;同理, 服务器收到此ACK后也就建立好的连接
为什么是三次握手?
首先我们需要知道,连接建立不是百分之百能成功的,通信双方在进行三次握手时,其中前两次握手能够保证被对方收到,因为前两次握手都有对应的下一次握手对其进行响应,但第三次握手是没有对应的响应报文的,如果第三次握手时客户端发送的ACK报文丢失了,那么连接建立就会失败。
虽然客户端发起第三次握手后就完成了三次握手,但服务器却没有收到客户端发来的第三次握手,此时服务器端就不会建立对应的连接。所以建立连接时不管采用几次握手,最后一次握手的可靠性都是不能保证的。
既然连接的建立都不是百分之百成功的,因此建立连接时具体采用几次握手的依据,实际是看几次握手时的优点更多。
三次握手是验证双方通信信道的最小次数:
- 因为TCP是全双工通信的,因此连接建立的核心要务实际是,验证双方的通信信道是否是连通的,而三次握手恰好是验证双方通信信道的最小次数,通过三次握手后双方就都能知道自己和对方是否都能够正常发送和接收数据。
- 在客户端看来,当它收到服务器发来第二次握手时,说明自己发出的第一次握手被对方可靠的收到了,证明自己能发以及服务器能收,同时当自己收到服务器发来的第二次握手时,也就证明服务器能发以及自己能收,此时就证明自己和服务器都是能发能收的。
- 在服务器看来,当它收到客户端发来第一次握手时,证明客户端能发以及自己能收,而当它收到客户端发来的第三次握手时,说明自己发出的第二次握手被对方可靠的收到了,也就证明自己能发以及客户端能收,此时就证明自己和客户端都是能发能收的。
- 三次握手就足够建立连接,五次,七次也能建立连接,但是三次是最优解!
三次握手能够保证连接建立时的异常连接挂在客户端:
- 当客户端收到服务器发来的第二次握手时,客户端就已经证明双方通信信道是连通的了,因此当客户端发出第三次握手后,这个连接就已经在客户端建立了。
- 而只有当服务器收到客户端发来的第三次握手后,服务器才知道双方通信信道是连通的,此时在服务器端才会建立对应的连接。
- 因此双方在进行三次握手建立连接时,双方建立连接的时间点是不一样的。如果客户端最后发出的第三次握手丢包了,此时在服务器端就不会建立对应的连接,而在客户端就需要短暂的维护一个异常的连接。
- 而维护连接是需要时间成本和空间成本的,因此三次握手还有一个好处就是能够保证连接建立异常时,这个异常连接是挂在客户端的,而不会影响到服务器。
- 虽然此时客户端也需要短暂维护这个异常,但客户端的异常连接不会特别多,不像服务器,一旦多个客户端建立连接时都建立失败了,此时服务器端就需要耗费大量资源来维护这些异常连接。
四次挥手
四次挥手的目的是为了断开连接. TCP的六位标记位中有一个叫
FIN
的标记位, 它代表要和对端断开连接.
TCP断开过程:
客户端会先给服务器发送一个带有FIN标志位的报文,表示请求与服务器断开连接;
服务器收到此请求后会给客户端返回一个ACK,将没发完的数据发给客户端;
服务器收到客户端断开连接的请求,且已经没有数据需要发送给客户端的时候,会给客户端发送FIN,标识我的数据已经给你发完,可以关闭连接了;
客户端收到断开连接请求后给服务器发送一个ACK就断开连接了;
理解TIME_WAIT状态
连接断开后,会维持一段时间的TIME_WAIT状态,在此期间,不能重新在同样的端口启动服务
为什么是TIME_WAIT的时间是2MSL?
滑动窗口
对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.
但有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候.
此时,滑动窗口机制就能使其一次性发送多条数据
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段).
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
- 窗口越大, 则网络的吞吐率就越高;
滑动窗口中存在着两个指针会随着对端发过来的确认序号进行左右移动;
TCP的重传机制要求暂时保存发出但未收到确认的数据,而这部分数据实际就位于滑动窗口当中,只有滑动窗口左侧的数据才是可以被覆盖或删除的,因为没有收到对端的ACK,所以肯定也没收到对端发来的确认序号, 只要收不到确认序号, 滑动窗口就不会向右滑动(不会丢失数据)
流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应;
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control)
- 接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK通知发送端。
- 窗口大小字段越大,说明网络的吞吐量越高。
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。
- 发送端接收到这个窗口之后,就会减慢自己发送的速度。
- 如果接收端缓冲区满了,就会将窗口值设置为0,这时发送方不再发送数据,但需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。
第一次向对方发送数据时如何得知对方的窗口大小?
TCP通信之前需要先进行三次握手建立连接,TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息
16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?
理论上确实是这样的,但实际上TCP报头当中40字节的选项字段中包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位得到的。
拥塞控制
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
- 此处引入一个概念程为拥塞窗口
- 发送开始的时候, 定义拥塞窗口大小为1;
- 每次收到一个ACK应答, 拥塞窗口加1;
- 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.
- 为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.
- 此处引入一个叫做慢启动的阈值
- 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
延迟应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M
延迟应答的目的不是为了保证可靠性,而是留出一点时间让接收缓冲区中的数据尽可能被上层应用层消费掉,此时在进行ACK响应的时候报告的窗口大小就可以更大,从而增大网络吞吐量,进而提高数据的传输效率。
此外,不是所有的数据包都可以延迟应答。
- 数量限制:每个N个包就应答一次。
- 时间限制:超过最大延迟时间就应答一次(这个时间不会导致误超时重传)。
物极必反,秒回也不是啥好事!
捎带应答
捎带应答其实是TCP通信时最常规的一种方式,比如在TCP三次握手时,客户端向服务器发送SYN(向服务器请求建立连接);服务器在响应回复时,发送ACK应答时顺便发送一个SYN(向客户端请求建立连接);此时,服务器既收到客户端数据的响应,又向客户端发送了数据,这就叫做捎带应答。
TCP协议这么复杂就是因为TCP既要保证可靠性,同时又尽可能的提高性能。
可靠性:
- 检验和
- 序列号
- 确认应答
- 超时重传
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
TCP的这些机制有些能够通过TCP报头体现出来的,但还有一些是通过代码逻辑体现出来的。
综上,TCP比UDP复杂很多,有很多机制来保证可靠性和提高性能;
TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景
UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播