套接字半关闭与关闭 (可参考TCP/IP网络编程)
套接字的半关闭状态: 可以传输但不可接收或可接收但不可传输,即只关闭流的一半.
shutdown()函数的三种断开连接方式:
SHUT_RD: 断开输入流
SHUT_WR: 断开输出流
SHUT_RDWR: 同时断开I/O流
区别:
如果传入SHUT_RD 那么无法接收数据,即使数据到缓冲区了也会抹去并无法调用相关读取函数.
如果传入SHUT_WR 那么无法输出数据了, 但如果输出缓冲中还有未传输的数据会首先传输完毕. [这是与SHUT_RD的区别]. close()才不管缓冲区还有没有内容直接全抹掉.
打算关闭套接字连接时close()和shutdown()都会向客户端发送EOF符号表示传输结束.
而调用close()函数会同时关闭I/O流,并且再也无法接收对方的数据了. 使用shutdown()进行半关闭就比较优雅了, 我可以发送完EOF后等待客户端发送的最后一次数据(SHUT_WD).
比如下面的服务端代码:
while (1) {
read_cnt = fread((void*)buf, 1, BUF_SIZE,fp);
if(read_cnt < BUF_SIZE) {
break; //我输出完毕之后推出循环
}
}
shutdown(client_fd,SHUT_WR); //告诉客户端我不再发数据给你了
read(client_fd, buf, BUF_SIZE); //阻塞在这等客户端最后一次发数据过来.
printf("msg from client:%s
", buf); //打印客户端最后一次数据
fclose(fp);
close(client_fd); //正式关闭客户端套接字
close(server_fd);
closesocket或shutdown(使用SHUT_WR当作参数时),会向通信对方发出一个fin包,
而此时套接字的状态会由ESTABLISHED变成FIN_WAIT_1,然后对方发送一个ACK包作为回应,
套接字又变成FIN_WAIT_2,如果对方也关闭了连接则对方会发出FIN,
我方会回应一个ACK并将套接字置为TIME_WAIT。因此可以看出closesocket,shutdown所进行的TCP行为是一样的,
所不同的是函数部分,shutdown会确保windows建立的数据传输队列中的数据不被丢失,
而closesocket会冒然的抛弃所有的数据,因此如果你愿意closesocket完全可以取代shutdown,
然而在数据交互十分复杂的网络协议程序中,最好还是shutdown稳妥一些!
@1. (ESTABLISHED) -- shutdown -> 服务端(FIN_WAIT_1) --fin-> 客户端 --ack-> 服务端(FIN_WAIT_2)
@2. 客户端 --fin--> 服务端 --ack--> 客户端(TIME_WAIT);
@3. 客户端套接字处于TIME_WAIT 阶段即处于等待回收状态,
windows默认4分钟后自动回收, 既然是资源那有稀缺性,
比如nginx默认可操作1024个文件描述符(套接字), 如果这些TIME_WAIT待回收的或CLOSE_WAIT(调用close()产生待回收)的套接字占用太多而无法及时回收的话,
那么nginx剩下可用于建立ESTABLISHED的套接字资源就不足了, 那么就无法继续接受请求,
于是报"Too Many Open Files异常"或卡主(nginx还必须维护这些TIME_WAIT套接字),
再比如mysql配置max-connections=2048, 一旦没有可用的连接(套接字),mysql就会报too many connections, 因此及时的回收或重新利用这些TIME_WAIT 就显得很关键了.
HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP连接(套接字)传输多个 request/response,一个主要原因就是发现了这个问题. 即告诉套接字别发完一个就像等待被回收了, 继续站岗循环使用吧.
继续看下面的案例怎么通过修改内核配置尽快回收套接字.
vim /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将time-wait sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间