TCP协议
TCP协议
- 面向连接、可靠的字节流传输协议
- 全双工 可相互传递字节流
- 只可用于 一对一通信 ,不能用于多播和组播
- TCP 使用校验和,确认和重传机制来保证可靠传输
- TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制
- TCP使用
(源地址,源端口,目的地址,目的端口)
来标识一个连接
TCP报文
源端口和目的端口
端口大小为16
位,可见端口范围为:0~2^16
也就是[0~65536]
IP层则用ip地址来标识一台主机,TCP层用端口来标识一个应用,使用(源地址,源端口,目的地址,目的端口)
唯一确定一个连接,所以对于IPV4的话单台主机最大的连接数为 2^(32+16+32+16)
个
序号
每个TCP包都有一个序号,编号是为了解决TCP包乱序问题,给每个包编上序号就知道了每个包的顺序,这样就可以完整的拼好所有的TCP包了而不造成乱序,TCP给个字节都有一个序号,序号是整个TCP包数据段的第一个字节的序号,比如现在一个包的起始序号为101
,数据长度为500byte
,那么最后一个字节的序号就为600
,这个TCP包的序号为第一个字节的序号101
,所以下一个TCP包的第一个字节的序号应该为601
,也就是下一个TCP包的序号为601
如果序号达到最大值2^32,则回卷到0
由于初始的seq号是随机生成的,所以TCP回绕到0之后怎么继续保持字节序呢?怎么判断字节序呢?
回绕之后遇到相同的seq号则可以根据TCP头部携带的时间戳来判断前后
确认号
设置TCP包的确认号是为了告诉对方上一个数据包已经确认收到了,你可以继续发送下一个TCP包了,这个确认号填的就是期望对方发送的下一个TCP包的 序号
比如B收到了A发的序号为101
的TCP包,该包长度为500byte
,B现在收到了A发的600byte
之前的数据,期望A发送下一个序号为601
的TCP包,所以确认号为601
A收到B的回复的确认号为601
得知B已经收到了A发的600byte
之前的数据,现在要发下一个序号为601
的TCP包了
首部长度
长度为4
位
指出TCP首部的大小,因为TCB首部中有可选择字段,所以每个TCB首部都是不定长的,所以需要指定首部长度,单位是4字节
,最大为2^4
,所以首部最大长度为16*4byte=60byte
保留位
6
位,保留以后使用,目前全部置0
五大标志位
6
位
标志 | 含义 |
---|---|
URG | 紧急位,为1 的话则表示这是个紧急的TCP包,应该放到发送队列的最前面去立即发送 |
ACK | ACK=1 表示确认号有效,ACK=0 表示确认号无效,TCP连接建立成功后所有的传输报文必须把ACK置为1 |
PSH | 很少用到,一般为0 |
RST | 复位 RST=1 表示TCP连接出错,必须释放连接重新建立 |
SYN | 同步 SYN=1 表示这是一个请求建立连接或接受建立连接请求报文 SYN=1 ACK=0 表示这是一个建立连接请求,SYN=1 ACK=1 表示这是一个应答建立连接请求的TCP报文,详情请参考TCP三次握手 |
FIN | 用来释放连接, FIN=1 表示数据已经发送完毕并且请求释放TCP连接 |
窗口大小
TCP使用 滑动窗口 来实现拥塞控制和流量控制,详情请见下文,比如B响应给A的报文中窗口字段的大小为1000
,确认号为101
目的就是为了告诉A可以发送101
开始的数据包了,缓存空间大小为1000
byte,也就是还可以接受101~1101
字节范围的数据包
发送双方都会建立一个窗口来提醒对方自己能接受的最大数据量,展示自己的处理能力
窗口明确指出现在允许对方发送的数据量,窗口大小经常动态变化以此来进行拥塞控制和流量控制
检验和
目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃
紧急指针
URG=1
才有意义,TODO (不理解)
可选项
<=40byte
,TCP可选的头部字段,比较常用的有如下:
MSS
Maximum Segment Size
最大报文段长度
MSS
指的是不包括TCP头部的最大数据段长度,如果应用层数据超过MMS就需要分多个TCP包进行传输
UDP则没有这个限制,直接将应用层传递下来的所有数据封装为一个UDP包传递给IP层
但是IP层还是要根据MTU
大小将传输层的数据包来进行分片传输的,因为以太网中规定MAC数据帧的数据大小不能超过MTU,所以IP报文大小必须小于MTU,如果超过了这个值则IP数据包就会进行分片传输到目的地再根据IP报头进行组装
MSS
是在TCP握手阶段进行确立的,双方在三次握手包中都会发送自己的MSS,然后以后通信就会按照对方的MSS大小来发送TCP包给对方
MSS大小的选择必须是要不大不小,并且需要参照MTU
,如果太大了则IP层会进行分片传输容易造成IP分片而造成IP包的丢失进而引发TCP层的重传影响效率,所以一般MSS大小选择和MTU
大小接近,这样每个TCP包刚好能被一个IP包传输
如果TCP传输的数据太小则利用率不高,比如数据只传递1byte
却要携带好几十byte的头部信息,所以MSS也不是越小越好
TimeStamps 时间戳
会记录TCP包发送的时间和ACK包回复的时间戳
用于计算TCP报文往返时间RTT
,A发送数据带上时间戳,B接受到数据之后将时间戳复制到确认包,并且也加上当前时间戳表示回显的时间,然后发给A,这样A收到ACK报文之后就可以根据时间戳来计算出TCP包 发送+确认
的往返时间间隔了
RTT的变化在一定程度上反映了网络拥塞程度的变化
并且时间戳还可以用于防止 Sequence序列号回滚、TCP包重负 ,可以通过判断时间戳来识别两个TCP包的先后顺序,如果发生重复则丢弃距离当前最远的那个TCP包,只接受最近传过来的TCP包
总结时间戳解决了两个问题:
- 计算RTT值,判断网络拥塞程度
- 解决TCP序列号回滚问题
为什么TCP握手是三次
3次握手
注意,握手阶段是没有数据的,所以ack=x+1
ack=y+1
,后面连接的时候发送确认包这时的ack=x+<data-size>
那么为什么需要 三次握手,而不是 N次握手、2次握手呢?
- 第一次 (
SYN,seq=x
) [A->B]
第一次是由A发起连接请求,表示A希望和B建立连接
- 第二次 (
SYN,ACK,seq=y,ack=x+1
) [B->A]
A发出去需要知道B是否收到、包有没有丢、B是否同意建立连接 所以A需要等待B的ACK包,也就是需要第二次握手
如果B收到了A的连接请求并且同意建立连接则需要回复一个ACK包
- 第三次 (
ACK,seq=x+1,ack=y+1
) [A->B]
因为B回复的ACK包也可能会丢,所以B也需要等待A的 应答之应答 ,A回复之后B就知道A收到了B的ACK,此刻便可认为连接已经建立
三次握手之后,AB双方都经历了 一发一收,这样就可以基本判断双方都同意建立连接了,而不是永无休止的握手下去
2次握手的情况
一般人和人打招呼都是一来一回就可以了,但是网络上的情况比较复杂
A发送连接请求包给B,如果A没有收到B的ACK包,那么有下面两种情况:
- 包丢了
- B不愿意建立连接而不回复
上面两种情况A都会重试,直到超时则会放弃建立连接
如果B愿意建立连接则回复一个ACK确认包给A,对于B来说这个ACK包有下面两种情况:
- ACK包丢了 A不认为B收到了自己的连接请求,所以会重复发送SYN包
- A挂了
那么B发送ACK报包之后就不能认为连接已经建立了,所以B也需要等A的 应答之应答
如果B没有收到A的应答则不会建立连接,会继续发送ACK包直到自己收到应答
这样双方都经历了 一收一发 则可以认为已经建立连接了,则不需要再确认下去了,再确认下去都是多余的
其次如果AB已经建立过连接了并且断开了,但是A因为之前发送了很多建立连接的请求后来又陆续到了B,那么B也会认为A还要建立则会回复ACK,那么此刻并不能认为是A还要建立连接,所以需要A继续回复一个给B才能确定建立连接,如果A不回复则表示这是A之前的连接已经建立过了,这种情况下A是不会回复的,也就不会建立错误的连接
3次握手则可以避免这样错误的连接请求
握手请求的seq号
TCP握手连接请求的seq号是随机生成的,随机生成的目的是防伪造防黑客,比如A发给B的连接请求seq=x
,B回复A的ack应该为ack=x+1
表示收到了A的SYN包
同理B回复A的ACK包的seq也是随机生成的seq=y
,A收到之后返回 应答之应答 包的 ack=y+1
表示收到了B的应答
建立连接之后,双方就继续使用握手产生的seq号和ack号进行通讯,第一个包的ack号还是最后一次与对方握手时的ack号,之后的ack号就必须遵循数据偏移来了,所以握手的目的之一就是 协商seq号
为什么seq号要随机生成而不是从0开始?
如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击,发送一些其它内容到目标主机
TCP三次握手目标
TCP三次握手除了建立连接之外,还有如下几个目的:
- 协商初始的
seq
号,这个号是随机产生的 - 交换TCP参数,比如
MSS
值、指定双方的校验算法、窗口比例因子等
SYN攻击和连接队列
理解SYN攻击之前我们需要知道 全连接队列和半连接队列 ,Linux会为每个端口应用创建一个 半连接队列和全连接队列,每个客户端来连接这个端口的时候都会先进入半连接队列,然后再进入全连接队列
如果队列满了则会丢弃或则发送RST包要求进行重新连接
1、半连接队列
客户端发送SYN包,服务器收到SYN包并且回复SYN+ACK包的时候就会将这个连接放入半连接队列,表示等待建立连接,半连接队列大小可以在 /proc/sys/net/ipv4/cp_max_syn_backlog
中配置
2、全连接队列
客户端回复了第三次握手之后双方就会进入 ESTAB
状态,Linux服务器就会将这个连接从半连接队列里面取出来放入全连接队列,然后应用就可以到这个队列去取连接进行处理了
全连接队列溢出的策略需要在 /proc/sys/net/ipv4/tcp_abort_on_overflow
下配置
0
:(默认配置)如果全连接队列满了,那么 server 扔掉 client 发过来的 ack ;1
:如果全连接队列满了,server 发送一个reset
包给 client,表示废掉这个握手过程和这个连接
全连接队列大小可以在/proc/sys/net/core/somaxconn
下配置
3、SYN Flood攻击
因为客户端在发送SYN
请求建立连接的时候,服务器回复了SYN-ACK
之后还必须等待客户端回复 应答之应答 ,但是如果客户端此刻挂了则服务器会重试,重试次数可以配置,并且下一次重试间隔时间会延长,直到到达重试上限之后服务器就会主动关闭TCP连接将客户端此刻的从 半连接队列 移出
因此如果有人恶意伪造不存在的客户端IP地址或端口发送SYN
给服务器,那么服务器就永远无法收到应答,这种恶意的SYN
包太多了则会造成操作系统的半连接队列满了,那么就无法再接收其它正常的SYN连接请求了
3、相关的Linux命令
# -l 显示正在Listener 的socket
# -n 不解析服务名称
# -t 只显示tcp
# Recv-Q 完成三次握手并等待服务端 accept() 的 TCP 全连接总数,
# Send-Q 全连接队列大小
ss -ntl
netstat -s #查看网络统计结果
4、那么什么是SYN攻击呢
所谓的 SYN攻击 就是攻击者伪造一些不存在的IP向服务器发起SYN请求连接,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪
这是典型的 DOS攻击
5、预防SYN攻击有一下几种方法
- 缩短超时(SYN Timeout)时间
- 增加半连接队列容量
- 过滤请求
为什么TCP挥手是四次
为什么TCP断开连接需要进行四次挥手呢?
假如现在A请求断开连接,表示A已经没有数据要发送给B了,B会回复一个ACK包表示B收到了A请求断开的FIN包,但是B的数据可能还没有发完,B还不能断开
所以A必须等待B发送FIN表示B也没有数据要发送了这样才能算双方都没有数据要发送了
如果B要发的数据发完了,同时发送了FIN包告诉A我的数据已经发完了,我也要断开了,同时A也必须回复一个ACK包表示收到了B的FIN包
所以这里就必须进行4次挥手断开连接
- 第一次 (
FIN,seq=x
) [A->B]
A发送给B表示自己要断开了,注意此处的seq=x
也是随机生成的,ack
还是上次数据传输报文的ack号
- 第二次 (
seq=y,ack=x+1
) [B->A]
B回复A表示收到了A的断开请求,这里的seq=y
也是随机生成的,其中ack=x+1
表示收到了断开请求,B回复之后可能还有数据要发
- 第三次 (
FIN,seq=z,ack=x+1
) [B->A]
B发送给A表示自己数据也传输完成了,这里的 ack=x+1
还是上面的ack号,因为在此期间A已经请求断开连接了不会发送数据了,seq=z
是随机生成的
- 第四次 (
FIN,seq=x+1,ack=z+1
) [A->B]
A回复B的断开连接请求,表示A收到了B的断开连接,这里的seq=x+1
就是B发送FIN包的ack号,ack=z+1
注意,A发送完回复之后还不能马上断开,还需要等一段时间,这个时间是2MSL
,会启动一个定时器,如果在TIME_WAIT
期间又都到了B发来的FIN则会继续回复ACK包然后重置定时器
如果在2MSL
一直没有收到则表示可以关闭连接了
为什么需要TIME_WAIT
TCP四次挥手的发起方需要在最后等待2MSL
时间,这就是著名的TIME_WAIT
阶段
被动关闭方则不需要等待,在搜到FIN包的ACK之后即可立即关闭
这个MSL
就是 Maximum Segment Lifetime,报文最大生存时间 ,指的是如何报文在网络上传输的最大时间,一般为1~2
分钟
IP报文有个TTL
属性,每经过一个路由器就会减一,直到0则丢弃这个IP报文然后回复给发起方一个ICMP报文,通知包被丢弃了
最后的等待时间主要有如下两个目的:
- 回复FIN的ACK
因为可能A回复的包丢了而B没收到,那么B就会继续发送FIN包,A还必须回复一个ACK包,A等待的时间必须长到B重发的这个FIN包能被A再次收到
如果A不等待了,直接发送ACK包之后就断开了,那么此刻刚好A主机上的端口被其他占领了,同时A发的ACK包也丢了,那么B会继续发FIN包到这个端口,虽然说seq
是随机生成的,但是如果凑巧就和新程序的上一个ack号撞了呢?就会引发错误,所以最后的等待时间必须足够长,长到可以再次或则多次接受B重复发来的FIN包
- 接受遗留的TCP包
并且等待的时间为2MSL
,MSL
这个为网络IP报文最大的生存时间,这样就可以接受所有之前还遗留在网络上的TCP报文,可以保证此时间之后没有对方的TCP数据包遗留在网络中而不至于对以后的同端口的其他应用造成骚扰,超过这个时间之后网络上的报文肯定全部都挂了不存在了
- 重发ACK包
可能之前传输的ACK包丢失了,B又发了一遍数据,则在此期间需要重发ACK包
TIME_WAIT 连接的数量过多问题?
如果并发数量太多,那么处于TIME_WAIT状态的连接也会增加,此状态的连接太多会消耗全连接队列而影响其他正常的连接
MSS和MTU
MTU
:Maxitum Transmission Unit 最大传输单元,是以太网中MAC帧规定Data域的最大长度,一般为1500
至于为什么要设定这个最大长度可以参考知乎的这个问题为什么以太网mtu值被设定为1500? 我总结了一下,主要有如下几个目的:
- 计算机的缓冲队列大小有限制
- 提升用户体验,提升性能,多个不同程序的数据包可以并发的发送
- 便于传输和查错
因为MTU是MAC帧Data数据部分的最大长度,也就是说在TCP/IP中,这个MTU其实就是规定IP数据包的最大小,如果上层协议比如TCP传递下来的数据包大小超过了MTU的大小,那么一个TCP包就会别拆分为好几个IP数据包,IP数据包会通过头部相关字段来进行标识,以此来将原来被拆分的TCP包进行重新组装,这涉及到IP头部的相关信息,并且组装过程由操作系统来完成,对于上层的TCP来说都是透明的,但是如果其中一部分IP数据包丢了,那么就无法组装为完整的TCP包了,底层的IP协议是不负责可靠传输的,但是TCP协议是可靠的,它负责将丢失的TCP包进行重传,所以说TCP报文最好不要超过MTU大小,太大会被IP层分包传输,这样不仅仅增加了拼接IP包的损耗,而且还增加了TCP包丢失的分险,只要其中一个IP包丢失了那么这个TCP包就不是完整的了,就需要整个进行重传,由此在TCP层也规定了一个MSS
,就是为了解决这个问题的
MSS
: Maxitum Segment Size 最大分段大小,表示TCP数据包的Data
部分的最大大小,设置这个大小就是因为物理帧有一个MTU的限制,所以为了防止IP分片带来的损耗,设置了这个大小,这个大小接近MTU是最合理的,太小则数据传输的效率不高,太大则会带来IP分片的损耗
另外还需要注意的是IP分片会对每个包都会加上一个IP头部,如果TCP因为数据太大被IP分片了,那么这个TCP包是不会有多个头部的,因为底层是透明,TCP只要认为这个TCP包就是完整一个的传输过去的即可,IP分片的组装和拆分都由操作系统来完成
RTT、RTO、ARQ、MSL
RTT(Round Trip Time)往返时间间隔
报文发送和收到ACK的时间间隔
RTT很大说明网络环境很不好,RTT很小则表示传输很快网络环境很好
RTO(Retransmission Time Out)
重传超时时间
从数据发送时刻算起,超过这个时间还没有收到ACK便执行重传
发生重传这里必须分为两种情况:
B没收到A的数据包,A发给B的数据包丢了
此时如果A的定时器到了但是还没收到B的ACK则会进行重传
B收到了A的数据包,但是B发给A的ACK包丢了
此时B其实已经收到了A的数据包,但是A没有收到ACK则认为自己的包丢了于是又发送了一遍,如果B再次收到了同样的数据包则不会交付给操作系统而是丢弃重复的数据包,并且再次发送一个ACK告诉A自己已经收到了
如果RTO
太大,那么用户体验会非常不好,包丢弃了则需要过很久才察觉到才会进行重发,会造成很大的延迟,如果RTO
太小,那么会造成很多无用的重发,对方的ACK还没到就触发重发了
所以RTO应当略大于RTT,RTO应该根据RTT来实时变化,这样才能最高效的传输数据和带来用户体验
RTT 和 RTO 的关系是:由于网络波动的不确定性,每个RTT都是动态变化的,所以 RTO 也应随着 RTT 动态变化, RTO 的值基于上次 RTT 往返时间动态计算出来
MSL(Maximum Segment Lifetime)
最大报文段生存时间 此时间和IP报文的TTL
是不一样的,TTL表示最大进过的路由器跳数,和时间无关
但是MSL和时间有关,而不关心经过了多少个路由器
TCP滑动窗口和流量控制
滑动窗口主要是为了解决 发送接受双方的发送速度和接受速度不匹配 问题 ,因为一旦发送过快了,接受方来不及接受TCP数据包了,那么就会造成大量的丢包
发送发有 发送窗口 ,代表自己能发送给对方的数据,更具对方ACK包的win
字段进行设置
接受方有 接受窗口,代表自己接受信息能力,需要将窗口信息在ACK包中告诉发送方
接收方可能会乱序收到不同的字节块包,这时就需要延迟发送ACK,直到前面的TCP字节块包都收到之后然后发送最后一个包的ACK,这样发送方收到最后一个TCP字节包的ACK则认为接收方前面的都收到了,这就是 累计确认 ,这样会造成一个问题,这就是著名的 TCP队首阻塞问题,如果前面的包一直没收到一直丢包,那么窗口之外后面的数据就发不了了,只能等待前面的数据包到达之后,窗口才可以滑动,接收方才可以继续发送后面的数据
累计确认 累计确认的缺点就是如果A发送了5个分组,但是中间的2
分组丢了,后面的3、4、5
都到了,此时就只能发送1
的ACK,A收到之后则会再次发送后4个分组,这样就造成3、4、5
重复发送,如果网络状况不好,经常造成中间或则前面的数据包丢失则会增加带宽消耗
滑动窗口还需要注意 0窗口问题,也就是说如果接受方缓存的数据一直没有被操作系统读取,那么缓存满了就无法再继续接受数据了,此时接受方就需要缩小窗口,直到窗口大小为0,窗口大小为0则表示不能发送数据了,因为不能发送数据了,那么发送方也就不会再收到接收方的ACK包了,那么如果接收方想要扩大窗口的话发送方就永远无法通知发送方了,所以为了避免这种 0窗口情况,发送方需要定时的发送 keep-alive
包,接受方就可以回复包告诉发送方窗口大小了
keep-alive
包还用于探测对方是否存活,如果存活的话则会回复,如果挂了则无法回复,发送方一旦没有收到keep-alive
包的回复则认为对方已经挂了,则不需要再维护连接了,此时就会主动断开连接删除相关的TCP连接数据
拥塞控制
前面滑动窗口cwnd
是为了发送和接受速度不匹配问题,而拥塞控制也有一个窗口: 拥塞窗口 cwnd
如果网络出现拥塞,则cwnd
的值就会减小,如果网络非常通畅,也就是说RTT值非常短,那么cwnd
的值就会变大,也就是能发的数据更大了
因为有了拥塞窗口,那么最终的发送窗口就变为:
swnd = min(cwnd, rwnd)
拥塞窗口主要是为了解决网络拥塞问题,因为如果过多的数据发送到互联网上,如果各个路由器来不及转发的,路由器缓存区溢出的话也会发生丢包
如果RTT
(数据包往返传递时间)过大则表示网络比较拥塞,需要减少发送,如果继续发送则会造成网络更加拥塞导致丢包
TCP拥塞控制主要有如下几种:
- 慢开始 刚开始发送数据的时候不知道网络的状况,因此需要谨慎,慢慢的加大窗口以此来探测网络状况
- 拥塞避免
- 快重传 以数据为驱动,而不是按照
RTO
超时时间来,如果接受到3次同样的ACK则会立即发送ACK要求的包,则不等待定时器超时 - 快恢复
TCP和UDP的区别
UDP报文很简单,只有一个源端口和目标端口,加上UDP长度,注意这个长度是 报文头+UDP的数据长度
UDP特点如下:
- 面相报文 直接封装应用层数据为一个UDP报文进行传输,数据有边界,底层IP层可能会被分片
- 无连接 发送数据不需要连接,不需要维护相关的数据结构
- 无拥塞控制 不管网络多忙,反正应用层有数据传递下来就发送
- 不可靠、不保证顺序 UDP不会进行重传,丢了就丢了,和IP报文一样,丢失不会进行重传,所以UDP可以看作是传输层的IP协议吧,只是比IP协议多了端口
- 支持一对多、多对多、多对一 谁都可以给自己传数据,他也可以把数据传给任何人,不像TCP一样只能一对一传输
所以UDP应用场景有如下:
- 网络环境简单的内网,因为内网基本不会丢包,并且网络情况也比较好不会拥塞
- 需要广播的应用
- 要求处理快、延迟底,但是可以容忍少量丢包 因为UDP没有拥塞控制,只会发包所以传输的速度比TCP快,比如 视频、直播、实时通讯 等应用
为什么说TCP是面相字节流
为什么TCP是面相字节流,而UDP是面相报文的呢?
UDP会将上层传递下来的数据全部封装为一个UDP包,也叫UDP报文,而不会进行拆分,然后就直接传递给下层的IP,所以UDP是面相报文的 也就是说UDP认为上层的数据是有边界的
而TCP则认为上层的数据是字节流,是没有结构没有边界的,TCP相当于开辟了一个传输通道,如果上层传递下来的数据太大则会分为多个TCP包进行传输,大小限制为MSS
,如果传递下来的数据太小则为了提高传输利用率,会等待一定的数据量的时候再分装为一个TCP包传递给下层
TCP会给每个包设置一个 序号 从而维护这些字节流的顺序, 对方收到之后就可以重新按照序号进行组装传递给应用层
如果TCP通道一直没有数据在传输则会自动进行4次挥手关闭连接,这和 KeepAlive 机制有关,详情见下文
与网络相关的Linux配置
主要在路径 /proc/sys/net
下
参考
- 《计算机网络》谢希仁
- https://www.cnblogs.com/xiaolincoding/p/12732052.html 【30张图解: TCP 重传、滑动窗口、流量控制、拥塞控制】 写的很好!
- https://coolshell.cn/articles/11564.html 【TCP那些事(上)】
- https://coolshell.cn/articles/11609.html 【TCP那些事(下)】
- https://www.zhihu.com/question/34400902 【tcp协议握手为什要各随机一个数字并加一?】
- https://yuanrengu.com/2020/77eef79f.html 【TCP三次握手和四次挥手】
- http://c.biancheng.net/view/6424.html 【C语言中文网-TCP协议】
- https://hit-alibaba.github.io/interview/basic/network/TCP.html 【TCP协议】
- https://developer.aliyun.com/article/773014 【五分钟带你读懂 TCP全连接队列】
- https://www.cnblogs.com/xiaolincoding/p/12995358.html 【TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?】
- https://www.huaweicloud.com/articles/b026587fb60bbb3f12202b9c30358eb1.html 【关于MTU和MSS以及网络报文为什么需要分段】
- https://draveness.me/whys-the-design-tcp-segment-ip-packet 【为什么 TCP/IP 协议会拆分数据】