概述
TCP协议
全称:传输控制协议
TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。TCP提供的是一种可靠的数据流服务,采用”带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为”滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。
UDP协议
全称:用户数据报协议,User Datagram Protocol
UDP是传输层的协议,功能即为在IP的数据报服务之上增加了最基本的服务:复用和分用以及差错检测。UDP提供不可靠服务,具有TCP所没有的优势。
- UDP无连接,时间上不存在建立连接需要的时延。
- UDP没有拥塞控制,能容忍一些数据的丢失,但是不能允许有较大的时延(比如实时视频,直播等)。UDP提供尽最大努力的交付,不保证可靠交付。
- UDP是面向报文的,报文不可分割,是UDP数据报处理的最小单位。
- UDP常用一次性传输比较少量数据的网络应用,如DNS,SNMP等,因为对于这些应用,若是采用TCP,为连接的创建,维护和拆除带来不小的开销。
TCP与UDP区别
TCP报头
报头如下,首部大小固定20字节
- 源端口号/目的端口号: 表示数据从哪个进程来, 到哪个进程去.
- 32位序号:
- 4位首部长度: 标识该TCP头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。
- 6位保留: 顾名思义, 先保留着, 以防万一
- 6位标志位
- URG: 标识紧急指针是否有效
- ACK: 标识确认序号是否有效,我们称携带ACK标识的TCP报文段为确认报文段。
- PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在TCP接收缓冲区中
- RST: 要求重新建立连接。我们把含有RST标识的报文称为复位报文段
- SYN: 请求建立连接。我们把含有SYN标识的报文称为同步报文段
- FIN: 通知对端,本端即将关闭.。我们把含有FIN标识的报文称为结束报文段
- 16位窗口大小:是TCP流量控制的一个手段。这里说的窗口,指的是接收通道窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
- 16位检验和: 由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。
- 16位紧急指针:是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。
- 选项和数据暂时忽略
TCP特性
TCP如何保证传输可靠性
通过校验和 、序列号 、确认应答 、超时重传、连接管理、流量控制、拥塞控制等机制来保证可靠性
一句话:通过 校验和 、 序列号 、 确认应答 、 超时重传 、 连接管理 、 流量控制 、 拥塞控制 等机制
(1)校验和
在数据传输过程中,将发送的数据段都当做一个16位的整数,将这些整数加起来,并且前面的进位不能
丢弃,补在最后,然后取反,得到校验和。
发送方:在发送数据之前计算校验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方
式进行计算,求出校验和,与发送方进行比较。
(2)序列号
TCP 传输时将每个字节的数据都进行了编号,这就是序列号。序列号的作用不仅仅是应答作用,有了序
列号能够将接收到的数据根据序列号进行排序,并且去掉重复的数据。
(3)确认应答
TCP 传输过程中,每次接收方接收到数据后,都会对传输方进行确认应答,也就是发送 ACK 报文,这个
ACK 报文中带有对应的确认序列号,告诉发送方,接收了哪些数据,下一次数据从哪里传。
(4)超时重传
在进行 TCP 传输时,由于存在确认应答与序列号机制,也就是说发送方发送一部分数据后,都会等待接
收方发送的 ACK 报文,并解析 ACK 报文,判断数据是否传输成功。如果发送方发送完数据后,迟迟都没
有接收到接收方传来的 ACK 报文,那么就对刚刚发送的数据进行重发。
(5)连接管理
就是指三次握手、四次挥手的过程。
(6)流量控制
如果发送方的发送速度太快,会导致接收方的接收缓冲区填充满了,这时候继续传输数据,就会造成大
量丢包,进而引起丢包重传等等一系列问题。TCP 支持根据接收端的处理能力来决定发送端的发送速
度,这就是流量控制机制。
具体实现方式:接收端将自己的接收缓冲区大小放入 TCP 首部的『窗口大小』字段中,通过 ACK 通知发
送端。
(7)拥塞控制
TCP 传输过程中一开始就发送大量数据,如果当时网络非常拥堵,可能会造成拥堵加剧。所以 TCP 引入
了 慢启动机制 ,在开始发送数据的时候,先发少量的数据探探路。
TCP如何提高传输效率
TCP 协议提高效率的方式有 滑动窗口 、 快重传 、 延迟应答 、 捎带应答 等。
(1)滑动窗口
如果每一个发送的数据段,都要收到 ACK 应答之后再发送下一个数据段,这样的话我们效率很低,大部
分时间都用在了等待 ACK 应答上了。 为了提高效率我们可以一次发送多条数据,这样就能使等待时间大大减少,从而提高性能。窗口大小指 的是无需等待确认应答而可以继续发送数据的最大值。
(2)快重传
快重传也叫 高速重发控制 。 那么如果出现了丢包,需要进行重传。一般分为两种情况:
- 数据包已经抵达,ACK丢了。这种情况下,部分ACK丢了并不影响,因为可以通过后续的ACK 进行确认
- 数据包直接丢了。发送端会连续收到多个相同的 ACK 确认,发送端立即将对应丢失的数据重传
(3)延迟应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口大小可能比较小。
假设接收端缓冲区为1M,一次收到了512K的数据;如果立刻应答,返回的窗口就是512K;但实际上可能处理端处理速度很快,10ms之内就把512K的数据从缓存区消费掉了;在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;如果接收端稍微等一会在应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;
窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传
输效率。
(4)捎带应答
在延迟应答的基础上,很多情况下,客户端服务器在应用层也是一发一收的。这时候常常采用捎带应答
的方式来提高效率,而ACK响应常常伴随着数据报文共同传输。如:三次握手。
校验和
发送的数据包的二进制相加然后取反,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
计算方式:在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。
- 发送方:在发送数据之前计算检验和,并进行校验和的填充。
- 接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。
注意:如果接收方比对校验和与发送方不一致,那么数据一定传输有误。但是如果接收方比对校验和与发送方一致,数据不一定传输成功。
确认应答+序列号
应用数据被分割成 TCP 认为最适合发送的数据块。 TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 TCP 的接收端会丢弃重复的数据。
- 序列号:TCP传输时将每个字节的数据都进行了编号,这就是序列号。
- 确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。
序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是TCP传输可靠性的保证之一。
超时重传
当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
在进行TCP传输时,由于确认应答与序列号机制,也就是说发送方发送一部分数据后,都会等待接收方发送的ACK报文,并解析ACK报文,判断数据是否传输成功。如果发送方发送完数据后,迟迟没有等到接收方的ACK报文,这该怎么办呢?而没有收到ACK报文的原因可能是什么呢?
首先,发送方没有接收到响应的ACK报文原因可能有两点:
- 数据在传输过程中由于网络原因等直接全体丢包,接收方没有接收到。
- 接收方接收到了响应的数据,但是发送的ACK报文响应却由于网络原因丢包了。
TCP在解决这个问题的时候引入了一个新的机制,叫做超时重传机制。简单理解就是发送方在发送完数据后等待一个时间(里面有一个超时计数器),时间到达没有接收到ACK报文,那么对刚才发送的数据进行重新发送。
- 如果是刚才第一个原因,接收方收到二次重发的数据后,便进行ACK应答。
- 如果是第二个原因,接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送ACK应答。
一般报文超时是怎么确定的?
一刀切的办法就是,我直接把超时时间设成一个固定值,比如说 200ms,但这样肯定是有问题的,我们的电脑和很多服务器都有交互,这些服务器位于天南海北,国内国外,延迟差异巨大,所以设置固定值是很不可靠的,我们要根据网络延迟,动态调整超时时间,延迟越大,超时时间越长。
在这里先引入两个概念:
- RTT(Round Trip Time):往返时延,也就是数据包从发出去到收到对应 ACK 的时间。RTT 是针对连接的,每一个连接都有各自独立的 RTT。
- RTO(Retransmission Time Out):重传超时,也就是前面说的超时时间。
jacobson算法
工作原理是:
- 将每条连接TCP都保持一个变量RTT。
- 当发送一个数据段时,同时启动连接的定时器。
- 如果定时器超时前确认到达,则记录所需的时间,修正RTT的值。
- 如果定时器超时前没有收到确认,则将RTT的值增加一倍。
发送一个报文段,设定的重传时间到了,还没有收到确认。于是重传报文段,经过一段时间后:收到了确认报文段。
现在的问题是:如何判定此报文段是对先发送的报文段的确认,还是对后来重传的报文段的确认?由于重传的报文段和原来的报文段完全一样,所以源主机在接受到确认后,无法做出正确的判断,而正确的判断对确定加权平均RTTs的值关系很大。因此产生了Karn算法,只要报文段重传了,就不采用其往返时间样本
Karn算法
- 报文段每重传一次,就将重传时间增大一些:
- 新的重传时间 = γ×(旧的重传时间)
- 系数 γ 的典型值是2 。
- 当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延 RTT 和重传时间的数值。
实践证明,这种策略较为合理。
SYN报文重传间隔时间
在实际情况下,由于SYN报文是TCP连接的第一个报文,如果该报文在传输的过程中丢弃了,那么发送方则无法测量RTT,也就无法根据RTT来计算RTO。因此,SYN重传的算法就要简单一些,SYN重传时间间隔一般根据系统实现的不同稍有差别,windows系统一般将第一次重传超时设为3秒,以后每次超时重传时间为上一次的2倍
重传次数
在三次握手时,重传次数时可以确定的。
net.ipv4.tcp_syn_retries = 6 //for client, 用于在syn发送阶段net.ipv4.tcp_synack_retries = 5 //for server, 用于在yn-ack发送阶段
ARQ协议
当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层的错误纠正协议之一。它包括停止等待ARQ协议和连续ARQ协议,错误侦测(Error Detection)、正面确认(Positive Acknowledgment)、逾时重传(Retransmission after Timeout)与负面确认继以重传(Negative Acknowledgment and Retransmission)等机制。
流量控制
TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP利用滑动窗口实现流量控制)
接收端在接收到数据后,对其进行处理。如果发送端的发送速度太快,导致接收端的结束缓冲区很快的填充满了。此时如果发送端仍旧发送数据,那么接下来发送的数据都会丢包,继而导致丢包的一系列连锁反应,超时重传什么的。而TCP根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。
在TCP协议的报头信息当中,有一个16位字段的窗口大小。在介绍这个窗口大小时我们知道,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度。如果接收到窗口大小的值为0,那么发送方将停止发送数据。并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端。
注:16位的窗口大小最大能表示65535个字节(64K),但是TCP的窗口大小最大并不是64K。在TCP首部中40个字节的选项中还包含了一个窗口扩大因子M,实际的窗口大小就是16为窗口字段的值左移M位。每移一位,扩大两倍。
可变滑动窗口

