- 思考
- 方法 当看不懂或理不清文字描述时,先去看看教学视频,再来研读文档
应用层定义消息边界的几种方式
长度边界
实现:应用层在发送消息的时候指定每个消息的固定长度,不足时用固定的字符串填充,当接收方读取消息的时候,每次也截取固定长度的流作为一个消息来解析。
缺陷:应用层发送数据有长度限制,需提前知道消息大小范围,太大出问题,太小填充多不划算
符号边界
应用层在发送消息前和发送消息后标记一个特殊的标记符,当接收方读取消息时,根据特殊的标记符来截取消息的开始和结尾
缺陷:发送的消息内容里面本身就包含用于切分消息的特殊符号,所以在定义消息切分符时候尽量用特殊的符号组合。即一个特殊不够用多个
组合边界
定义一个Header+Body格式,Header消息头里面定义了一个开始标记+一个内容的长度,这个内容长度就是Body的实际长度,Body里面是消息内容,当接收方接收到数据流时,先根据消息头里的特殊标记来区分消息的开始,获取到消息头里面的内容长度描述时,再根据内容长度描述来截取Body部分。">思考 为什么在TCP层没有解决半包、粘包的问题
因为TCP是面向连接的传输协议,TCP传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以TCP也没办法判断哪一段流属于一个消息。而UDP是没有半包、粘包的问题,因为UPD是面向消息的,它有边界协议,可以根据消息的格式区分消息的开始和结尾,UDP和TCP两个发送消息就好像一个用桶运水,一个用水管运水,用水管运水的你是没办法区分那部分的水是属于哪一桶的。
应用层如何解决半包粘包的问题
本质:定义消息边界
应用层定义消息边界的几种方式
长度边界
实现:应用层在发送消息的时候指定每个消息的固定长度,不足时用固定的字符串填充,当接收方读取消息的时候,每次也截取固定长度的流作为一个消息来解析。
缺陷:应用层发送数据有长度限制,需提前知道消息大小范围,太大出问题,太小填充多不划算
符号边界
应用层在发送消息前和发送消息后标记一个特殊的标记符,当接收方读取消息时,根据特殊的标记符来截取消息的开始和结尾
缺陷:发送的消息内容里面本身就包含用于切分消息的特殊符号,所以在定义消息切分符时候尽量用特殊的符号组合。即一个特殊不够用多个
组合边界
定义一个Header+Body格式,Header消息头里面定义了一个开始标记+一个内容的长度,这个内容长度就是Body的实际长度,Body里面是消息内容,当接收方接收到数据流时,先根据消息头里的特殊标记来区分消息的开始,获取到消息头里面的内容长度描述时,再根据内容长度描述来截取Body部分。
思考
- 围绕哪些知识点去展开学习TCP?
- TCP各知识点的前因后果即由来背景、中途问题和后续优化等是什么?
- 如何将TCP各知识点全部串联起来?
VS Code使用
传输层——端口号——端口区分进程
以流的方式进行网络传输
点对点通信——具体到主机上的进程
TCP报文格式(首部+IP数据报)
规律 TCP首部内容都是为了整个TCP的功能进行实现的,不要单纯的为记而记报文格式
思考过程:
- 包含端口号:源和目的——端口号占2bytes即共4bytes=32bits 4bytes
- 序号seq——32bits 4bytes
- 确认序号ack——32bits 4bytes
- TCP报文长度——8bits=1bytes
- TCP三次握手四次挥手——标志位6bytes
- 滑动窗口——16bits 4bytes
- 校验和——32bits 4bytes
以上共20bytes——固定首部
可选项20bytes
明确上述各个字段存在的意义,始终围绕着TCP的特性
TCP特性
面向连接——三次握手+四次挥手
图示
简要文字描述(面试时用自己的话能够清晰地表达出来)
- 客户端发送SYN报文来发起请求
- 服务端收到SYN报文后发送ACK+SYN报文来确认请求和发起请求
- 客户端收到后发送ACK报文表示连接建立
TODO 尝试从三次握手和四次挥手的图中联系出TCP中的大部分问题
问题 长短连接到底针对什么?TCP、HTTP、Socket?
三次握手做了哪些工作即实现了哪些功能
- 协商了滑动窗口大小
- 建立连接
- ?确认序号?
序号seq、确认序号ack是如何设置的
思考 为什么自己对某知识点的描述语言不够精炼,如何去进行修正
seq:TCP第一次握手是随机的,连接后的第一个报文seq是第二次握手seq+1吗?
ack:待接收的请求报文seq+1,即收到seq的请求报文后会返回ack=seq+1的响应报文表示期待下次收到seq+1的请求报文
seq/ack有最大值,但超过最大值后会重新从0/1??开始
TCP状态
TCP状态转换图
在三次握手四次挥手图里就可以知道,特殊状态CLOSING
CLOSING
Linux查看TCP连接状态
netstat -t -a
ss -t
如何实时监控TCP连接状态
watch -n 1 “netstat -at | grep listenPort”
TIME_WAIT出现在主动关闭的那一方,并不是固定的。
出现大量的TIME_WAIT的原因及解决方案
TIME_WAIT是为了确保第四次挥手成功,故除非有其他替代方案,否则不推荐关闭TIME_WAIT
出现大量的原因是:短时间内出现大量的TCP连接建立和断开——短连接
端口占用问题,会造成一定的性能
关联Socket编程
connect发生在TCP三次握手的第1、2次,accept发生在TCP三次握手的第2、3次
正式需要2RTT时间来网络传输,故connect/accept默认是阻塞系统调用
可连接队列和已连接队列(accept队列)
#面试 TCP是如何保证可靠传输的
序列号、确认应答、重传控制、连接管理、窗口控制
#思路 思考TCP传输过程中会出现哪些问题
数据破坏
丢包:重传
重复
分片顺序混乱
且需要考虑请求响应两方向
以下特性均是基于序列号和确认应答来实现的
重传机制
超时重传——快速重传——SACK——D-SACK
什么情况下会造成重传
- 请求报文丢失/时延(重复接收)
- 应答报文丢失/延迟(重复接收)
超时重传
在发送数据时,会设定一个定时器,超过指定时间未收到ACK确认应答报文,会重发;未超时收到应答报文会重置定时器
重传再超时重传时,超时间隔加倍即*2,Linux中为1s、2s、4s、8s、16s、32s
缺陷
超时周期较长即RTO太长
#问题 定时器由谁实现?在哪里实现?存放在哪里?定时时间是多少?如何设置超时时间?
RTT:往返时延(Round-Trip-Time),即请求应答一来一回的时间
RTO:超时重传时间(Retransmission Timeout),即定时器设置的超时时间
很明显,RTO略大于RTT
若RTO < RTT,则基本每个报文都要重发
若RTO > RTT,则网络空闲时间长,降低传输效率,重发间隔太长
网络状态动态变化——RTT动态变化——RTO动态变化
Linux计算RTO方式
- 采样RTT后加权平均——算出平滑RTT
- 采样RTT波动范围
RFC6289中
#通用 网络中的报文多——增加网络负荷拥塞——超时更多——造成更多重传
网络这种资源,需要充分利用,如同CPU一样
吞吐率、网络拥塞、带宽、流量
#问题 网络中会出现哪些情况如堵塞、,造成的原因是什么?
快速重传
解决超时重传的时间等待
不以时间为驱动,以数据驱动重传。
背景:为了多发,不可能总是一请求一应答,而是支持同时发送多个请求报文
连续三次收到相同ack的报文,会重发seq=ack的请求报文
上图可以看出在超时时间内就能进行seq2报文重发
#问题 如何保存ack、seq报文的,即如何知道收到重发的Seq2后会应答ACK6,ACK6如何存放
缺陷:??
SACK
Selective Acknowledgment,选择性确认
需要Client、Server均支持。
Linux下,net.ipv4.tcp_sack设置
Linux2.4+,默认打开
实现:应答报文中TCP头部选项字段中添加SACK(和ACK类似)
ACK < SACK,ACK~SACK-1为需重发报文,如ACK=200,SACK=300,则需重发200-299
#问题 SACK数据格式是?正常接收报文有无SACK,有SACK的值是多少
D-SACK
Duplicate SACK,告诉发送方重复接收数据——应答包丢失/网络延时(该请求重发后又收到了第一请求)
ACK > SACK时,此SACK为D-SACK
Linux下,net.ipv4.tcp_dsack设置
Linux2.4+,默认打开
好处
- 让发送方知道请求包丢失还是应答包丢失
- 发送方确认是否网络延时
- 确认数据包在网络中是否被复制?#疑问
滑动窗口
解决:一请求一应答模式即为每个数据包确认应答,发送数据包的数量取决于RTT,RTT越长吞吐量越低
引入“窗口”,即无需等待确认应答,而可继续发送数据包的最大数量
TCP段:TCP分段
累计应答(累计确认)
背景:控制可接收请求报文的发送数量——网络环境不好,极易重传——恶性循环
窗口大小:在TCP三次握手中进行协商。
TCP首部中的滑动窗口Window
wnd = min(rwnd, )
由接收方窗口大小(可接收的缓存区大小,单位:字节)决定
问题 保存在三次握手中的哪一个报文中?
如何实现
发送方滑动窗口图示
已发送且已确认:即收到最后的ack,已过滑动窗口
已发送但未确认:发送窗口
未发送但可发送:可用窗口
不可发送:还没到滑动窗口
程序实现
3个指针:2个绝对指针(指向特定序列号)+ 1个相对指针(偏移)
SND.WND:发送窗口大小即第2部分+第3部分,有接收方指定
SND.UNA:指向发送窗口的首字节即第2部分第一个字节,绝对指针
SND.NXT:指向可用窗口的首字节即第2部分尾字节后第一个字节(可以是第3部分的首字节也可以是第4部分的首字节),绝对指针
可用窗口大小 = SND.WND - (SND.NXT - SND.UNA)
接收滑动窗口
#1 已接收已确认:等待应用程序进行处理
#2 未接收可接收
#3 未接收且不可接收
程序实现
RCV.WND:接收窗口大小,会告知给发送方
RCV.NXT:指向接收窗口首字节
注意 接收窗口大小约等于发送窗口大小
滑动窗口是动态变化的,当应用程序读取数据速度快,接收窗口越大,并通过TCP首部字段告知发送方——此过程有时延,此时不等
思考 缓存区大小是哪部分,对接收窗口而言,明显#1、#2都在缓存区
思考 窗口大小变动时,字段Window才设值,还是Window一直都设值
Window值一直动态变化
流量控制
方法 当看不懂或理不清文字描述时,先去看看教学视频,再来研读文档
背景
避免发送方一次性大量发送数据包(无法应答——重传——大量数据包,恶性循环)
避免发送方数据填满接收方缓存
发送方根据接收方的实际接收能力控制发送的数据量
客户端、服务器作为发送方、接收方,都有两个窗口
思考 上述的TCP特性是否有冲突的地方,或者说每种机制都分别解决了什么问题
拥塞控制
思考 有了流量控制为什么还要拥塞控制
流量控制避免发送方数据填满接收方缓冲区;拥塞控制避免发送方数据填满整个网络
Neagle算法?
TCP拆包、半包、粘包
拆包:将超过MSS的应用数据包拆分
半包:接收到半个包,如果发送的包的大小比TCP发送缓存的容量大,那么这个数据包就会被分成多个包,通过socket多次发送到服务端,服务端第一次从接受缓存里面获取的数据,实际是整个包的一部分,半包不是说只收到了全包的一半,是说收到了全包的一部分。
粘包:如果发送的包的大小比TCP发送缓存容量小,并且TCP缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从接受缓存就可能一下读取了多个包
粘包发生在发送或接收缓冲区中
产生原因
背景:MSS——一次发送TCP数据包大小存在限制
根本原因:TCP是流动协议,消息无边界。(“TCP数据流、UDP数据包”)
粘包:发送方每次写入数据 < 套接字缓冲区大小,接收方读取套接字缓冲区数据不够及时
半包:发送方写入数据 > 套接字缓冲区大小,还有就是发送的数据大于协议的MTU(Maximum Transmission Unit 最大传输单元)的时候必须拆包。(MTU其实就是TCP协议每层的大小)
#思路 分析:应用A通过网络发送数据向应用B发送消息,大概会经过如下阶段:
阶段一:应用A把流数据发送到TCP发送缓冲区。
阶段二:TCP发送缓冲区把数据发送到达B服务器TCP接收缓冲区。
阶段三:应用B从TCP接收缓冲区读取流数据。
- TCP本身传输数据包大小存在限制,应用数据包过大就会拆包发送
- Neagle算法优化,当应用数据包过小,TCP为了减少网络请求次数的开销,它会等待多个消息包一起,打成一个TCP数据包一次发送出去
- TCP缓冲区里的数据都是字符流,没有明确的边界,没办法指定一个或几个消息一起读,而只能选择一次读取多大的数据流,而这个数据流中就可能包含着某个消息包的一部分数据。
思考 为什么在TCP层没有解决半包、粘包的问题
因为TCP是面向连接的传输协议,TCP传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以TCP也没办法判断哪一段流属于一个消息。而UDP是没有半包、粘包的问题,因为UPD是面向消息的,它有边界协议,可以根据消息的格式区分消息的开始和结尾,UDP和TCP两个发送消息就好像一个用桶运水,一个用水管运水,用水管运水的你是没办法区分那部分的水是属于哪一桶的。
应用层如何解决半包粘包的问题
本质:定义消息边界

