Chinaunix首页 | 论坛 | 博客
  • 博客访问: 526941
  • 博文数量: 28
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3824
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-27 00:06
个人简介

一直从事高性能高并发服务器研究

文章分类

全部博文(28)

文章存档

2013年(28)

分类: LINUX

2013-09-12 02:33:13

    终止一个连接要经过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)将不会影响到其它进程.


阅读(12183) | 评论(6) | 转发(12) |
给主人留下些什么吧!~~

astezheng2013-09-25 23:54:50

引用已经删除!!!

回复 | 举报

astezheng2013-09-18 19:14:08

受教了,一直以来我都希望学习这样的涉及底层细节的技术,博主的文章通俗易懂,让困扰我很久的一些问题得到了答案
现在我知道我写的程序在下层的具体行为了,网络程序的机理基本上了然于胸了
还请博主多多赐教!

joepayne2013-09-12 22:19:00

weizhulinux:是的,多进程模式的标准处理确实如朋友所说,不过这样做就不会出现这个CLOSE_WAIT的问题了:)
我只是引入这个问题,让他在子进程的模块里面处理罢了
非常感谢你的回复,希望以后多多交流

呵 我也是菜鸟一个 上面例举的一个模型只是一种最典型的,其实服务器端还有其它的模型  多多交流

回复 | 举报

weizhulinux2013-09-12 20:27:34

joepayne:博主分析得不错!下面说一下个人见解:
拿服务端来说,有一种做法是主进程用那些复用接口来监测客户端发来的连接请求,之后开辟业务子进程来处理请求,在业务子进程中首先CLOSE掉监听套接字(文件表中该套接字所对应的引用数减1),然后接下来ACCEPT啊什么的,最后在子进程终止先CLOSE或者SHUTDOWN掉连接套接字,监听套接字的关闭就交给主进程了。通俗一点儿说就是:监听套接字由主进程打理,连接套接字由业务子进程打理,监听的套字应该不会太多,所以应该就不会有博主提到的问题了。

是的,多进程模式的标准处理确实如朋友所说,不过这样做就不会出现这个CLOSE_WAIT的问题了:)
我只是引入这个问题,让他在子进程的模块里面处理罢了
非常感谢你的回复,希望以后多多交流

回复 | 举报

joepayne2013-09-12 17:09:19

博主分析得不错!下面说一下个人见解:
拿服务端来说,有一种做法是主进程用那些复用接口来监测客户端发来的连接请求,之后开辟业务子进程来处理请求,在业务子进程中首先CLOSE掉监听套接字(文件表中该套接字所对应的引用数减1),然后接下来ACCEPT啊什么的,最后在子进程终止先CLOSE或者SHUTDOWN掉连接套接字,监听套接字的关闭就交给主进程了。通俗一点儿说就是:监听套接字由主进程打理,连接套接字由业务子进程打理,监听的套字应该不会太多,所以应该就不会有博主提到的问题了。