socket关闭连接与close和shutdown的区别及优雅关闭socket的方法:SO_LINGER
TCP主动关闭连接
appl: close(), --> FIN FIN_WAIT_1 //主动关闭socket方,调用close关闭socket,发FIN
<-- ACK FIN_WAIT_2 //对方操作系统的TCP层,给ACK响应。然后给FIN
<-- FIN
--> ACK "TIME_WAIT" -- 2MSL timeout -->CLOSED
//TIME_WAIT,防止ACK没有给到对方。
注意:close时,如果TCP发送队列中还有数据,那么将会发送RST包而不是FIN包。
参看:http://rdc.taobao.com/blog/cs/?p=1055
另外:对于Linux下来说,无论是FIN还是RST,应用层read将会返回0,可以认为对方请求关闭链接,调用close关闭fd即可。
TCP被动关闭连接
<-- FIN "CLOSE_WAIT" //被动方,收到对方的FIN,处于CLOSE_WAIT状态
--> ACK //被动方的TCP层,给ACK响应
appl: close(), --> FIN LAST_ACK
//被动方调用close,从CLOSE_WAIT转到LAST_ACK
不调close,将一直在CLOSE_WAIT状态。
<-- ACK --> CLOSED
tcp是全双工::
close()会关闭读写。
shutdown()可以选择性的关闭读、写或读写。
主动关系SOCKET链接的一方,会进入TIME_WAIT(作用是防止最后一个ACK包丢失)
TIME_WAIT的时间会非常长,因此server尽量减少主动关闭连接。
close和shutdown的区别:
int close(int sockfd);
close(fd)调用会将描述字的引用计数减1,只有当socket描述符的引用计数为0时,才关闭socket,即发送FIN包,因此,在fork()模式中,父进程在accept()返回后,fork()子进程,由子进程处理connfd,而父进程将close(connfd);由于connfd这个socket描述符的引用计数不为0,因此并不引发FIN,所以就没有关闭和客户端的连接。
int shutdown(int sockfd, int howto);
// howto: SHUT_RD, SHUT_WR, SHUT_RDWR
shutdown()则不管socket描述符的引用计数,而直接发生FIN,因此会直接关闭链接。
shutdown()可控制read/write两个方向的管道。
SHUT_RD shutdown(sockfd, SHUT_RD);后,来自对端的数据都被确认,然后悄然丢弃。
SHUT_WR half close状态。
close()引发的4次交互:(这里的close是client发起的)
client server
FIN_WAIT_1 ---- FIN M ------>
//(Server端操作系统的TCP层(网络协议栈)响应ACK包)
FIN_WAIT_2 <---- ACK M+1---- CLOSE_WAIT
//(这里必须调用close,才能从CLOSE_WAIT到LAST_ACK)
TIME_WAIT <------ FIN N ----- LAST_ACK
//(TIME_WAIT有一个重要的作用就是防止最后一个ACK丢失)
------- ACK N+1 ----> CLOSE
2020/2/27更新
最近又理解了一下,close是linux中关闭文件的命令,根据linux中任何皆为文件的特性,调用shutdown后还要调用close才能消除操作系统对这个socket的管理
关于shutdown和close的一些说明:
对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出
这个参数对应大量短链接的服务器很有必要!
-
shutdown(fd, SHUT_RDWR);
-
struct linger linger;
-
linger.l_onoff = 1;
-
linger.l_linger = 0;
-
setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
-
close(fd);
linger.l_linger = 0;
setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
close(fd);
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
有下列三种情况:
1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架