计算机网络(三)传输层
TCP三次握手
TCP头部有哪些字段
TCP头部字段主要有源端口号、目的端口号、序列号、确认应答号、控制位:SYN、ACK、FIN、RST、头部长度、窗口大小,校验和、紧急指针、可扩展的选项这些。
- 源端口号和目的端口号是16位大小,源端口是发送方使用的端口号,目的端口是接收方使用的端口号,端口的作用是标识TCP连接是哪个进程的。
- 序列号和确认应答号都是32位大小,序列号可以保证数据的有序性,接收方会按照发送方顺序发送的数据来组装有序的数据,确认号可以保证数据的可靠性,当发送方已发送的数据,超过一段时间没收到确认报文,就会重传报文。
TCP三次握手过程
- 一开始,客户端和服务端都处于
CLOSE
状态。先是服务端主动监听某个端口,处于LISTEN
状态 客户端会随机初始化序号(
client_isn
),将此序号置于 TCP 首部的「序号」字段中,同时把SYN
标志位置为1
,表示SYN
报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT
状态。服务端收到客户端的
SYN
报文后,首先服务端也随机初始化自己的序号(server_isn
),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入client_isn + 1
, 接着把SYN
和ACK
标志位置为1
。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD
状态。客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK
标志位置为1
,其次「确认应答号」字段填入server_isn + 1
,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于ESTABLISHED
状态。
为什么需要三次握手,两次不行吗
第一个原因是,三次握手可以有效防止历史连接的建立,避免资源浪费。
- 假设网络中残留一个序号为90的SYN报文,现在客户端向服务端发起了建立连接的请求,发送了一个序号为100的SYN报文,如果这时候服务端先收到的是序号为90的SYN报文,就代表收到了历史连接,这时候服务端会回复确认号为90+1的SYN-ACK报文,客户端收到后,发现其实自己期望收到的确认号是100+1,而不是90+1,所以会断开连接,并且回RST给服务端,服务端收到RST也就会断开连接了,这样就避免了历史连接的建立。如果是两次握手的话,服务端在收到第一次握手就会建立连接,如果这时候建立的历史连接,那么就会造成资源浪费了。
第二次原因是,三次握手可以确认客户端和服务端是否同时具备发送和接收的能力。
- 第一握手代表客户端具有发送能力,当服务端收到第一次握手并且响应了第二次握手,实际上这里就证明了服务端具有发送和接收,客户端收到了第二次握手,然后响应了第三次握手,才代表客户端有接收的能力。如果是两次握手的话,只能证明服务端具有发送和接收能力,以及客户端的发送能力,但是无法证明客户端具有接收的能力。
第一次握手丢失,会发生什么
当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到
SYN_SENT
状态。在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的
达到最大重传次数,再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接
第二次握手丢失,会发生什么
第二次握手SYN-ACK报文其实有两个目的
- 第一个是报文中的ACK,是对第一次握手的确认报文,那么当第二次握手的丢失的时候,就会导致客户端长时间没有收到ACK而触发超时重传SYN报文(第一次握手)。到最大重传次数tcp_syn_retries,等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
- 第二个是报文中的SYN,是服务端发起建立TCP连接的报文,那么当第二次握手的丢失的时候,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传SYN-ACK报文。到最大重传次数tcp_synack_retries,再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。
第三次握手丢失了,会发生什么
第三次握手的ACK是对第二次握手的SYN报文的确认,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传SYN-ACK报文,直到收到第三次握手,或者达到最大重传次数。
什么是半连接队列和全连接队列
- 半连接队列:服务端收到客户端发起的SYN请求后,内核会把未完成握手的连接存储到半连接队列,等待完成 三次握手后转移到全连接队列。
- 全连接队列:服务端收到第三次握手的ACK后,内核会把连接从半连接队列移除,然后创建新的完全的连接, 并将其添加到全连接队列,等待进程调用accept函数时把连接取出来。
这两个队列的存在是为了处理并发连接请求,确保服务端能够有效地管理新的连接请求
TCP四次挥手
TCP四次挥手的过程
- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN
标志位被置为1
的报文,也即FIN
报文,之后客户端进入FIN_WAIT_1
状态。 - 服务端收到该报文后,就向客户端发送
ACK
应答报文,接着服务端进入CLOSE_WAIT
状态。 - 客户端收到服务端的
ACK
应答报文后,之后进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后,也向客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态。 - 客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入TIME_WAIT
状态 - 服务端收到了
ACK
应答报文后,就进入了CLOSE
状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL
一段时间后,自动进入CLOSE
状态,至此客户端也完成连接的关闭。
每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手
这里一点需要注意是:双方都可以主动断开连接,主动关闭连接的,才有 TIME_WAIT 状态。*
为什么TCP需要四次挥手?三次挥手不行吗?
我的理解是TCP是全双工协议,双方都具备发送和接收的能力,那么在断开连接的期间,要确保双方能发送完自己的数据。
当客户端发送第一次挥手后,也就是FIN报文的时候,其实就代表客户端告诉服务端我不会再发送数据了,服务端收到后,服务器收到客户端的FIN报文时,内核会马上回一个ACK应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送FIN报文,而是将发送FIN报文的控制权交给服务端应用程序,如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数。所以第二次挥手和第三次挥手通常不会合并一起发送,而是分开发送,所以就需要四次挥手。
如果只有三次挥手,那么就可能出现一方还有数据没有发送完就被迫关闭连接的情况,这会导致数据的丢失。
如果「没有数据要发送」,同时开启「 TCP 延迟确认机制)」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
TIME_WAIT是如何产生的
当TCP连接的主动关闭方关闭连接,与被动关闭方进行了四次挥手的时候,在主动关闭方发送完第四次挥手后,也就是最后一个ACK报文后,主动关闭方的TCP连接就会进入到TIME_WAIT状态,这个状态会持续2MSL的时长,
作用:
- 防止历史连接中的数据(例如:服务端在关闭连接之前发送的报文,被网络延迟了)被后面相同四元组的连接错误的接收;
- 保证「被动关闭连接」的一方,能被正确的关闭(最后一个ACK没收到的超时重传);
为什么TIME_WAIT状态要等待2MSL
MSL
是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
- 第一个原因,主要是为了避免本次连接的历史报文,被新的连接接收到这些历史报文,从而导致出错。MSL表示TCP报文在网络中最大的存活时长,等待2MSL就可以让两个方向的报文可以在网络中自然消失,这样新的连接就不会接收到历史报文了。
- 第二个原因,是为了确保第四次挥手ACK报文能被接收,从而帮助被动关闭方正常关闭连接。如果主动关闭方的第四次挥手的ACK报文丢失了,由于被动关闭方没有收到这个ACK报文,它会超时重传FIN包,主动关闭方TIME_WAIT状态等待期间会重新发送ACK报文,如果没有TIME_WAIT状态,那么就没办法重新发生ACK报文,也就没办法帮助被动关闭方正常关闭连接了。
TIME_WAIT过多有什么危害
如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,占满了所有端口资源,那么就无法对「目的 IP+ 目的 PORT」都一样的服务端发起连接了,但是被使用的端口,还是可以继续对另外一个服务端发起连接的。(一个tcp连接由四元组确定,客服端,目的 IP+ 目的 PORT都确定,那么客户端time_wait就会换端口,等默认情况下客户端可以选择的端口 28232 个用完,就无法发起连接。)
如果服务端(主动发起关闭连接方)的TIME_WAT状态过多,并不会导致端口资源受限,因为服务端只监听一个端口,而且由于一个四元组唯一确定一个TCP连接,因此理论上服务端可以建立很多连接,但是TCP连接过多,会占用系统资源,比如文件描述符、内存资源、CPU资源、线程资源等。
怎么解决TIME WAIT状态过多的问题
- 如果是客户端有大量time_wait状态,可以考虑开启tcp_tw_reuse参数,当发起新连接的时候,会复用处于 time wait状态的连接。
- 如果是服务端的话,尽量让主动断开连接的方式,由客户端来进行,可以在应用层设计一个逻辑,当服务端要 断开连接的时候,发送一个消息给客户端,客户端收到之后,由客户端来断开tcp连接,这样服务端就不会有 time_wait状态了。
服务端产生大量TIME_WAIT状态的原因是什么
主动关闭方才会有TIME_WAIT状态,当服务端有大量TIME_WAIT状态的连接的时候,代表服务瑞主动关闭了连 接,我觉得可能的原因是:
- HTTP没有使用长连接,这时候客户端和服务端都是短连接,服务端发送完HTTP响应后,就会主动关闭了连接,如果有大量的短连接过来,那么服务端就会出现大量的TIME_WAIT状态,如果是这个问题的话,解决方式就是让客户端和服务端都开启HTTP Keep-Alive机制。
- HTTP长连接的请求数量设置过小,Web服务端通常会有个参数,来定义一条HTTP长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接。比如nginx的keepalive_requests这个参数,默认是100,意味着每个HTTP长连接最多只能跑100次请求,因为当QPS请求很高的时候,nginx就会频繁的关闭连接,那么此时服务端上就会出大量的TIME_WAIT状态。解决的方式也很简单,调大nginx的keepalive._requests参数就行。
服务端产生大量CLOSE WAIT状态的原因是什么
CLOSE_WAIT状态是「被动关闭方」才会有的状态,而目如果「被动关闭方」没有调用close函数关闭连接,那么就无法发出FIN报文,从而无法使得CLOSE_WAIT状态的连接转变为LAST_ACK状态。所以,当服务端出现大量CLOSE_WAIT状态的连接的时候,说明服务端的程序没有调用close函数关闭连接。
我觉得问题原因在于,是服务端没有及时调用close关闭连接的函数,导致出现大量CLOSE_WAIT状态的连接,因为只有正确调用了close关闭连接函数的时候,TCP连接状态才会有从CLOSE_WAIT状态变为LAST ACK状态。
我觉得大概率服务端的代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用close。
TCP和UDP
TCP和UDP有什么区别
连接:TCP是面向连接的协议,在发送数据的时候,需要先建立TCP三次握手,而UDP无连接的协议,直接就可以发送数据。
可靠性:TCP会通过超时重传、流量控制、拥塞控制保证数据的可靠传输,而UDP并没有这些特性,UDP不考虑数据的可靠性。
传输方式:TCP发送的数据是以字节流的形式,没有边界。而UDP是一个包一个包的发送,是有边界的。
UDP:操作系统不会对消息进行拆分,在组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界
TCP:当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文
粘包:粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。
一般有三种方式分包的方式:
- 固定长度的消息;
- 特殊字符作为边界;
- 自定义消息结构。
所以,综合来看:
- TCP的优势在于可以保证数据的可靠性,但是缺陷就是实时性没有UDP协议好。
- UDP的优势在于足够简单,不用建立连接,数据直接丢过去即可,并且UDP包头比TCP包头小很多,所以UDP实时性和速度方面是比TCP好的。
什么时候用TCP?什么时候用UDP?
- 如果主要关注数据接收的可靠性和顺序,可以选使用TCP,比如FTP协议、HTTP协议都是基于TCP协议进行传输数据。
- 如果主要关注的是速度和实时性,而且并不在意某些数据包的丢失,可以选使用UDP协议,比如直播、视频会议场景。
视频面试用的UDP还是TCP?UDP丢包会有什么现象?
UDP协议,因为视频会议这个场景下,重要的是实时性,UDP协议实时性比TCP好,采用UDP协议传输音视频数据的话,如果发生了丢包,只是就丢失某一瞬间的画面和语音,然后还可以继续进行会议沟通,不会太影响视频会议的体验,如果是采用TCP协议的,由于TCP是可靠传输,如果发生了丢包,可能画面就卡住不动,等丢包重传才会推进画面,这样实时性就比较差了。
UDP怎么改造变为可靠传输
按照TCP协议怎么实现可靠传输的方式,在应用层实现一遍就好了
- 在应用层增加序列号字段,用来确保UDP的数据可以按序接收,同时还会增加确认号,用来实现超时重传机制,当超过一定时间内没收到已发送数据的确认号,就重传该数据包;
- 在应用层开辟一个缓冲区,用来实现滑动窗口,有了滑动窗口这样发送数据可以先批量发送数据,不需要等上一个数据的确认了才能发送,提高了发送速率,同时还可以基于滑动窗口实现流量控制,用来保证发送方能按接收方的接收能力发送数据,避免发送的数据对方接收不了而发生数据丢失;
- 最后,为了保证整个网络的带宽环境,还需要实现拥塞控制,确保发送方的数据,不会占满整个带宽。
- 我觉得UDP实现可靠传输相比TCP可靠传输有几点优势:
- 拥塞控制算法可以根据不同的应用选用不同的拥塞控制算法,而TCP选用拥塞控制算法的时候,是所有应用都使用这一套拥塞控制算法;
- 升级方便,TCP是在内核实现的,升级TCP需要升级操作系统,而UDP可靠传输是在应用层实现的,升级协议就像升级软件一样简单;
- 可以实现网络连接迁移,在应用层用连接id来唯一标识一个连接,不必像TCP那样,是通过四元组才确定连接的,只要四元组的信息发生了变化,就需要重新建立连接。
TCP和UDP可以共用一个端口吗
socket是根据五元组信息唯一确认的:协议类型、源IP地址、源端口、目标IP地址、目标端口,只要有一个信息不同,就会认为是不同的socket,不会引起冲突,所以TCP和UDP是可以共用一个端口号的。
TCP可靠性
TCP如何保证可靠性
- 连接建立:TCP建立连接的时候,需要三次握手,这个过程可以证明双方都具有发送和接收的能力,并且能避免历史连接的建立;
- 序列号与确认应答:TCP头部有序列字段,可以保证数据的有序性,还有确认号,用来确认收到了数据;
- 数据包重传:如果发送方发送的数据,长时间没有收到确认报文,就会触发超时重传,重传丢失的报文;
- 滑动窗口机制:TCP有流量控制的机制,发送方会根据接收方的接收窗口来发送数据报文,可以避免发送方发送的数据报文太大,导致接收方接收不了而丢包的问题;
- 拥塞控制:TCP有拥塞控制的机制,通过慢启动、拥塞避免、拥塞发生,快速重传和快速恢复等算法调整发送速率来避免网络拥塞。当网络出现拥塞时,TCP会降低发送速率,以减少网络负载,保证数据的可靠传输。
TCP流量控制和拥塞控制的区别
控制的层面不一样,流量控制是端到端,拥塞控制是网络层面的控制。
流量控制:这是一个端到端的控制机制,目的是防止发送方发送的数据过快,导致接收方处理不过来。这通过滑动窗口机制实现,接收方在ACK报文中告诉发送方自己的接收窗口大小,这样就告诉了发送方可接收的最大数据量。
拥塞控制:这是一个网络层面的控制机制,目的是防止过多的数据包同时在网络中传输,导致网络拥塞。主要是通过慢启动、拥塞避免、拥塞发生,快速重传和快速恢复,这几种算法实现的。
所以,流量控制关注的是保护接收方不被淹没,而拥塞控制关注的是保护网络不被过载。
滑动窗口怎么设计的?解决什么问题?
发送方和接收方在内核各自都有一个缓冲区,发送缓冲区和接收缓冲区上都各有一个窗口,发送方的窗口表示可发送的最大数据量,接收方的窗口表示可接收的最大数据量。
- 发送方有了发送窗口后,那么发送方可以不用等待已发送数据的确认报文,就可以继续发送下一批数据,提高了发送的速率。
- 接收方有了接收窗口后,可以实现流量控制,把接收方的接收窗口告诉给发送方,让发送方按自己的接收情况来发送数据,避免发送方发送的数据过快,导致接收方处理不过来。
TCP协议拥塞控制是怎么实现的
TCP协议的拥塞控制主要通过五个算法来实现:慢启动、拥塞避免、超时重传、快速重传和快速恢复。
慢启动:发送方开始时设置一个较小的拥塞窗口大小,当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1,相当于每收到一次确认,窗口大小就翻倍,以指数方式增长,直到拥塞控制窗口达到慢启动门限
ssthresh
。拥塞避免:当窗口大小达到慢启动门限后,就进入拥塞避免阶段,每当收到一个 ACK 时,cwnd 增加 1/cwnd,相当于每收到一次确认,就增加一个报文段的大小,以线性方式增长。
拥塞发生:随着发送速率慢慢增长,可能网络会出现拥塞,发生了数据包丢失,这时候就需要重传数据,重传机制主要有两种,一个是超时重传和快速重传。
超时重传:当发生了超时重传,慢启动门限
ssthresh
会设置为cwnd/2
,并且将拥塞窗口cwnd
恢复为初始值,接着,就重新开始慢启动,发送速率就会瞬间下降了很多;快速重传和快速恢复:
快速重传:当发送方连续收到三个重复确认时,就认为发生了丢包,这时候拥塞窗口会减少到原来的一半
cwnd = cwnd/2
,然后慢启动门限设置为减少后的拥塞窗口大小ssthresh = cwnd
,然后进入到快速恢复阶段快速恢复:这时候会把拥塞控制窗口+3
cwnd = ssthresh + 3
,3的意思是确认有3个数据包被收到了,然后重传丢失的报文如果再收到重复的 ACK,那么 cwnd 增加 1;
如果收到新数据的 ACK 后,把 cwnd 设置为第一步(快速重传)中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
快速恢复算法过程中,为什么收到新的数据后,cwnd 设置回了 ssthresh ?
- 在快速恢复的过程中,首先 ssthresh = cwnd/2,然后 cwnd = ssthresh + 3,表示网络可能出现了阻塞,所以需要减小 cwnd 以避免,加 3 代表快速重传时已经确认接收到了 3 个重复的数据包;
- 随后继续重传丢失的数据包,如果再收到重复的 ACK,那么 cwnd 增加 1。加 1 代表每个收到的重复的 ACK 包,都已经离开了网络。这个过程的目的是尽快将丢失的数据包发给目标。
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,恢复过程结束。
首先,快速恢复是拥塞发生后慢启动的优化,其首要目的仍然是降低 cwnd 来减缓拥塞,所以必然会出现 cwnd 从大到小的改变。
其次,过程2(cwnd逐渐加1)的存在是为了尽快将丢失的数据包发给目标,从而解决拥塞的根本问题(三次相同的 ACK 导致的快速重传),所以这一过程中 cwnd 反而是逐渐增大的。
TCP的延迟应答和累计应答是什么
- TCP的延迟应答是指接收方不立即发送ACK确认接收到的数据,而是延迟一段时间后再发送ACK。这样可以等待是有更多的数据要发送,从而减少ACK报文的数量,提高网络利用率。
- TCP的累计应答是指接收方可以一次性确认接收到多个连续的数据包,而不是每收到一个数据包就发送一个ACK。这样可以减少ACK报文的数量,提高网络效率。
因此没有数据要发,而且启用延迟应答,四次握手就会变成三次握手,如果有数据要发,ack就和数据走了,不是和FIN报文
TCP场景
TCP粘包拆包原因是什么?怎么解决?
TCP是一个面向字节流协议,并不关心数据包的边界。
- 如果发送的数据包超过MSS(最大报文段长度)大小或者接收窗口大小后,会被拆分多个TCP段发送,这时候站在应用层的角度,就是一个完整的数据包被拆开了。
- 如果发送的数据包太小,TCP还有一个Nagle算法,会把多个小数据包堆积成一个TCP段发送,这时候就会感觉数据包是粘合一起发送的。
其实我认为,TCP拆包粘包并不是TCP的问题,只是因为TCP的特性,才出现的现象,所以要解决拆包和粘包,主要是在应用层进行处理的,比方说有这些解决方案:
- 固定消息长度的方式,每个数据报文都需要一个固定的长度。当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。当发送方的数据小于固定长度时,则需要空位补齐。但是缺点也非常明显,无法很好设定固定长度的值,如果长度太大会造成字节浪费,长度太小又会影响消息传输,所以在一般情况下消息定长法不会被采用。
- 特定分隔符的方式,可以在每次发送报文的尾部加上特定分隔符,接收方就可以根据特殊分隔符进行消息拆分。比方说,HTTP请求报文就是通过空白行的方式,分隔了请求头和请求体。这种方案需要注意的是分隔符的选择一定要避免和消息体中字符相同,以免冲突。
- 消息长度+消息内容的方式(自定义消息结构),消息头中存放消息的总长度,例如使用4字节的int值记录消息的长度,消息体实际的二进制的字节数据。接收方在解祈数据时,首先读取消息头的长度字段Len,然后紧接着读取长度为Len的字节数据,该数据即判定为一个完整的数据报文,这个方案项目开发中最常用的一种方法,因为使用方式非常灵活,且不会存在消息定长法和特定分隔符法的明显缺陷。当然在消息头中不仅只限于存放消息的长度,而且可以自定义其他必要的扩展字段,例如消息版本、算法类型等。
TCP的keepalive了解吗?说一说它和HTTP的keepalive的区别?
- HTTP的Keep-Alive是叫HTTP长连接,该功能是由应用程序实现的,可以使得用同一个TCP连接来发送和接收多个HTTP请求和应答,减少了HTTP短连接带来的多次TCP连接建立和释放的开销。
- TCP的Keepalive是叫TCP保活机制,该功能是由内核实现的,当客户端和服务端双方长达一定时间没有进行数据交互时,内核为了确保该连接是否还有效,就会发送探测报文,来检测对方是否还在线,然后来决定是否要关闭该连接。
IP层会分片,为什么TCP层还需要MSS呢
MTU:一个网络包的最大长度,以太网中一般为1500字节。当IP层有一个超过MTU大小的数据要发送,那么IP层就要进行分片,把数据分片成若干片,保证每一个分片都小于MTU
MSS:除去IP和TCP头部,之后,一个网络包所能容纳的TCP数据的最大长度。当TCP层有一个超过MSS大小的应用数据,那么TCP层就要针对应用层数据进行分片,把数据分片成若干片,保证每一个分片都小于MSS
如果交给IP来进行分片,如果某一个IP分片丢失了,整个IP报文的所有分片都得重传。因为IP层本身没有超时重传机制,它由传输层的TCP来负责超时和重传。当某一个IP分片丢失后,接收方的IP层就无法组装成一个完整的TCP报文(头部+数据),也就无法将数据报文送到TCP层,所以接收方不会响应ACK给发送方,因为发送方迟迟收不到ACK确认报文,所以会触发超时重传,就会重发整个TCP报文(头部+数据)。
应用层的数据在TCP层经过MSS分片之后,每一个分片都具备TCP头部信息,这样经过MSS分片后的报文,如果某一个丢失了,只需要重传这一个丢失的TCP报文就可以了,而不用重传所有的分片,大大增加了重传的效率。
一个服务端进程最多可以建立多少条TCP连接
一条TCP连接是通过源IP地址、目的IP地址、源端口、目的端口这四个信息唯一确定的。服务端进程通常是会固定监听一个端口,等待客户端的连接请求,现在假设服务端的IP地址和端口号都是不变,对 IPv4,客户端的 IP 数最多为 2 的 32次方,TCP端口号为16位,那么一个服务端进程最大能支持的理论连接数等于客户端的IP数量 * 客户端的端口数量= 2^32 * 2^16 = 2^48
这个只是一个理论值,实际上服务端还会受系统资源的限制,比如文件描述符的最大数量(每个TCP连接都是一个文件)、CPU和内存资源。
一个机器最多可以建立多少条TCP连接
一条TCP连接是由四元组组成的,服务器设备上可以有多个进程监听多个端口,然后如果服务器所有端口都被用来监听的话,理论上最大可以建立的TCP连接数等于客户端的IP数量 * 客户端的端口数量 * 服务端的端口数量= 2^32 * 2^16 * 2^16 = 2^64
上面这个只是理论值,实际中由于服务器硬件资源的限制是无法达到的,比如内存资源的限制,我之前了解到每条静止的TCP连接内存占用大概3.3K,如果服务器的物理内存是4G,那么最大只能建立100万左右(4GB/3.3KB)的连接了。
如果已经建立了连接,但是服务端突然出现断电了会发生什么
服务端突然断电的后:
- 如果客户端接下来会发送数据,那么由于服务端断电了,客户端已发送的数据就没办法得到确认,于是就会发生超时重传,重传次数达到上限之后,客户端就会断开TCP连接了。
- 如果客户端接下来不会再发送数据,那么要看客户端有没有开启TCP保活机制:
- 如果没有开启的话,客户端的TCP状态将一直保持不变;
- 如果有开启了TCP保活机制,双方超过一段时间没有进行数据交互的话,内核就会触发TCP保活机制,定时发送探测报文,因为服务端断电了,客户端发送的探测报文,都不会得到响应,那么探测次数达到上限,还未收到响应的话,客户端就会断开TCP连接了。
如果已经建立了连接,但是服务端的进程崩溃会发生什么
TCP的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有TCP连接资源,于是内核会发送第一次挥手FIN报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成TCP四次挥手的过程。
TCP中SYN洪水是什么?如何防止?
SYN洪水攻击就是攻击者伪造大量不同IP地址的SYN报文发送给服务端,服务端每收到SYN报文后,会把还没完成连接建立的socket,存放在tcp半连接队列,然后响应SYN-ACK报文,但是这些IP地址都是不存在的,这样服务端就无法收到第三次握手,这样就会导致tcp半连接队列被占满了,占满后,服务端就无法再建立tcp连接了,不能给正常的用户提供服务了。
解决的方式话,
- 我觉得最好的是开启tcp的syn_cookie机制,它可以在服务端第二次握手的时候,生成一个伪随机的序列号,用于验证客户端的SYN包。只有在客户端回应服务器的ACK包时,服务器才会分配资源,所以可以在不使用tcp半连接队列的情况下成功建立连接,相当于绕过了半连接来建立连接。
- 其次的话,可以通过调整内核参数来增大半连接队列的大小,以及减少SYN-ACK重传的次数来避免SYN洪水攻击。