文章目录
- 零. TCP和UDP的区别以及TCP详解
- TCP是如何保证可靠性的
- TCP超时重传的原理
- TCP最大连接数限制
- TCP流量控制和拥塞控制
- 流量控制
- 拥塞控制
- TCP粘包问题
- 一、三次握手和四次挥手
- 二、为什么要进行三次握手?两次握手可以吗?
- 三、为什么要进行四次挥手?三次可以吗?
- 四、第二次握手传回来了ACK,为什么还要传回SYN?
- 五. CLOSE-WAIT和TIME-WAIT的状态和意义
- 六. TIME-WAIT为什么是2MSL
- 七. 有很多TIME-WAIT状态要如何解决
- 八. 有很多CLOSE-WAIT状态要如何解决
零. TCP和UDP的区别以及TCP详解
TCP是如何保证可靠性的
- 数据分块:应用数据被分割成 TCP 认为最适合发送的数据块。
- 序列号和确认应答:TCP 给发送的每一个包进行编号,在传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答,即发送 ACK 报文,这个 ACK 报文当中带有对应的确认序列号,告诉发送方成功接收了哪些数据以及下一次的数据从哪里开始发。除此之外,接收方可以根据序列号对数据包进行排序,把有序数据传送给应用层,并丢弃重复的数据。
- 校验和: TCP 将保持它首部和数据部分的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到报文段的检验和有差错,TCP 将丢弃这个报文段并且不确认收到此报文段。
- 流量控制:TCP 连接的双方都有一个固定大小的缓冲空间,发送方发送的数据量不能超过接收端缓冲区的大小。当接收方来不及处理发送方的数据,会提示发送方降低发送的速率,防止产生丢包。TCP 通过滑动窗口协议来支持流量控制机制。
- 拥塞控制:当网络某个节点发生拥塞时,减少数据的发送。
- ARQ协议:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
- 超时重传:当 TCP 发出一个报文段后,它启动一个定时器,等待目的端确认收到这个报文段。如果超过某个时间还没有收到确认,将重发这个报文段。
TCP超时重传的原理
发送方在发送一次数据后就开启一个定时器,在一定时间内如果没有得到发送数据报的ACK报文,那么就重新发送数据,在达到一定次数还没有成功的话就放弃重传并发送一个复位信号。其中超时时间的计算是超时的核心,而定时时间的确定往往需要进行适当的权衡,因为当定时时间过长会造成网络利用率不高,定时时间过短会造成多次重传,使得网络阻塞。在TCP连接的过程中,会参考当前的网络状况从而找到一个合适的超时时间。
TCP最大连接数限制
-
Client 最大 TCP 连接数
client 在每次发起 TCP 连接请求时,如果自己并不指定端口的话,系统会随机选择一个本地端口(local port),该端口是独占的,不能和其他 TCP 连接共享。TCP 端口的数据类型是 unsigned short,因此本地端口个数最大只有 65536,除了端口 0不能使用外,其他端口在空闲时都可以正常使用,这样可用端口最多有 65535 个。 -
Server最大 TCP 连接数
server 通常固定在某个本地端口上监听,等待 client 的连接请求。不考虑地址重用(Unix 的 SO_REUSEADDR 选项)的情况下,即使 server 端有多个 IP,本地监听端口也是独占的,因此 server 端 TCP 连接 4 元组中只有客户端的 IP 地址和端口号是可变的,因此最大 TCP 连接为客户端 IP 数 × 客户端 port 数,对 IPV4,在不考虑 IP 地址分类的情况下,最大 TCP 连接数约为 2 的 32 次方(IP 数)× 2 的 16 次方(port 数),也就是 server 端单机最大 TCP 连接数约为 2 的 48 次方。
TCP流量控制和拥塞控制
流量控制
流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。如果接收方来不及接收发送方发送的数据,那么就会有分组丢失。在TCP中利用可变长的滑动窗口机制可以很方便低在TCP连接上实现对发送方的流量控制。 实现方式是接收端返回的ACK中会包含自己的接收端的滑动窗口大小,以控制发送方此次发送的数据量大小。
如果接收方的滑动窗口满了,基于TCP流量控制的滑动窗口协议,接收方返回给发送方的接收窗口大小为 0,此时发送方会等待接收方发送的窗口大小直到变为非 0 为止,然而,接收方回应的 ACK 包是存在丢失的可能的,为了防止双方一直等待而出现死锁情况,此时就需要坚持计时器来辅助发送方周期性地向接收方查询,以便发现窗口是否变大。
拥塞控制
拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。常用的解决方法有:慢开始和拥塞避免、快重传和快恢复。
- 慢开始
当发送方开始发送数据时,由于一开始不知道网络负荷情况,如果立即将大量的数据字节传输到网络中,那么就有可能引起网络拥塞。一个较好的方法是在一开始发送少量的数据先探测一下网络状况,即由小到大的增大发送窗口(拥塞窗口 cwnd)。慢开始的慢指的是初始时令 cwnd为 1,即一开始发送一个报文段。如果收到确认,则 cwnd = 2,之后每收到一个确认报文,就令 cwnd = cwnd* 2。
但是,为了防止拥塞窗口增长过大而引起网络拥塞,另外设置了一个慢开始门限 ssthresh。
① 当 cwnd < ssthresh 时,使用上述的慢开始算法;
② 当 cwnd > ssthresh 时,停止使用慢开始,转而使用拥塞避免算法;
③ 当 cwnd == ssthresh 时,两者均可。
- 拥塞避免
拥塞控制是为了让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT (往返时间定义为发送方发送数据到收到确认报文所经历的时间)就把发送方的 cwnd 值加 1,通过让 cwnd 线性增长,防止很快就遇到网络拥塞状态。
当网络拥塞发生时,让新的慢开始门限值变为发生拥塞时候的值的一半,并将拥塞窗口置为 1 ,然后再次重复两种算法(慢开始和拥塞避免),这时一瞬间会将网络中的数据量大量降低。
-
快重传
快重传算法要求接收方每收到一个失序的报文就立即发送重复确认,而不要等到自己发送数据时才捎带进行确认,假定发送方发送了 Msg 1 ~ Msg 4 这 4 个报文,已知接收方收到了 Msg 1,Msg 3 和 Msg 4 报文,此时因为接收到收到了失序的数据包,按照快重传的约定,接收方应立即向发送方发送 Msg 1 的重复确认。 于是在接收方收到 Msg 4 报文的时候,向发送方发送的仍然是 Msg 1 的重复确认。这样,发送方就收到了 3 次 Msg 1 的重复确认,于是立即重传对方未收到的 Msg 报文。由于发送方尽早重传未被确认的报文段,因此,快重传算法可以提高网络的吞吐量。 -
快恢复
快恢复算法是和快重传算法配合使用的,该算法主要有以下两个要点:
① 当发送方连续收到三个重复确认,执行乘法减小,慢开始门限 ssthresh 值减半;
② 由于发送方可能认为网络现在没有拥塞,因此与慢开始不同,把 cwnd 值设置为 ssthresh 减半之后的值,然后执行拥塞避免算法,线性增大 cwnd。
TCP粘包问题
为什么会出现TCP粘包和拆包?
- 当发送方写入的数据大于socket的缓冲区的大小的时候需要拆包。或者当TCP报文的数据部门大小大于MSS(最大报文长度)的时候将会出现拆包。
- 当发送方发送的数据太快,接收方处理数据的速度赶不上发送端的速度的时候将会发生粘包。
解决方案:
- 在消息的头部添加消息长度字段,服务端获取消息头的时候解析消息长度,然后向后读取相应长度的内容。
- 设置消息边界,也可以理解为分隔符,服务端从数据流中按消息边界分离出消息内容,一般使用换行符。
一、三次握手和四次挥手
二、为什么要进行三次握手?两次握手可以吗?
第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和对方的发送能力正常;
第二次握手:客户端可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;
第三次握手:服务器可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;
可见三次握手才能让双方都确认自己和对方的发送和接收能力全部正常,这样就可以愉快地进行通信了。
一方面,如果只有第二次握手,服务端发给客服端的包丢了之后,服务端就直接建立了连接,然后一直傻等,从而造成服务器系统调用超时返回。
另一个方面,TCP 实现了可靠的数据传输,原因之一就是 TCP 报文段中维护了序号字段和确认序号字段,通过这两个字段双方都可以知道在自己发出的数据中,哪些是已经被对方确认接收的。这两个字段的值会在初始序号值得基础递增,如果是两次握手,只有发送方(比如客户端)的初始序号可以得到确认,而另一方的初始序号则得不到确认。
三、为什么要进行四次挥手?三次可以吗?
四次挥手可以被抽象为如下过程:
- Client: 请求关闭
- Server: 同意该请求
- Server: 请求关闭
- Client: 同意该请求
考虑以下服务器遭遇的场景,
先同意对方关闭连接,对方无法传输数据;(第二次挥手)
自己若还有数据未发送完,接着发送直至全部发送完毕;
请求自身关闭连接;(第三次挥手)
客户端同意(第四次挥手)
如果说服务器在客户端请求关闭连接的一瞬间已经没有任何数据需要发送了,那么三次挥手应该也是可以的,但是在实际生产环境中这样的情况几乎没有,所以需要服务器先把自己的所有数据发送完毕,因此四次挥手更加稳妥。
或者也可以这么回答:
释放 TCP 连接时之所以需要四次挥手,是因为 FIN 释放连接报文和 ACK 确认接收报文是分别在两次握手中传输的。 当主动方在数据传送结束后发出连接释放的通知,由于被动方可能还有必要的数据要处理,所以会先返回 ACK 确认收到报文。当被动方也没有数据再发送的时候,则发出连接释放通知,对方确认后才完全关闭TCP连接。
四、第二次握手传回来了ACK,为什么还要传回SYN?
ACK是为了告诉客户端发来的数据接收无误,传回SYN是为了把自己的初始序列号同步到客户端。
五. CLOSE-WAIT和TIME-WAIT的状态和意义
CLOSE- WAIT发生在第二次挥手请求发送之后。
在服务器收到了客户端关闭连接的请求并且告诉客户端自己已经成功收到了该请求之后,服务器进入了CLOSE- WAIT状态,然而此时有可能服务端还有一些数据还没有传输完成,CLOSE- WAIT状态就是用来保证服务器在关闭连接之前能够将待发送的所有数据发送完成,因此不能立即关闭连接。
TIME-WAIT发生在第四次挥手的时候,当客户端向服务端发送ACK确认报文后进入该状态。作用有两个
- 客户端立即关闭后,立即又用同样的端口握手并建立通信,此时上次的连接残留的数据包会被误认为是最新连接的,造成数据异常,
- 客户端直接关闭后,若服务端重新发送 fin 包,客户端就会回应 RST,会报异常,但是其实是没有问题的
六. TIME-WAIT为什么是2MSL
当客户端发出最后的ACK确认报文时,并不能确定服务器能够接收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。
如果服务器在1MSL后仍然没有收到客户端发送的ACK确认报文,那么它会向客户端重传FIN报文,对客户端而言,从客户端发出ACK报文起,重传的FIN报文的最晚到达时间是2MSL。
若服务器在1MSL内没有收到客户端发出的ACK确认报文,再次向客户端发送FIN释放连接报文。若客户端在2MSL内收到了服务器在此发来的FIN报文,客户端将再次向服务器发出ACK确认报文,并重新开始2MSL的计时。
七. 有很多TIME-WAIT状态要如何解决
服务器可以设置 SO_REUSEADDR 套接字选项来通知内核,如果端口被占用,但 TCP 连接位于 TIME_WAIT 状态时可以重用端口。如果你的服务器程序停止后想立即重启,而新的套接字依旧希望使用同一端口,此时 SO_REUSEADDR 选项就可以避免 TIME-WAIT 状态。
实际上,要建立长连接的话,就需要使用SO_REUSEADDR关键字。
八. 有很多CLOSE-WAIT状态要如何解决
一般来说,系统内不会出现很多CLOSE- WAIT(第二次挥手后服务器出现的状态)。我们可以首先检查一下是不是自己的代码出现了问题,比如查看服务端是否忘记关闭连接。其次我们可以调整系统参数,包括句柄相关参数和TCP/IP的参数,一般一个CLOSE_WAIT会维持至少两个小时。