应用层定义消息边界的几种方式
长度边界
实现:应用层在发送消息的时候指定每个消息的固定长度,不足时用固定的字符串填充,当接收方读取消息的时候,每次也截取固定长度的流作为一个消息来解析。
缺陷:应用层发送数据有长度限制,需提前知道消息大小范围,太大出问题,太小填充多不划算
符号边界
应用层在发送消息前和发送消息后标记一个特殊的标记符,当接收方读取消息时,根据特殊的标记符来截取消息的开始和结尾
缺陷:发送的消息内容里面本身就包含用于切分消息的特殊符号,所以在定义消息切分符时候尽量用特殊的符号组合。即一个特殊不够用多个
组合边界
定义一个Header+Body格式,Header消息头里面定义了一个开始标记+一个内容的长度,这个内容长度就是Body的实际长度,Body里面是消息内容,当接收方接收到数据流时,先根据消息头里的特殊标记来区分消息的开始,获取到消息头里面的内容长度描述时,再根据内容长度描述来截取Body部分。
为什么要分包,适应IP分包?
长连接和短连接
Wireshark抓包实践
0000、0010、0020框中两个ASCII代表4bytes(如:0e、aa)
目的MAC地址:24bytes
源MAC
面试题
- TCP是如何实现可靠传输的?
面向连接、序号确认、重传机制
拥塞控制与流量控制的区别?
拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。
流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。