流量控制引发的死锁?
当发送者收到了一个窗口为0的应答,发送者便停止发送,等待接收者的下一个应答。但是如果这个窗口不为0的应答在传输过程丢失,发送者一直等待下去,而接收者以为发送者已经收到该应答,等待接收新数据,这样双方就相互等待,从而产生死锁。
为了避免流量控制引发的死锁,TCP使用了持续计时器。每当发送者收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小。若接收者仍然返回零窗口,则重置该计时器继续等待;若窗口不为0,则表示应答报文丢失了,此时重置发送窗口后开始发送,这样就避免了死锁的产生。
拥塞控制
当网络拥塞时,减少数据的发送。 发送方有拥塞窗口,发送数据前比对接收方发过来的接收窗口,取小 如何避免:慢启动、拥塞避免、拥塞发送、快速恢复
应用数据被分割成TCP认为最适合发送的数据块。TCP的接收端会丢弃重复的数据。
TCP传输的过程中,发送端开始发送数据的时候,如果刚开始就发送大量的数据,那么就可能造成一些问题。网络可能在开始的时候就很拥堵,如果给网络中在扔出大量数据,那么这个拥堵就会加剧。拥堵的加剧就会产生大量的丢包,就对大量的超时重传,严重影响传输。
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。 发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
1)慢启动
所以TCP引入了慢启动的机制,在开始发送数据时,先发送少量的数据探路。探清当前的网络状态如何,再决定多大的速度进行传输。发送刚开始定义拥塞窗口为 1,每次收到ACK应答,拥塞窗口加 1。在发送数据之前,首先将拥塞窗口与接收端反馈的窗口大小比对,取较小的值作为实际发送的窗口。
算法流程
- 连接建好的开始先初始化cwnd = 1(窗口),表明可以传一个MSS(最大报文段长度)大小的数据
- 每当收到一个ACK,cwnd++,线性上升
- 每当过了一个RTT,cwnd = cwnd*2, 呈指数上升。
- ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”

