#TCP
简介
- 面向连接的、可靠的、基于字节流的传输层协议。
- 将应用层的数据流分割成数据段,发送到目标节点的传输层
- 每一个都有一个序号,目标收到则发送ACK确认,未收到则重传。
套接字:IP地址+协议+端口号作为一个网络进程的表示
一. 连接的建立与释放
传输单位:报文段
报文首部格式
-
第1-2字节:源端口号
-
第3-4字节:目的端口号
源端口号+源ip+目的端口号+目的ip地址唯一确定一个tcp链接
- 第5-8字节:32位序号
编号为了解决传输乱序的问题
- 第9-12字节:32位确认序号
解决包丢失的问题
- 第13-14字节:前4位为首部长度,随后6为为保留位,在之后6位为标识位。控制报文各种状态。
首部长度:表示首部到第一个有效数据的长度。1代表首部长度为是4个字节长,一般的报文长度为20个字节长,因此首部长度范围为5(20字节)-15(60字节)
标识位:
1. URG:配合紧急指针使用,当为1时表示该报文需要紧急处理,不进入队列排队,此时紧急指针有效。
2. ACK:确认序号标志,当为1时表明确认序号有效,0时忽略
3. PSH:接收到数据后尽快提交给应用层,不放入缓存区
4. RST:重置连接标志
5. SYN:同步序号,用于建立连接
6. FIN:关闭标志,用于释放连接
6. 第15-16字节:滑动窗口大小
用于控制发送端、接收端缓存大小,以此来控制发送端发送数据的速率,达到流量控制的目的
- 第17-18字节:校验和
发送端计算存储,接收端验证
## 还不明白校验和如何进行,需要进一步学习- 第19-20字节:紧急指针是一个正偏移量,和序号中的值相加,指出紧急数据最后一个字节的序号,使得接收方知道紧急数据有多长
MSS最长报文大小
最常见的可选字段,只在SYN传送过来时出现,指明本端能接收的最大报文大小。如果不发送默认为536字节
三次握手
- 客户端发送请求连接报文给服务端,将TCP标识位中的同步序号SYN置为1,并随机产生一个序号j发送给服务端,客户端进入SYN_SENT的状态
- 服务端收到报文后,由SYN=1知道客户端请求建立连接,则将回复的数据包SYN、ACK置为1,并随机产生需要seq=k,同时ack序号为j+1此时服务器进入SYN_RCVD
- 客户端收到确认后,检查ack是否为j+1,ACK是否为1,如果正确则将数据包ACK置为1,seq=j+1,ack=k+1并发送数据包给服务端,此时客户端进入ESTABLISHED状态。服务端收到数据包后确认ACK是否为1ack是否为k+1。如果正确则连接建立成功,服务端进入ESTABLISHED状态
为何时三次握手
- tcp为全双工通道,要确保双方均能接收和发送数据
- 初始化序号的初始值,双方在通信时才能正常发送和接收报文。
SYN包超时
服务端收到了SYN请求,没有收到ACK包,Linux默认等待63秒后断开连接
针对SYN Flood的防护措施
SYN队列满了以后,Linux给予tcp-syncookies参数回发SYN Cookies的功能,若为正常连接,客户端会回发SYN Cookie,双方建立连接。
建立连接后,客户端故障怎么办?
tcp设有保活机制,向客户端发送保活探测报文,如果未收到响应继续发送,尝试达到保活探测次数后断开连接。
四次握手
- 客户端发送FIN m 并进入FIN_WAIT_1
- 服务端收到FIN m 进入CLOSE_WAIT状态,发送ack m+1
- 服务端发送FIN n 并进入LAST_ACK
- 客户端收到FIN n进入TIME_WAIT状态(等待2MSL后进入CLOSED状态),发送ack n+1 ACK 1,服务端收到后进入CLOSED状态
为什么是四次握手
双方均能发送信息,支持半关闭,一方结束了发送仍能接收对方的数据,所以需要确保双方均关闭
三次握手和四次握手状态说明
- 三次握手
- 首先服务端处于Listen状态
- 客户端主动发送syn请求进入SYN_SENT状态
- 服务端接收到syn请求进入SYN-RCVD状态
- 客户端收到syn和ack后进入ESTABLISHED状态
- 服务端收到ack后进入ESTABLISHED状态
- 四次握手
- 客户端主动发送fin请求,进入FIN_WAIT_1状态
- 服务端收到fin请求进入CLOSE_WAIT状态
- 客户端收到ack进入FIN_WAIT_2状态
- 服务端发送fin请求进入LAST_ACK状态
- 客户端收到fin进入TIME_WAIT状态
- 服务端收到ack进入CLOSED状态
FAQ
TIME_WAIT状态
也被称为2MSL等待状态,MSL=Maximum Segment Lifetime,报文最大生存时间,根据不同的tcp实现自行设定, linux一般为30s。 主动关闭一方发送最后一个ack所处的状态,这个状态必须位置2MSL等待时间。
为何TIME_WIAT状态需要等待2MSL
四次握手时,主动关闭方发送的最后一个ack包丢失了,需要等待足够长的时间来让服务器重新发送fin包并接受到该包。 不然会导致该连接与后续的连接混淆。
TIME_WAIT状态等待2MSL会导致的问题
可能会导致socket连接过多
同时发送请求连接
同时发送syn请求,打开一条连接,而不是两条
同时关闭
双方同时主动发送fin请求
二. TCP数据的传输
tcp传输数据的分类
- 成块数据传输
- 交互数据传输
交互数据传输
- 经受时延的确认 收到数据时并不是马上发送ack确认,而是与其他地点一致的数据一起发送。接受到ack时表示正确收到了一直到ack-1序号的所有字节 延时时间一般在200ms左右,不会超过500ms
- Nagle算法 解决了过多的微小分组导致拥堵的问题 要求tcp上只能有一个未被确认的未完成的分组,该分组到达之前不能发送其他分组。发送端收集这些分组,确认到来之后以一个分组的形式发送出去。
成块数据传输
主要使用滑动窗口协议
滑动窗口协议
为何需要滑动窗口协议?
发送端和接收端发送和接收的速率不匹配,导致大量数据包丢失。
如何解决?
接收方告诉发送发目前缓存区大小,发送方根据接收方接收能力来发送数据。
协议使用的数据结构
一.发送端
1. LastByteAcked:成功发送且收到确认数据包的位置
2. LastByteSent:成功发送但还未收到确认数据包的位置
3. NotSent:可以发送但还未发送的数据(接收方还有空间)
4. LastByteRead:应用正在处理的位置或者接收方空间不足
二.接收端
1. LastByteRead:缓存区读取到的位置
2. NextByteExpected:收到的连续数据包的最后一个位置
3. LastByteRcvd:收到的最后一个数据包的位置
4. 2-3空白处:还未收到的数据包
滑动窗口示意图
黑框表示滑动窗口
1. 表示发送了并且收到ack确认的数据
2. 表示发送了还没收到ack的数据
3. 表示在窗口中还没有发出的(接收方还有空间)
4. 窗口以外的数据(接收方没空间)
当收到ack后窗口向前滑动对应的位置,重复该过程,达到控制流量的目的。
拥塞窗口
为何需要拥塞窗口
当发送方发送速率过快,造成网络堵塞
如何解决?
发送方使用一个拥塞窗口,滑动窗口大小取拥塞窗口和接收端发送来的窗口大小中的最小值,拥塞窗口大小根据当前网络拥塞程度来决定。
零窗口
1. 为何会发生?
接收端处理过慢,发送端发送过快,导致窗口大小变为0
2. 如何解决
发送端定期发送探测报文,获取窗口大小
超时与重传
如何解决
设置定时器,时间到了还没收到确认就重传
tcp管理的定时器类型
- 重传定时器:等待收到确认
- 坚持定时器:保持窗口大小不断更新
- 保活定时器:检测空闲连接
- 2MSL定时器:检测TIME_WAIT状态
超时重传机制
为何会有各种重传机制?
接收端发送的ack是连续收到的最后一个数据包的序号+1,如果中途有包丢失,那么发送端应该如何应对?
被动等待的超时重传策略
不做任何处理,等待发送方超时,然后重传
缺点:发送端不知道重传多少个数据包
主动的快速重传机制
如果收到的包不为连续的包,每接受到一个这样的包就发送一次连续最后的包的ack,当发送方收到3次相同的ack则重传该包,不等待超时
优点:解决了每次都要等待超时的问题
缺点:发送端不知道重传多少个数据包
SACK
基于快速重传,在tcp头部添加SACK参数,记录一个数值范围,表示哪些数据已经被接收到了
Duplicate SACK
基于SACK策略,多记录一项有哪些数据被重复接收了,告诉发送端是发送的包丢失了还是ACK包丢失了
坚持定时器
为何需要坚持定时器?
当窗口大小为0时,接收方会发送一个没有数据,只有窗口大小的数据,如果该包ack丢失,双方可能因为等待而中断连接,此时使用坚持定时器周期性的向接收方询问窗口大小,这些报文被称为窗口探查
启动时机
当发送窗口大小为0时
与超时重传的区别
不放弃发送,直到窗口被打开或者进程被关闭。而超时重传在一定次数后便放弃发送
保活定时器
为何需要保活定时器?
当tcp上没有数据传输时,服务器需要直到客户端是否还存在