TCP之半关闭与CLOSE_WAIT
终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递,可理解为两个方向相反的独立通道),因此每个方向必须单独地进行关闭。
这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,内核让read返回0来通知应用层另一端已经终止了向本端的数据传送。发送FIN通常是应用层对socket进行关闭的结果。
例如:TCP客户端发送一个FIN,用来关闭从客户到服务器的数据传送。
半关闭对服务器究竟有什么影响呢?先看看下面的TCP状态转化图
tcp状态装换图
客户端主动关闭时,发出FIN包,收到服务器的ACK,客户端停留在FIN_WAIT2状态。而服务端收到FIN,发出ACK后,停留在COLSE_WAIT状态。
这个CLOSE_WAIT状态非常讨厌,它持续的时间非常长,服务器端如果积攒大量的COLSE_WAIT状态的socket,有可能将服务器资源耗尽,进而无法提供服务。
那么,服务器上是怎么产生大量的失去控制的COLSE_WAIT状态的socket呢?我们来追踪一下。
一个很浅显的原因是,服务器没有继续发FIN包给客户端。
服务器为什么不发FIN,可能是业务实现上的需要,现在不是发送FIN的时机,因为服务器还有数据要发往客户端,发送完了自然就要通过系统调用发FIN了,这个场景并不是上面我们提到的持续的COLSE_WAIT状态,这个在受控范围之内。
那么究竟是什么原因呢,咱们引入两个系统调用close(sockfd)和shutdown(sockfd,how)接着往下分析。
在这儿,需要明确的一个概念---- 一个进程打开一个socket,然后此进程再派生子进程的时候,此socket的sockfd会被继承。socket是系统级的对象,现在的结果是,此socket被两个进程打开,此socket的引用计数会变成2。
继续说上述两个系统调用对socket的关闭情况。
调用close(sockfd)时,内核检查此fd对应的socket上的引用计数。如果引用计数大于1,那么将这个引用计数减1,然后返回。如果引用计数等于1,那么内核会真正通过发FIN来关闭TCP连接。
调用shutdown(sockfd,SHUT_RDWR)时,内核不会检查此fd对应的socket上的引用计数,直接通过发FIN来关闭TCP连接。
现在应该真相大白了,可能是服务器的实现有点问题,父进程打开了socket,然后用派生子进程来处理业务,父进程继续对网络请求进行监听,永远不会终止。客户端发FIN过来的时候,处理业务的子进程的read返回0,子进程发现对端已经关闭了,直接调用close()对本端进行关闭。实际上,仅仅使socket的引用计数减1,socket并没关闭。从而导致系统中又多了一个CLOSE_WAIT的socket。。。
如何避免这样的情况发生?
子进程的关闭处理应该是这样的:
shutdown(sockfd, SHUT_RDWR);
close(sockfd);
这样处理,服务器的FIN会被发出,socket进入LAST_ACK状态,等待最后的ACK到来,就能进入初始状态CLOSED。
补充一下shutdown()的函数说明
linux系统下使用shutdown系统调用来控制socket的关闭方式
int shutdown(int sockfd,int how);
参数 how允许为shutdown操作选择以下几种方式:
SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后被丢弃。
SHUT_WR:关闭连接的写端。
SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR
注意:
在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会影响到其它进程.
TCP之半关闭与CLOSE_WAIT的更多相关文章
- 深入浅出TCP之半关闭与CLOSE_WAIT
转自:https://www.2cto.com/net/201309/243585.html(相关链接) 深入浅出TCP之半关闭与CLOSE_WAIT 终止一个连接要经过4次握手.这由TCP的半关闭( ...
- TCP/IP编程——基于TCP的半关闭
在TCP服务端和客户端建立连接之后服务端和客户端会分别有两个独立的输入流和输出流,而且相互对应.服务端的输出流对应于客户端的输入流,服务端的输入流对应于客户端的输出流.这是在建立连接之后的状态. 当我 ...
- Linux:TCP状态/半关闭/2MSL/端口复用
TCP状态 CLOSED:表示初始状态. LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接. SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行 ...
- 理解tcp顺序释放操作和tcp的半关闭
Shutdown的调用 在关闭socket的时候,可以有两种方式closesocket和shutdown,这两个函数的区别在什么地方呢? #include <sys/socket. ...
- 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景
欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...
- TCP连接建立与关闭
http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd TCP连接建立与关闭 TCP 是一个面向连接的协议,无论哪一方向另一 ...
- TCP系列04—连接管理—3、TCP连接的半打开和半关闭
在前面部分我们我们分别介绍了三次握手.四次挥手.同时打开和同时关闭,TCP连接还有两种场景分别是半打开(Half-Open)连接和半关闭(Half-Close)连接.TCP是一个全双工(Full-Du ...
- TCP连接的关闭
原文地址:http://lib.csdn.net/article/computernetworks/17264 TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们 ...
- 深入理解TCP建立和关闭连接
建立连接: 理解:窗口和滑动窗口TCP的流量控制TCP使用窗口机制进行流量控制什么是窗口?连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端 接收方发送的确认信息中包含了自 ...
随机推荐
- codeforces上某题
一道codeforces上的题目. 题目大意: 定义有k个不同的字符的字符串为好字符串.现在给出一个字符串,求解对该字符串的每个前缀Si至少是多少个好字符串的连接,若不能由好字符串连接而成则输出-1. ...
- INSPIRED启示录 读书笔记 - 第33章 新瓶装老酒
精明的公司至少要手握两件“法宝” 1.对目标市场了如指掌,对现有产品的缺陷洞若观火 2.跟踪最亲的技术趋势.新技术层出不穷,让之前无法实现的方案变得可能
- 報錯:One or more validation errors were detected during model generation:System.Data.Edm.EdmEntityType: : EntityType 'Movie' has no key
報錯:One or more validation errors were detected during model generation:System.Data.Edm.EdmEntityType ...
- Flume之核心架构深入解析
我们一起来了解Source.Channel和Sink的全链路过程. 一.Flume架构分析 这个图中核心的组件是: Source,ChannelProcessor,Channel,Sink.他们的关系 ...
- Java中使用注释
在编写程序时,经常需要添加一些注释,用以描述某段代码的作用. 一般来说,对于一份规范的程序源代码而言,注释应该占到源代码的 1/3 以上.因此,注释是程序源代码的重要组成部分,一定要加以重视哦! Ja ...
- C++两种字符串传参构造函数
第一种: #include"iostream" #include"string" using namespace std; class Motor{ prote ...
- 深入理解Servlet3.0异步请求
异步请求的基础概念 异步请求最直接的用法就是处理耗时业务,Http协议是单向的,只能客户端拉不能服务器主推. 异步请求的核心原理主要分为两大类:1.轮询.2长连接 轮询:就是定时获取返回结果. 长连接 ...
- DB处理大量数据处理日志报错问题
因为当插入.更新或删除大批量数据的时候,有时候会出现事务日志满的问题,所以解决步骤 1.连接到当前数据库 db2 connect to uppdb 2.查看数据库配置文件 db2 get db cf ...
- Spring Cloud Ribbon实现客户端负载均衡
1.构建microservice-consumer-movie-ribbon项目,在pom.xml中引入ribbon依赖 在引入Eureka依赖的时候,默认里面含有ribbon依赖 2.添加@Load ...
- review37
线程的常用方法 1.start() 线程调用该方法将启动线程,使之从新建状态进入就绪队列排队. 2.run() 3.sleep() 4.isAlive() 线程处于新建状态时,线程调用isAlive( ...