2)拥塞避免
拥塞窗口的增长是指数级别的。慢启动的机制只是说明在开始的时候发送的少,发送的慢,但是增长的速度是非常快的。为了控制拥塞窗口的增长,不能使拥塞窗口单纯的加倍,设置一个拥塞窗口的阈值,当拥塞窗口大小超过阈值时,不能再按照指数来增长,而是线性的增长。
- 一般来说ssthresh的值是65535字节(2的16次方),当cwnd达到这个值时后
- 算法流程
- 收到一个ACK时,cwnd = cwnd + 1/cwnd
- 每过一个RTT时,cwnd = cwnd + 1
3)阻塞发生
一旦出现网络拥塞,发生超时重传时,慢启动的阈值会为原来的一半(这里的原来指的是发生网络拥塞时拥塞窗口的大小),同时拥塞窗口重置为 1。
当丢包的时候,有两种情况:
第一种:等到RTO超时,重传数据包,TCP认为该情况太糟糕了
- sshthreash = swnd / 2
- cwnd 重置为1
- 进入慢启动算法

第二种:快速重传算法,即收到3个重复的ACK就开始重传,无需等待RTO超时
- TCP Tahoe(代表版本)的实现和RTO超时一样。
- TCP Reno的实现:
- cwnd = cwnd / 2
- sshthresh = cwnd
- 进入快速恢复算法——Fast Recovery

