TCP协议
TCP协议
说明
- 如果只想关注计算机网络的应用:
- 了解:TCP特点,功能。
- 知道:TCP通过三次握手,四次挥手建立连接即可。
- 熟悉:应用场景
- 如果想深入学习计算机网络:
- 熟悉:TCP特点,功能,报文格式,应用场景。
- 熟悉:TCP全部功能和机制。
TCP概述
TCP(Transmission Control Protocol,传输控制协议)是互联网核心协议之一,属于传输层,负责在不可靠的网络环境中提供可靠的、面向连接的端到端数据传输服务。
TCP特点(面试题)
- TCP是面向连接的传输层协议。
- TCP是基于字节流传输的。
- TCP提供的是可靠的,无差错的,不丢失,无重复的数据传输服务。
- TCP提供全双工通信。
- 发送缓冲区:存放发送的数据和也发送但还未收到确认的数据。
- 接收缓冲区:存放按序到达的但尚未被接受的应用程序读取的数据和不按序到达的数据。
- TCP协议支持报文的拆分和重装,所以TCP报文较长也是可以的。
TCP首部
- 源端口:发送数据的进程所绑定的端口号。
- 目的端口:接收数控的进程所绑定的端口号。
- 序号:用于标记数据部分第一个字节在原始字节流中的位置。
- 确认号:用于反馈,表示序号在改确认号之前所有的字节都以正确收到。==起始序号是发送方自己设置的,不一定从0开始。==
- 数据偏移:表示TCP首部长度,以4B为单位。
- 保留:暂时没啥用,通常是全部设置为0。==在TCP首部中,并不会有专门记录TCP数据部分的长度(会根据IP地址首部,TCP首部的信息算出来)==
- URG:表示紧急数据,应尽快插队发送。URG=1时。紧急指针有效。
- ACK:表示发送报文的确认。ACK=0时,ack_seq无效;反之有效。
- PSH:PSH=1时,表示希望接收方尽快回复(用于交互式通信)
- PST:RST=1时,表示出现严重错误,必须释放连接。也可用于拒绝一个非法报文段。
- SYN:SYN=1时,表示这是一个连接请求或者连接接受的报文。==只有第一次和第二次握手时,SYN=1,其他情况全为0==
- FIN:FIN=1时,表明此报文的发送方的数据已经发送完毕,要求是否传输连接。==只有第一次和第三次挥手时,FIN=1,其他情况全为0==
- 窗口
(rwnd/rcvwnd)
:16bit,表示接收窗口的大小。即从本报文段首部的ack_seq
算起,接收方还能接受多少数据。 - 校验和:原理和UDP雷同。计算校验和之前也需要加上伪12B首部。
- 紧急指针:紧急数据专用序号。
- 选项:可以为空,可以非空。
- 填充字段:凑足4B整数倍。
注意事项:
- 建立TCP连接时,在第一次和第二次握手的选项中协商
MSS
(最大段长) - MSS的值表示接下来的数据传输中,应该TCP报文段最多携带多少数据。
- 通常MSS的值不会设置的太大,以免在IP层被分片。
TCP功能(面试题)
- 面向连接:三次握手建立连接,四次挥手释放连接
- 可靠传输:
- 确认机制(ACK):如果接收方成功接受报文,返回ACK确认帧,支持累计确认。
- 重传机制:当ACK确认帧丢失或者报文有误时,发送方会重传。
- 序列号和确认号:保证数据按序到达,处理乱序和重复问题。
- 流量控制:通过滑动窗口机制动态调整发送速率。
- 拥塞控制:使用慢启动、拥塞避免、快速重传和快速恢复算法实现。
面向连接(面试题)
大致流程
三次握手
- 客户端进程A向服务端进程B进行第一次握手:
第一次握手之前,服务端进程一直处于等待客户端连接状态,监听(LISTEN)状态。
1 | SYN = 1 ACK = 0 FIN = 0 seq = 666 ack = 无效 |
SYN=1
:表示这是一个连接请求。ACK=0
:客户端和服务器之前没有对话,所以ACK为0。FIN=0
:只有4次挥手释放连接时才置1,其他时候置0。seq=666
:值是客户端进程自己决定的。ack=无效
:ACK为0时,ack无效。
第一次握手之后,客户端进程进入同步发送(SYS-SENT)状态。
- 服务端进程B向客户端进程A进行第二次握手:
客户端收到服务器响应之后,客户端进程进入连接建立(ESTABLISHED)状态。
1 | SYN = 1 ACK = 1 FIN = 0 seq = 50 ack = 667 |
SYN=1
:表示这是一个连接请求。ACK=1
:表示对客户端进程发送报文的确认。FIN=0
:只有4次挥手释放连接时才置1,其他时候置0。seq=50
:值是服务端进程B自己决定的。ack=667
:第一次握手消耗一个序号,所以客户端进程发送的667号及以前的所有数据段全部正确收到
第二次握手之后,服务端进程进入同步接收(SYS-RECV)状态。
- 客户端进程A向服务端进程B进行第三次握手:
1 | SYN = 0 ACK = 1 FIN = 0 seq = 667 ack = 51 |
SYN=0
:只有三次握手的前两次SYN位才置1,其他时候置0。ACK=1
:表示对服务端进程发送报文的确认。FIN=0
:只有4次挥手释放连接时才置1,其他时候置0。seq=667
:客户端进程继续发送667号的报文段。ack=51
:第二次握手消耗一个序号,表示对服务端进程发送的51号及以前的所有报文全部正确收到。
第三次握手之后,服务端进程进入连接建立ESTABLISHED
状态。
自此TCP三次握手结束,客户端进程A和服务端进程B建立连接。双方可以进行全双工通信!
注意事项
- 第一次和第二次握手都不可以携带数据(只有TCP首部),但是都要消耗一个序号(seq)
第三次握手可以携带数据也可以不携带数据
- 如果不携带数据:那么不消耗序号(seq)
- 如果携带数据:那么消耗序号(seq)。
建立连接的耗时分析
四次挥手
- 客户端进程A主动向服务器进程B进行第一次挥手:
第一次挥手之后,客户端进程进入终止等待1FIN-WAIT1
状态。
1 | SYN = 0 ACK = 1 FIN = 1 seq = 9999 ack = 5600 |
SYN=0
:只有三次握手的前两次SYN位才置1,其他时候置0。ACK=1
:表示对服务端进程发送报文的确认。FIN=1
:只有4次挥手的第一步和第三步才置1,其他时候置0。seq=9999
:客户端进程继续发送9999号的报文段。ack=5600
:表示对服务端进程发送的5600号及以前的所有报文全部正确收到。
- 服务器进程B响应客户端进程A进行第二次挥手:
第一次挥手之后,服务端进程进入关闭等待CLOES-WAIT
状态。
1 | SYN = 0 ACK = 1 FIN = 0 seq = 5600 ack = 10000 |
SYN=0
:只有三次握手的前两次SYN位才置1,其他时候置0。ACK=1
:表示对客户端进程发送报文的确认。FIN=0
:只有4次挥手的第一步和第三步才置1,其他时候置0。seq=5600
:服务端进程继续发送5600号的报文段。ack=10000
:第一次挥手消耗了一个seq序号,所以客户端进程发送的10000号及以前的所有报文全部正确收到。
之后服务端可以继续向客户端发送信息,而客户端则不能发送。
假设此时服务端又向客户端发送了2200个报文段的数据。
- 服务端进程B向客户端进程A进行第三次挥手:
第二次挥手之后,客户端进程进入终止等待2FIN-WAIT2
状态。
1 | SYN = 0 ACK = 1 FIN = 1 seq = 7800 ack = 10000 |
SYN=0
:只有三次握手的前两次SYN位才置1,其他时候置0。ACK=1
:表示对服务端进程发送报文的确认。FIN=1
:只有4次挥手的第一步和第三步才置1,其他时候置0。seq=7800
:服务端进程继续发送7800号的报文段。ack=10000
:表示对客户端端进程发送的10000号及以前的所有报文全部正确收到。因为这段时间内客户端没有发送任何数据,所以ack序号不变
第三次挥手之后,服务端进程进入最后确认LAST-CHECK
状态。
- 客户端进程A向服务端进程B进行第四次挥手:
1 | SYN = 0 ACK = 1 FIN = 0 seq = 10000 ack = 7801 |
SYN=0
:只有三次握手的前两次SYN位才置1,其他时候置0。ACK=1
:表示对服务端进程发送报文的确认。FIN=0
:只有4次挥手的第一步和第三步才置1,其他时候置0。seq=10000
:服务端进程继续发送10000号的报文段。ack=7801
:第三次挥手消耗了一个seq序号,表示对客户端进程发送的7801号及以前的所有报文全部正确收到。
补充
- 客户端进程进入到TIME-WAIT状态,立刻启动TIME-WAIT定时器,倒计时2MSL之后才能进入CLOSE状态。==(如果收到了服务端发来重复的挥手,就重置定时器,防止客户端退出之后,依然占用服务端连接,导致服务端资源浪费)==
- MSL:最长报文段寿命,是由TCP协议规定的一个固定时间长度。
- 如果服务端进程收到一次挥手时已经没有数据可以发送的了,那么就是可以连续进行第二次、第三次挥手。
注意事项
- 第一次和第三次挥手不携带数据,也要消耗一个序号。
- 第二次挥手可以携带数据
- 第四次挥手不能携带数据!
释放连接的耗时分析
可靠传输
我们假设:
- 客户端的发送和接收缓存区均为10B
- 服务端的发送和接收缓冲区均为8B
接收窗口
累计确认
累计确认:收到多个连续的报文段,只返回一个ACK。
捎带确认
捎带确认:客户端返回确认帧的同时正好有数据发送。
超时重传
超时重传:每发出一个报文,就设置超时重传定时器,如果在定时器超时之前没有收到数据,那么就重传改数据段。
第一种情况:客户端发送的数据丢失:
第二种情况:服务端发送的确认帧丢失:
快重传机制、立即确认机制
快重传:如果接受方收到3个确认帧号相同的冗余ACK,那么就立即重传对应的报文段。
立即确认:每收到一个报文段就确认,即使是失序报文段,也要立刻返回ACK。
要点
可靠传输
- 确认机制
- 累计确认原则:如果收到ack_seq=n表示,在序号n之前的全部报文段均已正确接收。
- 返回ACK时机:推迟确认
- 推迟时间不能超过0.5s(TCP标准规定)
- 如果自己也有数据发送给对方,立即返回ACK段,并捎带自己要发送的数据。
- 如果连续收到两个长度为MSS的报文段,就立即返回ACK段。
- 两种ACK段
- 专门确认(不严谨术语):一个ACK段。
- 捎带确认:应该ACK段顺道携带了数据,就是捎带确认。
- 重传机制
- 超时重传:没发出一个报文段,就设置一个定时器。若定时器到期还没有收到确认,就重传这一报文段,并重置定时器。
- 快重传
- 作用:让可能出错的报文段尽早重传,而不是非要等到超时再重传。
- 立即确认:
- 每收到一个TCP报文段,就返回一个ACK段。
- 即使收到一个失序的报文段,也要立即返回ACK段(失序报文段会导致冗余ACK)
- 当发送方连续收到三个确认号相同的冗余ACK时,就立即重传该确认号对应的报文段。
流量控制(滑动窗口)
- 接收方维持一个接收窗口
- 接收窗口不能大于接受缓冲区大小
- 在接收窗口内的是接收方还允许接收的序号范围。
- 发送方维持一个发送窗口
- 发送窗口不能大于发送缓冲区大小
- 发送窗口不能大于接受窗口大小
- 在发送窗口内的是已发送但尚未收到确认的数据和可以发送但未发送的数据。
拥塞控制(面试题)
如何控制网络阻塞:
- 发出的每个报文段,都能顺利的收到ACK确认————==不拥塞==
- 发出的报文段未能按时收到ACK,引发超时重传————==严重阻塞==
- 收到冗余ACK,引发快重传————==有点拥塞==
检测到网络拥塞怎么办?
- 迅速减少发送的数据量
- 严重阻塞就迅速缩小拥塞窗口
- 有点拥塞就适当缩小拥塞窗口
慢开始、拥塞避免算法
==注:传输轮次也可以是RTT==
- 慢开始算法:
cwnd
值从1
开始,每收到一个ACK
,就让cwnd+1
(当cwnd<ssthreess
时适用) - 拥塞避免算法:在一个
RTT
内,即使收到多个ACK
,也只能让cwnd+1
(当cwnd>=ssthress
时适用)
快重传,快恢复
- 快重传:当发送方收到三个确认号相同的冗余
ACK
时,立即重传对应报文段。 - 快恢复算法:一旦发送快重传,就将阈值、
cwnd
都设为当前cwnd
的一半,然后切换为拥塞避免算法。
应用场景
TCP协议应用场景
可靠性要求高的场景:
- 网页浏览(HTTP/HTTPS)
- 文件传输(FTP)
- 电子邮件(SMTP/POP3)
- 远程登录(SSH)
TCP首部URG紧急指针的应用
- 远程中断操作:例如通过SSH执行Ctrl+C终止远程进程时,发送方会设置URG标志,确保中断指令被优先处理。
- 实时控制场景:工业控制系统中需要立即响应关键指令的场景。
- 局限性:现代协议栈较少直接使用URG,因紧急数据通常通过应用层协议(如HTTP/2优先级流)实现。
TCP首部PSH推送的应用
- 交互式通信:如Telnet、SSH等场景中,用户输入的每个字符需立即传输,避免输入延迟。
- 流量控制辅助:在接收方窗口即将耗尽时,PSH标志可触发应用层尽快读取数据以释放缓冲区。
- 默认行为:TCP协议栈通常自动设置PSH标志(如发送最后一个报文段时),无需开发者显式调用。
TCP首部RST复位的应用
- 处理半打开连接:当一方意外断开(如服务器重启),另一方发送数据时会触发RST,强制释放连接。
- 防御攻击:防火墙可通过发送RST阻断恶意连接尝试。
- 开发调试:在Socket编程中,调用close()时若接收缓冲区仍有未读数据,可能触发RST(需设置SO_LINGER选项)。