共计 1686 个字符,预计需要花费 5 分钟才能阅读完成。
0. 前言
最近在处理公司遗留项目的时候发现自己对 TCP 协议一点都不懂,所以补了点关于 TCP 连接的建立和终止的内容,这里简单写下自己了解的部分,省略了报文序号确认序号这些无关的字段,主要讨论 TCP 状态的转换以及 Linux 下的一些问题。
对于这篇文章来说,主要是记录自己遇到的一些问题以及学习到的一些东西。
关于 TCP/IP 协议,这里推荐一本书:《TCP/IP 协议详解:卷 1》
1. TCP 连接的建立
学过计算机网络的都知道 TCP 连接的建立需要三次握手,当时在大学也这么听着,但是具体怎么三次还是最近补了才知道这么回事。
对于客户端 / 服务器模型来说:
1. 首先客户端发起连接(发送 SYN 报文,进入 SYN_SENT 状态)
2. 服务器接收到 SYN,然后响应(发送 SYN ACK,进入 SYN_RCVD 状态,注意这个时候连接并没有建立)
3. 客户端接受到后,发送确认报文(发送 ACK,进入 Established 状态)
4. 服务器收到后才进入链路建立(Established)状态。
如下图(图为百度搜出来的)
2. TCP 连接的终止
对于 TCP 连接的终止来说,却需要四次挥手来完成,这里以客户 / 服务器模型,客户端主动发起关闭来说明 (这里服务器也可以发起主动发起关闭)。
1. 客户端发送 FIN(进入 FIN_WAIT_1 状态)
2. 服务器接收到 FIN 后对发送 ACK 确认(此时服务器进入 CLOSE_WAIT 状态,客户端接受到该 ACK 后由 FIN_WAIT_1 转为 FIN_WAIT_2 状态)
3. 服务器调用 close 发送 FIN(进入 LASK_ACK 状态)
4. 客户端接收到服务器发送的 FIN 后发送 ACK(进入 TIME_WAIT 状态)
5. 服务器接收到后结束(LASK_ACK 状态转为虚拟的 CLOSED,即已经没有状态了)
如下图所示:
3. TCP 状态转换
其实如果看懂了上面连接的建立以及终止的话,很容易就可以看懂下面的状态转换图,上面两个就可以看做由它拆解出来的。
4. 遇到 CLOSE_WAIT 状态的一些情况
前面说了,最近在维护一些历史遗留的项目,那代码简直是叼炸天了,直接使用两个进程共用一个 select,在 accept 前加上文件锁,select 只监听服务器端 fd,其它 fd 不监听 ….(此处省略一千字),然后现在出现了大量的 CLOSE_WAIT 状态,我在想难道以前就没出现过?奇葩。不过被 DDoS 攻击有些也会出现这种现象。最后讨论了一下午说怎么样才能少改动原来的代码 …. 最后老大拍板,重写 select 部分~^~,使用 epoll 实现。
上面的状态图可以看出,服务器由于接收到客户端发来的 FIN,会进入 CLOSE_WAIT,如果此时没有监听该客户端 fd 并且没有调用 close,那么这时会导致占用的 FD 没有被释放,资源就这么被泄漏掉了,这样也会导致存在大量的 CLOSE_WAIT 状态,以至于后续 FD 消耗完了的时候(一般系统默认 1024),accept 就会失败。(注:这里 CLOSE_WAIT 状态是由于应用程序没有调用 close 导致的,系统不会释放该资源,即会一直存在)
(注:即使 accept 失败,但是对于链路来说,还是能建立成功的,因为对于 Linux TCP 底层实现来说,存在两个队列,一个为半链路队列,另一个为三次握手成功但是没有被服务器 accept 取走的链接的队列。)
后续对于 Linux 下的服务器来说一般都是使用 epoll 来处理,效率比 select 高。
5. 补充
这里看到一些博客说通过设置系统参数来改变 CLOSE_WAIT 的维持等待时间,我查阅了一下,并且也尝试过(其实不用试)。不能够通过设置系统参数来更改的,因为这时应用程序内部导致的,,而且根本就不存在说 CLOSE_WAIT 维持时间一说,该状态只有应用程序调用 close 才会转为其它状态(或者关闭应用程序),这里说通过修改系统参数一般指的时 TIME_WAIT 状态的维持时间。
本文永久更新链接地址 :http://www.linuxidc.com/Linux/2015-09/122777.htm