理解TCP序列号(Sequence Number)和确认号(Acknowledgment Number)
TCP的基本认识
什么是TCP
TCP位于传输层,是面向连接的、可靠的、基于字节流的传输层通信协议。
面向连接:
TCP是1对1进行连接,不像HTTP3中的UDP一样,一对多
可靠的:
一旦进行了连接,无论网络链路发生什么变化,TCP都能保证报文一定送达
基于字节流
无论消息多大都可以进行传输,因为转化了二进制形式,且是有顺序的,当前一个报文消息没有收到,即使收到了后面的报文字节,也不能送到应用层去处理,对重复的报文进行抛弃处理。
TCP用来保证可靠性和流量控制维护着某些信息
这些信息是:Socket、序列号和窗口大小
所以建立一个TCP连接是客户端与服务器 达成 Socket、序列号、窗口大小的共识
Socket:由IP地址和端口号组成
序列号:用来解决乱序问题
窗口大小:用来进行流量控制。
TCP的头部格式
源
源端口:16位
序列号:32位
确认应答号:32位
首部————长度为4位 保留6位
校验和——16位
选项长度可变
数据
目标
目标端口号:16位
序列号:32位
确认应答号:32位
窗口大小:16位
紧急指针:16位
选项
数据
序列号: 32位,用来标识TCP源端口向目标端口发生的字节流,用来确认身份
确认应答号: 指下一次期待收到的数据的序列号,发送端收到了这个应答之后,可以认为这个序号之前的数据都正常接收、
控制位:
ACK=1: 确认连接,当ACK=1是才有效,ACK=0是此控制位无效。
SYN=1:表示希望建立连接。
FIN=1:断开连接,提出断开的这一方为1
RST=1:出现异常,需要断开连接,再重新进行连接
URG=1:紧急指针有效,即有紧急数据报文,需要优先传送,不用按照原来的顺序
PSH=1:接受到该控制位的一方需要立即传送数据报文。
如何确定一个TCP连接
源地址、源端口、目标地址、目标端口
地址号是32位 存在于IP的头部,作用:通过IP协议发生报文给对方主机
端口号:16位 存在于TCP头部,作用:告诉TCP协议应该把报文发送给哪个进程
最大TCP连接数是多少
最大TCP连接数=客户端IP数*客户端端口数
如何计算TCP的数据长度
TCP数据长度=IP总长度-IP首部长度=TCP首部长度
TCP和HTTP3中UDP有什么不同
TCP:面向对象连接,传输数据前需要先连接
UDP:不需要连接,即可传输
服务对象
TCP:1对1 一条连接 两个端口
UDP:支持1对1 多对多 1对多的通信
可靠性
TCP:可靠传输数据,数据可以无差错、不丢失、不重复按需到达
UDP:尽量保证送达数据
拥塞控制、流量控制
TCP:有进行拥塞控制和控制流量的机制、保证数据传输的安全——--字节流传输等。。。。。。。待完善
UDP:不会发生网络阻塞——————为什么?
首部开销
TCP:首部字节较长,没有选项时是20字节,选了更高
UDP:首部较短 固定在8字节
传输方式
TCP:Stream传输,没有边界、保证了顺序和可靠性 最大值 MSS 最大报文长度 ——Maximum Segment Size
UDP:一个包一个包传输,可能发生丢包 MTU 最大传输单元 ————Maximum Transmit Unit
分片方式
TCP: 如果数据大小超过 MSS(最大报文长度),就要在传输层进行分片处理,
如果发生丢包,后续传输丢失的包即可
UDP:如果数据大小超过 MTU(最大传输单元),会在IP层进行分片,但如果丢失了数据包,就需要重新传输所有的数据包
应用场景
根据可靠性、面向性(对象 和 无面向)、时效性划分
TCP适合文本传输、文件传输 ——HTTP、FTP
UDP适合 视频音频 广播等多媒体
TCP的连接——3次握手
比较重要的字段:
序列号: seq 号——32位
源端口 向目标端口发生的字节流,用来确认身份
确认号 ack号——32位
标志位 SYN ACK UGR PSH RST FIN
⼀开始,客户端和服务端都处于 CLOSED 状态。
先是服务端主动监听某个端⼝,处于 LISTEN 状态
即握手之前服务器和客户端都属于CLOSED阶段
随后服务器处于LISEN阶段,之后可以开始三次握手
第一次握手
客户端向服务端发送一段TCP报文
标记位SYN=1表示请求建立连接
发送自己的序列号Seq——此序号位于TCP首部的序号字段中。
此段报文不能含有应用层数据,客户端发送之后属于 SYN-SENT(已发送建立表达的意思)
第二次握手
服务端收到客户端的报文请求,
服务器初始化自己的序列号,——填入TCP首部的序号字段
同时在客户端的序列号上+1作为确认应答号
接着 SYN 和 ACK标记为1 ——意思为愿意建立连接 和建立连接
本次报文也不含应用层数据;发送之后 服务端属于 SYN-RCVD状态
第三次握手
客户端收到服务端报文后
将服务端的序列号+1填入确认应答号,将标志位ACK改为1,本次发送可以携带应用层数据
客户端进入 ESTABLISHED 转态
服务端再次接受到后也进入ESTABLISHED状态
为什么要三次握手才能连接? 为什么不是4次?
1避免重复连接
在数据传输过程中,可能会发生丢包 如数据包90延迟了,
而后面的数据包91-99正常传输,
后面数据包90赶上来了,到达服务器,
服务器没能识别出这个确认应答号,便返回一个SYN和ACK(包含序列号)给客户端
客户端认为此时的包应该是100,而不是90,所以没有给与服务器反应
等到了后面的数据包 序列号为100时 再传送给 服务端便开始了新的传送
三次握手可避免同一请求的数据包发生网络阻塞时出现重复建立
两次握手会导致这种情况发生。
那么这个丢失的数据如何处理呢????待解决
2同步双方的初始序列号
当客户端给服务端一个初始序列号 client_is
服务端会返回 client_is+1
后面每次返回 都会+1 即有了连续性。
当然 四次握手也可以进行维护一个 序列号
但是 正常的第二次握手就可以返回一个 客户端序列号+1 和 服务端的序列号
三次握手避免资源浪费
3避免资源浪费
如果客户端发送SYN——建立请求的消息后,发生了延迟
如果延迟多次
SYN。。。
SYN。。。
如果没有第三次握手 即成功一次后 ACK标记为1 ,
那么服务器收到的一直是SYN=1 那么会建立多次连接 浪费资源。
TCP 建⽴连接时,通过三次握⼿能防⽌历史连接的建⽴,能减少双⽅不必要的资源开销,能帮助双⽅同步初始化序
列号。序列号能够保证数据包不᯿复、不丢弃和按序传输
「两次握⼿」:⽆法防⽌历史连接的建⽴,会造成双⽅资源的浪费,也⽆法可靠的同步双⽅序列号;
「四次握⼿」:三次握⼿就已经理论上最少可靠连接建⽴,所以不需要使⽤更多的通信次数
为什么每次都初始化序列号
每次建⽴连接前᯿新初始化⼀个序列号主要是为了通信双⽅能够根据序号将不属于本连接的报⽂段丢弃。
,防⽌⿊客伪造的相同序列号的 TCP 报⽂被对⽅接收
既然IP层会分片,为什么TCP层还需MSS呢?
MTU——最大传输单元
MSS——最大报文传输
如果IP层的数据大于MTU,数据就要分片处理(此时数据包还是很大),
如果发生丢包,后续TCP层就要重新进行连接发送所有数据。——而IP层的传输是分片的效率低,重传会导致原本的传输更慢
在TCP层进行MSS分片。到IP层数据包长度小于MTU,无需在IP层分片
当TCP发生丢包后,只需重传丢失的部分,无需全部传输,增大了传输的效率。
MTU分片之后只有第一个分片才有TCP头部,所以丢失之后需要重传
MSS分片每个片都有TCP头部,所以丢失之后 只需部分重传
TCP MSS分⽚如果丢失了⼀⽚,发送⽅没收到对⽅ACK应答,也是会触发超时传送的,因为TCP层是会保证
数据的可靠交付
如何避免SYN攻击
即攻击者仿造多次 SYN请求——第一次握手,在得到服务器回应后,客户端不开始第三次握手,会导致服务器一直主动回应,使得占据大量的资源,久了系统就不能正常服务。
避免SYN攻击1:
修改Linux的内核参数:
当接收的参数大于 处理的内核速度时,用一个队列保存这些数据包
如设限————SYN_RCDV状态连接数量
超出处理时,直接丢出RST 断开连接。
避免SYN攻击2:
设置一个syncookie=1
当syn队列满了 ,服务器不再接收syn包。
TCP的断开——4次挥手
第一次挥手c–>s
客户端打算关闭连接,发送一个TCP首部报文为FIN=1的报文
之后客户端处于FIN_WAIT_1状态
第二次挥手s–>c
服务端收到了客户端的FIN=1 的报文后,向客户端发送TCP首部报文为ACK=1的报文
之后服务器处于 CLOSED_WAIT状态
客户端收到了服务器的 ACK=1的报文后,进入了FIN_WAIT_2状态
第三次挥手s–c
服务端处理完数据后,向客户端发送一个FIN报文,进入LAST_ACK状态
第四次挥手c–s
客户端收到服务端的FIN报文后,回了一个 ACK应答报文,之后进入了TIME_WAIT状态
服务器收到了ACK之后进入CLOSED状态
客户端经过2MSL之后也进入了CLOSED状态。
TCP关闭连接特点:
1每个方向都有一个FIN=1 和 ACK=1
2主动关闭连接才有TIME_WAIT的状态
为什么四次握手
应为客户端发送FIN=1 时表明客户端不再发送数据了
而服务器收到之后 返回一个ACK=1 进入 CLOSED_WAIT状态,表明 还有可能还有数据需要处理和发送
(客户端收到之后进入FIN_WAIT2转态 此时应该也可以接收服务端的数据包)
所以 服务端的ACK=1 和FIN=1 一般都会分开发送。
为什么客户端从TIME_WAIT–>CLOSED需要2MSL
MSL:最大报文生存时间
任何报文超过这个时间都将被丢弃。
TTL:IP数据报可以经过的最大路由数
MLS>=TTL
⽹络中可能存在来⾃发送⽅的数据包,当这些发送⽅的数据包
被接收⽅处理后⼜会向对⽅发送响应,所以⼀来⼀回需要等待 2 倍的时间。
在Linux中2MSL默认60秒,MSL=30秒
如果需要修改MSL值 就要修改Linux中的TCP_TIMEWAIT_LEN的值 并重新编译。
为什么要有TIME_WAIt状态
首先主动的一方才有TIME_WAIT 所以一般为客户端
TIME_WAIT是在服务端同意关闭之后 客户端(关闭一方进入的状态)
防止连接旧的数据包
1如果TIME_WAIT过短或者没有
可能会让客户端接收因网络延迟的数据,造成数据错乱等严重问题。
所以 TIME_WAIT的时间为2MSL,足以让两个方向上延迟的数据都消失,再次出现的数据都是新建立连接产生的。
保证双方正常关闭连接
1如果连接时间过短或没有
如果TIME_WAIT过短,恰好 ACK报文发生丢失,导致服务器无法正常进入CLOSEED状态
下次客户端建立新的请求发生SYN时,服务器处于LAST_ACK中会发送一个RST ,当前的连接会被终止。
2如果TIME_WAIT时间过长
a正常关闭连接
b服务端没有收到ACK,所以会再次发送一个FIN标记。
TIME_WAIT过多有什么危害
客户端TIME_WAIT过多会占据端口资源,被沾满有无法建立新的连接。
在多线程过长中,线程池无法处理大量为断开的连接。
如果是服务端主动关闭,然后TIME_WAIT过多会发生啥?
服务器可以建立很多链接,对于每个连接只监听一个端口,
但是会把连接交给线程处理,TIMEWAIT过多会导致线程无法处理
但不会影响其端口
如何优化TIME_WAIT
如果已经建立了连接客户端发送故障怎么办
TCP 有⼀个机制是保活机制
定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作⽤,每隔⼀个时间间
隔,发送⼀个**探测报⽂**,该探测报⽂包含的数据⾮常少,如果连续⼏个探测报⽂都没有得到响应,则认为当前的
TCP 连接已经死亡,系统内核将错误信息通知给上层应⽤程序。
如果TCP连接正常,那么TCP的保活时间初始化,直到下一个保活机制或者TCP有了连接获得
如果对端程序可以响应,但没有连接有效的信息,就会产生一个RST报文。
如果其他原因导致报文无法传达,连续几次保活机制都无效,那么这个TCP连接死亡。
当客户端 timewait 时间超过了 2MSL,则客户端就直接进⼊关闭状态。
Socket编程
IO流读写 打开 关闭
建立连接 三次握手
关闭连接 断开连接
TCP重传、滑动窗口、流量控制、拥塞控制。
重传机制
超时传送
当客户端发生一个数据之后吗,在指定时间内没有收到对方的ACK,就重新发送该数据。
需要重传的两种情况:
数据包丢失
确认应答丢失
时间相关词
RTT——往返时延:客户端发送数据包到收到服务器的ACK为止
ROT——超时传送时间。
当ROT较长时,就会发生丢失的数据包需要长时间才能送达
当ROT较短时,会发生不必要的重传,导致网络阻塞
所以ROT的时间应该略大于报文往返的RTT时间
如何选取ROT时间大小很关键:
如果发生了一次重传那么下次重传的时间将设定为 先前值的两倍。
快速重传
快速重传的⼯作⽅式是当收到三个相同的 ACK 报⽂时,会在定时器过期之前,重传丢失的报⽂段。
即当发送方收到三个或以上的冗余ACK(duplicate ACK)
为什么是三次呢? 因为在发生丢包的情况下必定会发生三个冗余ACK重传
SACK——快速重传
在TCP头部加入SCAK,在发送方收到了三次ACK确认报文,会快速触发快速重发机制,通过SACK信息获取具体数据包。——但是这种情况下 也可能是发回的ACK丢失了
即可以快速知道哪些包丢失了
Duplicate-SACK
滑动窗口
**不加入滑动窗口:**客户端数据包发送之后,收到服务端的ACK响应才能继续发送数据——导致通信效率低
加入滑动窗口:
窗口大小由发送端决定:用于告诉客户端,自己可以接受处理多大的数据量
发送方的数据大小不能超过接收端窗口大小
客户端的滑动窗口可以包含多个连续的TCP报文
当收到ACK响应之后,窗口滑动,以接受新的报文
中间没有收到ACK的,也不影响其他TCP报文发送。
尾部 未发送 但在接收端处理范围之内的
服务端:
已经收到并反馈ACK
未收到 可以处理
未收到不能处理
流量控制
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量 win 来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。
它的大小是接收端的缓存能力的衡量。
窗口未0可能发送死锁现象:
当接收端窗口处理的数据满了,就会给发送端发送一个窗口为0的通知
当处理完之后,再告诉发送端窗口恢复了——但如果这个通知丢失
那么发送端会一直等待接受端的消息,接收端也会等待发送端新的数据
解决:
TCP支持计时器:
如果收到了窗口为0
启动计时器
超过时间 就会将那些窗口探测
如果窗口依旧是0 那么重新启动计时器等待下一次
如果不是0 死锁局面被打破
窗口糊涂症
即 接收端处理数据慢,然后待处理的数据大,发送给发送端的窗口比较小
然后发送端又快速发送
导致接收端可用窗口越小
拥塞控制
流量控制是避免发送方的数据填满接收端的缓存。
那么拥塞控制可以避免传送过程 发生网络拥堵。
拥塞窗口cwnd
发送窗口swnd
接收窗口rwnd
拥塞窗口=min(发送窗口,接收窗口)
如何认定发生了网络拥塞?
ACK没有按时到达 即超过了ROT超时传送时间
拥塞控制主要有哪几种方法?
慢启动——指数增长
慢启动门限
拥塞避免——指数变线性
当拥塞窗口大于慢启动门限时,拥塞窗口每次只增加 1/cwnd
但是拥塞窗口还是在增加,导致了丢包——所以进入了拥塞发生
拥塞发生——重传数据包
超时传送——的拥塞发生
慢启动门限变为 拥塞窗口的一半
拥塞窗口恢复到1 所以数据传输突然变慢
快速传输——的拥塞发生
TCP认为这种阻塞不严重 所以
拥塞窗口变一半
慢启动门限 等于拥塞窗口
进入快速恢复