题目描述
1.什么是三次握手,四次挥手?为什么分别要三次与四次?
2.tcp协议中,close_wait与time_wait状态分别代表什么含义,为什么要设计这两种状态,解决了什么问题?
3.time_wait为什么要等待2MSL
4.平时排查问题中遇见大量close_wait应该如何处理?
参考答案
1.首先要理解TCP协议的定位,从wikipedia上抄一下定义:传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。用户数据报协议(UDP)是同一层内另一个重要的传输协议。
并且TCP是一个双向全双工的传输协议,这个后面再详细解释。
然后再聊一下一下tcp的6个标志位:
- SYN(synchronous) Synchronize sequence numbers to initiate a connection
- ACK(acknowledgement) The ackowledgement number is valid
- PSH(传送) The receiver should pass this data to the application as soon as possible
- FIN(finish结束) The sender is finished sending data.
- RST(reset重置) Reset the connection
- URG(urgent) The URGENT POINTER field contains valid data
step1:client端尝试建立连接,发送了一个tcp报文,这个tcp报文header里的SYN标志位是1,同时会随机生成ISN(initial sequence number)作为sequence number塞到报文的header里,这时候client端进入SYN-SENT状态
step2:server端接受到client端发送的请求,返回报文表示已经收到建立连接的请求,并同时尝试建立server端到客户端的连接。所以这时候header里的SYN与ACK标志位同时被置为1,且server端生成自己的ISN作为sequence number,而ack number则为client端的seq number+1。
step3:client端收到报文,这是client->server端的连接已经被建立,意味着已经可以从client端向server端发送数据,但ciient端同时也要发送ack消息给server端,这时候ack标志位为1,ack number为server端的seq number+1,当server端接受到这条消息表示连接建立成功。
然后回答为什么需要三次握手:首先tcp协议是可靠的,所以通过ack机制保证发送方可以确认接收方是否接收到了消息。其次,我们前面提到了tcp是双向全双工的,这意味着什么?意味着一旦建立连接后,client和server都可以主动像对方主动发送消息,且发送数据的时候同时也能够接受数据。所以step1+step2其实是建议client->server端的连接,而step2+step3建立的是server->client的连接。其实到这里我们已经理解了,step2其实是出于效率方面的考虑把2步并为1步,在返回ack的时候同时合并了一个建立连接的报文,所以由4步并为了3步。
step1:client主动发送fin包给server,此时的seq number为u,client端进入fin_wait_1状态
step2:server端接受到消息,发送ack包给client,此时的seq number为v,server端进入close_wait状态(一般这个时候会通知应用层进行相关的操作),client此时进入fin_wait_2状态,client->server的连接已经被close
step3:在等待应用层完成相关操作后,server端也发送fin包,尝试关闭server->client的连接,此时server端进入last_ack状态(看到很多地方说这个时候会带上一个client端中断的ack,这个我理解没什么必要?不知道有没有人可以帮我解释一下)
step4:client端返回ack给server端,并进入time_wait状态,持续2msl
为什么要四次挥手:其实大致流程跟三次握手差不多,唯一的差别只是中间两步并没有并成一步,之所以没有并成一步应该是给应用层一点时间来做close的准备工作。
2.close_wait与time_wait在上面应该已经都说了,close_wait表示接受到了对方申请关闭连接的请求,但是这个时候可能你的应用层还有事情需要处理,否则这2步就可以合并成一步,直接进入last-ack状态了。而当完成step4之后,server端可能因为网络原因没有接受到ack,这个时候会重复step3,如果client端没有进入time_wait状态而是直接关闭,将会导致server端无法正常关闭。同时,由于网络中的消息传递是存在延时的,如果发送完ack之后立即进入closed状态,然后在相同的port上立即建立新的连接,则有可能接受到上一次连接的残存消息,可能会导各种不可预知的致异常出现。
3.考虑最坏的情况,step4client发送给server,这个时候消息丢了,这一步骤最长占用1msl,server端判断消息丢失后,重复step3重新发送fin给client,这一步最长占用1msl,所以加起来就是2msl。
4.其实这个问题问得不太好,我们先要了解大量close_wait有什么危害。因为linux分配给一个用户的文件句柄是有限的(一般是1024),如果time_wait或者colse_wait两种状态被一直保持,这些通道会被一直占有,很快就会报出too many open files in system(这个是os层面的报错),然后就gg了。所以首先有有意识大量的close_wait是有危险的,然后根据上面所说的,close_wait是由对象主动发起断开,而你一直没有返回ack,那么你就会一直维持在close_wait状态(一般来说都是client端与server端建立了连接,然后client端忘记close,server端开始主动断开连接,但是client端没有响应,然后就挂起在那了)。然后平时发生问题首先可以等到机器上netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}',看看连接的情况(不过有monitor的话一般这一步可以省略)。然后netstat -an,看一下都是哪些连接在time_wait状态,根据server端的ip理论上可以判断出来是哪个连接,再然后就是去看这一块的代码,有没有忘记释放连接的地方。
后记:现实场景中可能会更加复杂,比如消息的乱序,丢失等种种情况,上面只讨论了正向流程,想再深入的话可能还需要考虑更多的异常流程。
参考文献:
1.https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE
2.http://telescript.denayer.wenk.be/~hcr/cn/idoceo/tcp_header.html