- OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
- TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层
1. TCP的三次握手和四次挥手
三次握手和四次挥手,其实客户端和服务端都要分别发送一次和接收一次,所以都是有4次传输:
1.客户端主动要求:在三次握手的时候是主动要求建立连接,叫SYN;在四次挥手的时候是主动要求断开连接,叫FIN。
2.服务端被动收到后响应:在三次握手和四次挥手的时候都叫ACK,但三次握手的时候服务端会把这个ACK和本该下一次传输的SYN一起合并起来叫SYN-ACK。
3.服务端主动要求:在三次握手的时候是主动要求建立链接,叫SYN,但如上面第2步所属,被合并到第2步一起发下去了,叫SYN-ACK;在四次挥手的时候是主动要求断开连接,叫FIN。
4.客户端被动收到后响应:在三次握手和四次挥手的时候都叫ACK。
三次握手和四次挥手,应答都叫ACK,发起分别叫SYN和FIN。
三次握手把第2、3步服务端的应答、发起合成一个步骤了,所以只剩3次,而四次挥手是老老实实地干了4次。
1.1. 四次挥手为啥需要老老实实地4次?
1.1.1. 理解方式1.
第二次挥手和第三次挥手不能合并的原因主要是服务器在收到客户端的FIN报文后,可能还有数据需要发送。如果在这两个阶段合并发送ACK和FIN报文,那么服务器就无法保证在发送FIN报文之前完成所有数据的发送,这可能会导致数据丢失或连接关闭不完整的问题。
1.1.2. 理解方式2.
1.1.2.1. TCP连接的全双工特性
TCP是一个全双工的协议,这意味着数据可以在两个方向上同时传输。在四次挥手过程中,每个方向都需要一个FIN报文来关闭该方向的数据传输,以及一个ACK报文来确认收到对方的FIN报文。因此,从逻辑上讲,四次挥手是必要的,以确保两个方向上的数据传输都能被正确关闭和确认。
1.1.2.2. 数据传输的完整性需求
当TCP连接的一方(如客户端)发起关闭请求(发送FIN报文)时,另一方(如服务器)可能还有数据需要发送。在这种情况下,服务器不能立即关闭连接,而是需要先发送完剩余的数据,然后再发送自己的FIN报文来关闭连接。
1.2. 三次握手,为什么两次不行?
因为要保证可靠传输,建立链接的时候必须要双方知道对方的序列号,所以我带上序列号喊你一声,你应(确认),你带上序列号喊我一声,我应(确认),这样才确保双方知道对方的序列号,如果只有2次,就最多一方知道对方序列号。
1.3. 四次挥手的TIME_WAIT和CLOSE_WAIT状态
四次挥手的具体过程如下:
1.第一次挥手:客户端发送一个FIN报文给服务器,表示客户端已完成数据的发送,想要关闭连接。此时,客户端进入FIN_WAIT_1状态。
2.第二次挥手:服务器收到FIN报文后,发送一个ACK报文给客户端,确认收到FIN报文。此时,服务器进入CLOSE_WAIT状态,客户端收到ACK报文后,进入FIN_WAIT_2状态。注意,此时服务器可能还有数据需要发送,因此不能立即发送FIN报文。
3.第三次挥手:服务器发送完剩余的数据后,发送一个FIN报文给客户端,表示服务器也已完成数据的发送,想要关闭连接。此时,服务器进入LAST_ACK状态。
4.第四次挥手:客户端收到FIN报文后,发送一个ACK报文给服务器,确认收到FIN报文。此时,客户端进入TIME_WAIT状态,等待一段时间后(通常是2MSL,即报文段最大生存时间的两倍),确保服务器收到ACK报文后关闭连接,客户端也关闭连接。
- CLOSE_WAIT是第一次收到FIN:为啥叫“close”wait,因为开始“close”了。
- TIME_WAIT是第二次收到FIN:为啥叫“time” wait,因为要等2MSL的“time”
1.4. 为什么会TIME-WAIT过多,怎么解决?
注意:握手只能客户端发起,挥手两边都可以主动发起,但一般是客户端来发起。
1.4.1. 原因1. 高并发短连接,服务端主动断开
主动发起FIN的一方才会进入TIME-WAIT状态,因为它让对方进入CLOSE_WAIT了。
解法:
- 使用长连接:长连接可以减少连接的建立和关闭次数。
- 限制并发连接数:避免过多的并发连接。
- 使用连接复用:用HTTP/1.1的keep-alive机制或HTTP/2的多路复用技术来复用TCP连接,减少TIME-WAIT连接数量。
- 使用负载均衡:将连接均衡地分配到多个服务器上,减少每台服务器上的TIME-WAIT连接数量。
1.4.2. 原因3. 使用了负载均衡
使用了负载均衡的情况下,数据包可能会经过多个节点,每个节点都会记录连接状态,这也可能导致TIME-WAIT状态的增加。
解法:主要解决负载均衡上的time-wait过多,因为它是更直接与client相连的节点。所以,让web服务器主动发起断开,这样负载均衡器上就不会有很多TIME-WAIT了,TIME-WAIT就到web服务器上了,但因为有很多web服务器(负载均衡挂过来的),所以问题不大。
1.4.3. 原因4. 系统参数设置不当
TCP参数设置不合理,如tcp_tw_reuse、tcp_tw_recycle等参数未启用或设置不当,可能导致TIME-WAIT状态的连接无法及时释放。
解法:
- 允许重用:允许TIME-WAIT状态的socket被重用,这可以通过修改内核参数来实现。
- 快速回收:启用快速回收TIME-WAIT状态的socket。但这会与NAT(网络地址转换)不兼容,用时要谨慎。
- 缩短持续时间:缩短TIME-WAIT状态的持续时间。
1.4.4. 原因2. 网络延迟较大时
连接的关闭和TIME-WAIT状态的处理也会变得缓慢,从而导致TIME-WAIT状态的积累。
解法:
上述每种都用上,同时加上对网络状态的监控。
2. TCP/UDP的区别
- TCP: 面向连接,字节流,可靠,传输效率低,用于文件、邮件,HTTP、SMTP(简单邮件传输协议)、FTP、Telnet、WebSocket、SSH。
- UDP:无连接,数据报文,不可靠,传输效率高,用于语音、视频、直播,DNS、SNMP(简单网络管理协议)、RIP(路由信息协议)。
RTP(实时传输协议)一般是基于UDP的,也可以基于TCP。
- TCP的“字节流”像是一个持续不断的水流,保证了数据的连续性和可靠性;
- UDP的“数据报文”像是一个封装好的信封,它保留了数据的边界,但不保证数据的可靠传输和顺序。
- TCP 就像一个正规的邮寄服务,需要详细的地址和签收确认;
- UDP 则像随手扔出去的纸条,不太在意是否能准确送达和确认。
3. TCP滑动窗口和拥塞控制
3.1. 滑动窗口
- 接收方根据自身处理能力调整窗口大小,并通过TCP报文段中的窗口大小字段通告给发送方。
- 接收方成功接收数据并发送确认应答时,发送方的滑动窗口会向前滑动,表示这部分数据已被确认,可以继续发送新的数据。
TCP滑动窗口就像一个可动态调整大小的餐厅传菜窗口。厨师(发送方)通过这个窗口将菜(数据)传给服务员(接收方)。服务员每次取走菜品后,窗口就会空出来,厨师就可以继续放下一批菜品。如果服务员告诉厨师客人吃得很慢,厨师就会调整传菜的速度,确保窗口不会堆满。这样,TCP滑动窗口就通过动态调整大小来控制数据的发送速率,保证数据传输的高效和有序。
3.2. 拥塞控制
TCP的拥塞控制是为了防止过多的数据注入网络,导致网络拥塞,从而影响网络性能的一种机制。
可以把网络想象成一条公路,数据是公路上的车。车多时,公路就变得拥挤,行驶速度就会变慢,甚至可能引发交通事故(即数据包丢失)。TCP的拥塞控制就是交通警察在指挥交通,确保车辆(数据)能够有序、高效地行驶(传输)。
TCP的拥塞控制包括以下几个步骤:
1.慢启动:
- 就像新手上路一样,TCP连接建立初期,发送方会谨慎地发送数据,避免一下子发送太多导致网络拥塞。
- 在慢启动阶段,TCP会逐步增加发送窗口的大小(即一次能发送的数据量),每收到一个确认应答(ACK),发送窗口就加倍,直到达到慢启动阈值(ssthresh)或发生超时。
这个过程就像是新手司机逐渐熟悉路况,开始加速行驶。
2.拥塞避免:
- 当发送窗口大小达到慢启动阈值时,TCP就进入了拥塞避免阶段。
- 在这个阶段,TCP不再像慢启动那样快速增加发送窗口的大小,而是改为线性增长,即每经过一个往返时间(RTT),发送窗口就增加一个单位(如一个最大报文段长度MSS)。
这样做是为了避免网络拥塞,就像司机在拥堵路段放慢车速一样。
3.快重传和快恢复:
- 如果TCP在传输过程中发现某个数据包丢失了(通常是通过连续收到三个重复的确认应答来判断的),它会立即重传丢失的数据包,并进入快恢复阶段。
- 在快恢复阶段,TCP不会将发送窗口重置为1,而是将其设置为慢启动阈值的一半(但不少于两个MSS),然后按照拥塞避免的方式线性增加发送窗口的大小。
这个过程就像是司机在发现前方有事故时,迅速变道避让,并继续谨慎行驶。
4. TCP粘包
TCP粘包是指在使用TCP协议进行数据传输时,出现了多个数据包粘连在一起的情况。
TCP中,数据以字节流的形式进行传输,没有明确的边界来区分每个数据包。这就可能导致以下情况:
- 发送的多个小包,接收时被合并成了一个包。例如,连续发送了两个包,分别包含“Hello”和“World”,接收可能会一次性收到“HelloWorld”,而以区分这原本是两个独立的数据包。
- 发送方发送的数据速度较快,接收方处理速度较慢,导致多个数据包堆积在一起。
TCP 粘包可能会给数据处理带来困扰,因为接收方需要根据应用层的协议来对粘连的数据进行拆解和区分,以获取正确的数据包内容。
为了避免 TCP 粘包问题,可以:
- 添加标识或分隔符,以便接收方能够准确地解析数据。
- 使用定长的包。
- 在数据包中包含数据的长度信息。