您现在的位置是:首页 >技术杂谈 >TCP协议网站首页技术杂谈
TCP协议
TCP协议是一个很典型传输层协议。
TCP协议报文格式
可靠传输
可靠传输是 TCP 存在的初心!最核心的机制!
可靠传输的意思不是说100%可以传输成功,而是尽可能的传输,如果传输不过去,发送方可以知道自己传输失败,接收方有没有收到数据,会有个回应。
TCP 如何实现可靠传输:确认应答+超时重传
确认应答
确认应答指的是:接收方收到数据,会返回应答报文,ACK(acknowledge)报文。
TCP 报头中有一个序号字段和一个确认序号字段。
发送方,报头中序号是1,后面数据有1000个字节。
接收方,收到数据,返回的应答报文的报头中确认序号是1001。
那么发送方通过应答报文中的确认序号,就知道前面1000个字节已经成功传输了,接下来要传输 1001 往后的数据。
TCP 是针对数据的每个字节去编号的。
超时重传
丢包
丢包是网络上非常常见的情况。
网络传输过程中,通过很多的路由器,交换机进行转发,这些网络设备转发能力是有上限的,当这中间某一个网络设备太忙了,要转发的数据太多了,这种情况下有些数据就会丢失。
- 数据包丢失
如果数据丢包了,接收方就不会返回应答报文。发送方接收不到应答报文,如果迟迟收不到应答报文,发送方就会重新发送这个数据,这就是 超时重传。
- ACK丢失
如果应答报文也可能丢包,发送方也收不到应答报文,也会重发送数据,那么接收方就会收到两份一样的数据,但是没关系,TCP 会通过序号在接收缓冲区帮我们去重, 上层的应用程序不会读到重复的数据。
- 连续丢包
如果连续丢包,这种情况可能是网络出现了严重故障。
TCP 面对这种连续丢包的情况,也会重传,但是每次丢包都会延长超时等待的时间(TCP认为网络出问题了,重传大概率也不能成功,干脆摆烂)。
连续多次重传,都没收到应答报文,TCP 就会尝试重新连接,如果重连也失败,TCP 就会关闭连接, 放弃网络通信。
确认应答保证可靠性,如果出现丢包,超时重传作为补充。
连接管理机制
连接管理也一定程度上保证了可靠传输。
连接指的是通信双方,各自记住对方的信息。
三次握手
TCP 建立连接,三次握手
握手🤝,指的是通信双方进行一次网络通信。
三次握手,就是客户端和服务器通信三次,成功握手三次,才建立连接。
通信双方各自给对方发同步报文 SYN
和应答报文 ACK
。
过程:
第一次握手:客户端先给服务器发送一个 SYN
。
第二次握手:服务器收到后,将 ACK
+ SYN
一起返回给客户端。
第三次握手:然后客户端也返回给服务器 ACK
。
TCP 报头里,有6个特殊的bit位,默认是0,如果设为1,那么就代表目前是什么类型的报文。
ACK
+ SYN
就是第2位和第5位都为1。
为啥要三次握手:
投石问路!验证客户端和服务器各自的发送能力和接收能力是否正常。
三次握手就是确保双方能正常通信,才建立连接。
四次挥手
TCP 断开连接,四次挥手
通信双方通过四次网络通信,来断开连接。
通信双方,各自给对方发送一个结束报文 FIN
,再各自返回应答报文 ACK
。
与建立连接不一样,客户端和服务器都可以主动断开连接。
过程:
以客户端断开连接为例。
第一次挥手:客户端先向服务器发送 FIN
。
第二次挥手:服务器向客户端返回 ACK
。
第三次挥手:服务器也向客户端发送 FIN
。
第四次挥手:客户端也向服务器返回 ACK
。
服务器返回 ACK
和发送 FIN
是有概率合并为一条报文的,但是通常情况下,是不能合并的。
三次握手中服务器发送的 ACK
和 SYN
是可以 100% 合并的。
四次挥手为什么不能合并呢?
三次握手: ACK
和 SYN
是同一时机触发的(都是系统内核完成的)。
四次挥手: ACK
和 FIN
不是同一时机触发的。
ACK
是系统内核完成的,会在收到 FIN
的时候第一时间返回。
FIN
是由应用程序的代码控制的,在调用 socket
的 close
方法时,才会发送。
批量传输
滑动窗口
TCP 协议保证了可靠性,同时也降低了效率。
每次发送数据,都要等待ACK,效率较低。
滑动窗口是用来弥补TCP效率 的机制。
批量发送数据,一次发多条数据,一次等多条ACK。
批量发四条数据,再去等待ACK,然后每次收到一条ACK,就立即发送下一条数据,这样就可以同时等待四条ACK,而不是等一条,发一条了。
使用一份时间,等待多条ACK,总的等待时间缩短,效率也就提高了。
这个批量传输,称为滑动窗口。
窗口大小:同时等待ACK的数量。
滑动:收到一个ACK就立即发下一条。
这个图可以看到,主机A批量发送了四条数据,同时等待四个
ACK
。
当 主机A 收到 主机B 返回的ACK
后,立即发送下一条数据,并且窗口向后滑动。
如果收到ACK
的速度非常快,这个窗口就会快速的往后滑动。
批量发送的过程中,丢包了会发生什么?
- 数据包成功送达,ACK丢包
这种情况没关系,对传输可靠性没有任何影响。
确认序号的含义,表示该序号之前的序号的数据都已经收到了。
这说明前一个ACK丢了,后一个也能代表前一个应答。
确认序号为 3001 和 4001 的 ACK
都丢了,但是主机A收到 5001 这个 ACK
时,说明之前传输的数据 主机B 都收到了。
但是如果 6001 最后一个 ACK
丢了,主机A 迟迟没收到 ACK
,就会触发超时重传。
- 数据包丢失,没有成功送达
当发送端收到3个同样的ACK,就会重新发送接收端索要的数据。
这种重传机制,没有冗余操作,不会发送重复的数据,数据丢了才会重传,这个重传也称为 快速重传。
滑动窗口和快速重传,是在批量传输大量数据时,才会触发的机制。
如果只是传输少量,低频数据的时候,还是一条一条发,采用 确认应答和超时重传机制
流量控制
滑动窗口,窗口越大,批量传输的数据越多,同时等待的ACK越多,整体速度就越快。
但是不能让这个窗口太大,因为如果发的太快,接收端处理不过来,把接收端的接收缓冲区放满了,数据就会丢失,这样还不如发慢一点呢。
流量控制,本质上就是让接收端来限制发送端的传输速度。这也是保证可靠性的机制。
如何进行流量控制?
在 TCP 报文中有一个字段表示窗口大小,当这个报文为 ACK
报文时,这个字段就会生效。
这个字段的值就是建议发送端批量传输时的窗口大小。
接收方如何计算窗口大小?
接收缓冲区的剩余空间,作为窗口大小。
注意:
发送端第一次传输数据时不知道接收端缓冲区的情况,不会批量发送。
窗口探测:
接收端接收缓冲区满了之后,发送端会暂停发送,但是会隔一段时间发一个窗口探测报文,来检测接收端的状态。
拥塞控制
滑动窗口的大小取决于流量控制和拥塞控制。
流量控制是通过接收端的处理能力来调整窗口大小。
拥塞控制是通过预估路径的传输能力来调整窗口大小。
发送端给接收端传输数据,会经过很多的中间节点(路由器/交换器),这些设备的传输能力是有限的,当中间任意一个节点传输能力达到上限,就会出现大量丢包。
如何进行拥塞控制?
因为通信双方之间,有很多中间节点,每次传输的路径也不完全相同,所以无法直接给出合适的窗口大小。只能通过实验,来找到一个合适的发送速率。
一开始,拥塞窗口较小,如果没有丢包,拥塞窗口指数级增长,达到一个阈值,就转为线性增长,如果出现大量丢包(网络拥堵),就立即调小窗口,重复这个过程。
拥塞窗口:
这是发送端进行实验得到的,用来衡量通信双方中间节点的传输能力。
发送端将 拥塞窗口 和 接收端返回的 ACK
中的窗口大小做比较,取较小值作为实际发送的窗口大小。
拥塞控制是想尽可能快的传输数据,但是又要避免网络拥堵。
应答机制
延迟应答
延迟应答的目的是提高效率。
延迟应答指的是接收端收到数据,等一会才发送 ACK。
如果立即发送ACK,窗口大小可能比较小。
等一会再发,可能接收端已经处理完数据了,接收缓冲区剩余空间变大,返回的窗口大小就比较大。
延迟应答的方式:
- 数量限制:每隔N个包就应答一次;
- 时间限制:超过最大延迟时间就应答一次
捎带应答
捎带应答是在延迟应答的基础上实现的,很多时候服务器与客户端是双向通信,比如客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine,thank you”。
这时候服务器返回的 ACK
就可以搭顺风车,与服务器回应的 “Fine,thank you” 一起发送给客户端。
粘包问题
什么是粘包问题
首先要知道,这个 “包” 是 应用层数据包 。
TCP是 面向字节流 的,而且没有 UDP报头中 报文长度 这样的字段。
所以当 A 给 B 连续发了多个应用层数据包后,这些数据都堆在了 B 的接收缓冲区中,B 的应用层看到是一连串的字节数据。
那么 B 就难以区分这一连串的字节数据,从哪部分到哪部分,是一个完整的应用层数据包。
如何避免粘包问题
明确应用层数据包之间的边界,方法有很多。
- 可以在包头位置,约定一个数据包长度的字段,就知道了这个包的数据的结束位置。
- 可以约定使用明确的分隔符,只要分隔符不与正文冲突,就可以很好的区分两个包。
TCP异常情况
进程中止
进程虽然中止了,但是 TCP 连接还在,会正常进行四次挥手断开连接。
主机关机
会先关闭所有进程,也会进行四次挥手,但是可能还没挥完,向对端发送 FIN
了, 还没等到对端的 ACK
和 FIN
,就关机了。
对端多次发送 FIN
,也没有收到 ACK 回应,就会尝试重置连接,重置连接失败,就会释放连接。
主机掉电/网线断开
这种情况非常突然,连向对端发送 FIN
的机会都没有。
分两种情况考虑:
- 对端是发送端
对端发送数据,接收不到 ACK,就会超时重传,重复多次也没收到 ACK,就会尝试重置连接,重置连接失败,就会释放连接。
- 对端是接收端
对端是接收端的情况下,对端无法立即感知到我们出现了异常.
对于这种情况,TCP内置了一个保活定时器,定期发送一个心跳包,如果多次没有回应,就释放连接。