拥塞控制是TCP在传输时尽可能快的将数据传输,并且避免拥塞造成的一系列问题。是可靠性的保证,同时也是维护了传输的高效性。
4)快重传和快恢复
快重传算法要求首先接收方收到一个失序的报文段后就立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。
接收方成功的接受了发送方发送来的M1、M2并且分别给发送了ACK,现在接收方没有收到M3,而接收到了M4,显然接收方不能确认M4,因为M4是失序的报文段。如果根据可靠性传输原理接收方什么都不做,但是按照快速重传算法,在收到M4、M5等报文段的时候,不断重复的向发送方发送M2的ACK,如果接收方一连收到三个重复的ACK,那么发送方不必等待重传计时器到期,由发送方尽早重传未被确认的报文段。
与快重传配合使用的还有快恢复算法,其过程有以下两个要点:
- 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意:接下去不执行慢开始算法。
- 由于发送方现在认为网络很可能没有发生拥塞,因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
也有的快重传实现是把开始时的拥塞窗口cwnd值再增大一点,即等于 ssthresh + 3 X MSS(最大报文长度) 。这样做的理由是:既然发送方收到三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络 的资源而是停留在接收方的缓存中。可见现在网络中并不是堆积了分组而是减少了三个分组。因此可以适当把拥塞窗口扩大了些。
流量控制和阻塞控制的区别
- 流量控制:流量控制是控制端到端的速率,作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的流量。
- 拥塞控制:拥塞控制是控制全局网络的速率,作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。
举个例子
1.宽带速率1Gb/s,网络只有两台机器,从一台主机传送数据到另一台,这需要流量控制,以保证接收方能正常接收数据。
2.宽带速率1Gb/s,网络中有成千上万台机器,几万台主机发送到另外几万台,这需要拥塞控制,不然网络会瘫痪。
所以折中一下,在连接数较少的情况下可能需要流量控制,配合拥塞控制。
阻塞控制题目
设 TCP 的 ssthresh (慢开始门限)的初始值为 8 (单位为报文段)。当拥塞窗口上升到 12 时网络发生了超时, TCP 使用慢开始和拥塞避免。试分别求出第 1 次到第 15 次传输的各拥塞窗口大小。
| 次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 拥塞窗口大小 | 1 | 2 | 4 | 8 | 9 | 10 | 11 | 12 | 1 | 2 | 4 | 6 | 7 | 8 | 9 |
连接管理(三次握手、四次挥手)
三次握手

- TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
- TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
- TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
- TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
- 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
为什么不能两次握手
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并且没有丢失,只是因为在网络中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时之前滞留的那一次请求连接,因为网络通畅了, 到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
四次挥手

