TCP的状态兼谈Close_Wait和Time_Wait的状态
一些查看链接状态的命令
TCP连接中的TIME_WAIT状态
netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
TCP的状态:
LISTEN 首先服务端需要打开一个socket进行监听,状态为LISTEN.
SYN_SENT:客户端通过应用程序调用connect进行active open.于是客户端tcp发送一个SYN以请求建立一个连接.之后状态置为SYN_SENT.
SYN_RECV:服务端应发出ACK确认客户端的SYN,同时自己向客户端发送一个SYN.之后状态置为SYN_RECV
ESTABLISHED: 代表一个打开的连接,双方可以进行或已经在数据交互了
FIN_WAIT1:主动关闭(active close)端(可以是客户端也可以是服务器端)应用程序调用close,于是其TCP发出FIN请求主动关闭连接,之后进入FIN_WAIT1状态
CLOSE_WAIT:被动关闭(passive close)端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入CLOSE_WAIT.
FIN_WAIT2:主动关闭端接到ACK后,就进入了FIN_WAIT2
LAST_ACK:被动关闭端一段时间后,接收到文件结束符的应用程序将调用CLOSE关闭连接。这导致它的TCP也发送一个 FIN,等待对方的ACK.就进入了LAST_ACK
TIME_WAIT:在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME_WAIT状态
CLOSE_WAIT状态解释 CLOSE_WAIT状态的生成原因
通过TCP的状态图我们可以看出只有被动关闭的一端才有CLOSE_WAIT状态,当收到Fin并发送了Ack后
服务器状态就变成了CLOSE_WAIT状态,如果我们的服务器一直处于CLOSE_WAIT状态的话,说明套接字是被动关闭的,并且没有发送Fin信令!原因往往是没有调用TCP的CloseSocket。
解决CLOSE_WAIT的方法:
1 一般原因都是TCP连接没有调用关闭方法。需要应用来处理网络链接关闭。
2 对于Web请求出现这个原因,经常是因为Response的BodyStream没有调用Close.
比如Widnows下:
使用HttpWebRequest 一定要保证GetRequestStream和GetResponse对象关闭,否则容易造成连接处于CLOSE_WAIT状态
3 TCP的KeepLive功能,可以让操作系统替我们自动清理掉CLOSE_WAIT的连接。
但是KeepLive在Windows操作系统下默认是7200秒,也就是2个小时才清理一次。往往满足不了要求。可以调小该数值。
Windows下的调整方法为
HKEY_LOCAL_MACHINE/CurrentControlSet/Services/Tcpip/Parameters下的以下三个参数:
KeepAliveInterval,设置其值为1000 www.2cto.com
KeepAliveTime,设置其值为300000(单位为毫秒,300000代表5分钟)
TcpMaxDataRetransmissions,设置其值为5
Close_Wait引发的问题:
Close_Wait会占用一个连接,网络可用连接小。数量过多,可能会引起网络性能下降,并占用系统非换页内存。 尤其是在有连接池的情况下(比如HttpRequest)
会耗尽连接池的网络连接数,导致无法建立网络连接
#cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
#cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
#cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
// Set
#echo 1000> /proc/sys/net/ipv4/tcp_keepalive_time //多长时间之内没有数据到来就进行探测
#echo 50> /proc/sys/net/ipv4/tcp_keepalive_intvl //发送探测包的间隔
#echo 20> /proc/sys/net/ipv4/tcp_keepalive_probes //尝试次数
在代码中逐个socket设置的方式:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/tcp.h>
/* Set TCP KeepAlive proporties */
int keepAlive = 1; // 开启keepalive属性
int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
int rs = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
如果有码流申请:
tcp 0 0 192.168.1.230:8557 192.168.0.80:49352 ESTABLISHED
tcp 0 0 192.168.1.230:8557 192.168.0.155:1953 ESTABLISHED 1080P
tcp 0 0 192.168.1.230:8556 192.168.0.80:49533 ESTABLISHED 480P
tcp 0 0 192.168.1.230:8555 192.168.0.80:49556 ESTABLISHED 1080P JPEG