- 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
- 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
- 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
- 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
为什么客户端最后还要等待2MSL?
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
TCP 粘包/拆包
TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
数据包
首先要明确,粘包问题中的 “包”,是指应用层的数据包。在TCP的协议头中,,没有如同UDP一样的 “报文长度” 字段,但是有一个序号字段。
- 站在传输层的角度,TCP是一个一个报文传过来的,按照序号排好序放在缓冲区中。
- 站在应用层的角度,看到的只是一串连续的字节数据。
TCP粘包/分包原因
那么应用程序看到了这一连串的字节数据,就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包。此时数据之间就没有了边界, 就产生了粘包问题
应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象;
进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。
解决方法
- 消息定长:FixedLengthFrameDecoder类
- 包尾增加特殊字符分割:行分隔符类(LineBasedFrameDecoder)或自定义分隔符类 (DelimiterBasedFrameDecoder)
- 将消息分为消息头和消息体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩展头部的拆包与粘包。
UDP“粘包问题”
对于UDP, 如果还没有向上层交付数据, UDP的报文长度仍然存在。同时, UDP是一个一个把数据交付给应用层的, 就有很明确的数据边界.
站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收.不会出现收到 “半个” 的情况.
半连接攻击和全连接攻击
tcp通信是一个面向连接的过程,客户端要和服务端连接,必须进行连接才能进行通信。在tcp连接中,有两种连接攻击方式,是半连接攻击机和全连接攻击。
半连接攻击(syn泛洪)
半连接攻击是一种攻击协议栈的攻击方式,坦白说就是攻击主机的一种攻击方式。通过将主机的资源消耗殆尽,从而导致应用层的程序无资源可用,导致无法运行。
在正常情况下,客户端连接服务端需要通过三次握手,首先客户端构造一个SYN连接数据包发送至服务端,自身进入SYN_SEND状态,当服务端收到客户端的SYN包之后,为其分配内存核心内存,并将其放置在半连接队列中,服务端接收客户SYN包并会向客户端发送一个SYN包和ACK包,此刻服务端进入SYN_RECV态。客户端收到包之后,再次向服务端发送ACK确认包。至此连接建立完成,双方都进入ESTABLSHEDZ状态。半连接就是通过不断地构造客户端的SYN连接数据包发向服务端,等到服务端的半连接队列满的时候,后续的正常用户的连接请求将会被丢弃,从而无法连接到服务端。此为半连接攻击方式。根据服务端的半连接队列的大小,不同主机的抵抗这种SYN攻击的能力也是不一样。
如何来解决半连接攻击?
- 可以通过拓展半连接队列的大小,来进行补救,但缺点是,不能无限制的增加,这样会耗费过多的服务端资源,导致服务端性能地下。这种方式几乎不可取。
- 现主要通syn cookie或者syn中继机制来防范半连接攻,部位半连接分配核心内存的方式来防范。
全连接攻击
全连接攻击是通过消费服务端进程数和连接数,只连接而不进行发送数据的一种攻击方式。当客户端连接到服务端,仅仅只是连接,此时服务端会为每一个连接创建一个进程来处理客户端发送的数据。但是客户端只是连接而不发送数据,此时服务端会一直阻塞在recv或者read的状态,如此一来,多个连接,服务端的每个连接都是出于阻塞状态从而导致服务端的崩溃。
如何来解决全连接攻击?
可以通过不为全连接分配进程处理的方式来防范全连接攻击,具体的情况是当收到数据之后,在为其分配一个处理线程。具体的处理方式在accept返回之前是不分配处理线程的。直到接收相关的数据之后才为之提供一个处理过程。
例如在apache服务中,是通过预创建一定量的子进程作为处理连接继承。所有的自己进程都继承父进程的sockfd,每当有一个连接过来时,只有当accept返回是,才会为该链接分配一个进程来处理连接请求。负责,子进程一直处于等待状态。如果出现值是连接存在,而始终不放数据,该链接的状态是SYN_RECV,在协议栈中,提供一个保活期给该链接,如果超过保活期还没有数据到来,服务端协议栈将会断开该链接。如果没有该保活期,虽然避免了ESTABLESHED状态的数量,但是SYN_RECV的数据量的增长仍旧是不可估算的,所以需要利用保活期来监控该链接是需要清除断开。
