IOCP怎么正确关闭socket

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IOCP怎么正确关闭socket相关的知识,希望对你有一定的参考价值。

如果服务端的Socket比客户端的Socket先关闭,会导致客户端出现TIME_WAIT状态,占用系统资源。

所以,必须等客户端先关闭Socket后,服务器端再关闭Socket才能避免TIME_WAIT状态的出现。
判断客户端Socket的关闭
最近试验发现,当客户端Socket关闭时,服务端的Socket会接收到0字节的通知。
private int Receive(StringBuilder sb)

int read = 0, total = 0;
if (_Client != null)

try

byte[] bytes = new byte[SIZE];
int available = _Client.Available;
do

read = _Client.Receive(bytes);//如果客户端Socket关闭,_Client会接受到read=0
total += read;
if (read > 0)
sb.Append(_Server.DefaultEncoding.GetString(bytes, 0, read));

while (read > 0 && total < available);

catch (SocketException)

CloseSocket();


if (_Server.TraceInConsole && total > 0)

Console.WriteLine("Receive:" + total + "======================================");
Console.WriteLine(sb.ToString());

return total;

利用0字节接收条件判断客户端Socket的关闭,开始执行服务端Socket关闭代码。
private void ThreadHandler()

if (_Server.TraceInConsole)
Console.WriteLine("Begin HttpRequest...");
try

while (true)

StringBuilder sb = new StringBuilder();
int receive = Receive(sb);
if (receive > 0)

_Server.ReadRequest(this, sb.ToString());
_Server.Response(this);
_Server.ResponseFinished(this);

else

TryCloseSocket();

if (_Client == null)
break;


catch (Exception ex)

if (_Server.TraceInConsole)
Console.WriteLine(ex.Message);

if (_Server.TraceInConsole)
Console.WriteLine(" www.hbbz08.com End HttpRequest.");

服务端Socket的关闭
如果直接调用Socket的Close方法会关闭得太快,可能导致客户端TIME_WAIT现象;而Thead.Sleep延时再调用Socket的Close方法也不理想。应该采用尝试向客户端发送数据,然后利用异常来关闭Socket,方法如下。
private void TryCloseSocket()

try

while (true)

Thread.Sleep(1500);
Send(HttpServer.BYTES_CRLF); //发送自定义的字节,如果客户端关闭出现SocketException,然后关闭服务端Socket
if (_Client == null)
break;


catch (SocketException)

CloseSocket();



private void CloseSocket()

if (_Client != null)

_Client.Shutdown(SocketShutdown.Both);
_Client.Close();
_Client = null;
if (_Server.TraceInConsole)

Console.WriteLine("Close socket.");


参考技术A   PostQueuedCompletionStatus之后,在GetQueuedCompletionStatus中closesocket

Windows完成端口 IOCP模型

1 Windows完成端口基本介绍


2他是只能在Windows下的基于SOCKET事件管理的模型

3与select不同,select需要多次重置管理句柄,IOCP只要一次

4有事件后select需要操作获取数据,而IOCP通知你的时候说明数据操作好了

5select管理句柄的数目有限,IOCP没有限制

6IOCP支持多线程同时等待。


我的设计思路一个线程用来侦听accept事件, 一个线程来侦听SOCKET的IO事件,

大部分框架都是这样, 其实可以只使用一个线程做异步SOCKET就完全足够了,现在

使用多线程来 就是看看这个IOCP的多线程用法,


在这之前先要了解下 异步通信 和 重叠I/O模型

异步通信机制: http://blog.51cto.com/blogger/draft/412685

重叠I/O模型: http://blog.51cto.com/12158490/2058180


我建议使用单线程 原因: 应为多个线程,拿到数据后,还是要发送到另外一个线程里面去,

然后做成事件队列, 也就是你线程拿到数据后, 还是要按队列线程,进行逻辑处理,多线程没意义。



2完成端口内部运行流程

完成端口的做法:事先开好几个线程,你有几个CPU我就开几个,首先是避免了线程的上下文切换,因为线程想要

执行的时候,总有CPU资源可用,然后让几个线程等着,等到有用户请求到来的时候,就把这些请求都加入到一个

公共消息队列中, 然后这几个开好的线程就排队逐一去从消息队列中取出消息并加以处理, 这种方式实现了

异步通信和负载均衡的问题,因为他提供了一种机制来使用个线程"公平的"处理来自多个客户端的输入/输出

,并且线程如果没事干的时候,也会被系统挂起,不会占用CPU



3WSAAsyncSelect或者WSAEventSelect和完成端口

WSAAsyncSelect或者WSAEventSelect两种异步模型, 这两种模式一定没有使用overlapped(重叠)机制,

就不能算是真正的异步,可能是其内部维护了一个消息队列,这两个模式虽然实现了异步接收,却不能异步的发送,

完成端口他是先把用户数据接收回来后在通知用户直接来取,而这两种模式只会接到数据到达的通知,只能是由

应用程序自己去recv数据,性能上就发送了差异

要实现异步通信, 就要一个很强的I/O数据结构,叫重接结"Overtlapped",

Window所有异步通信都是基于他,完成端口也不例外

就是执行I/O请求的时间与线程执行其他任务的事件是重叠(overlapped)的,


重叠结构是异步通信机制实现的一个核心数据结构,因为几乎所有的网络操作例如发送/接收,都要用

WSASend()和WSARecv()代替,参数里面都会要附带一个重叠结构,因为重叠机构可以理解为是一个

网络操作的ID号,也就是说我们要利用重叠I/O提供的异步机制的话,每一个网络操作都要有一个唯一的ID号,

因为进来系统内核,一看到有重叠的I/O的调用进来了,就会使用其异步机制,并且操作系统就只能靠

这个重叠结构带有的ID号来区分是哪一个网络操作了,然后内核里处理完毕, 根据这个ID号,把对应的数据传上去.




4完成端口基本的使用流程 

完成端口也分步骤的

1调用CreateIoCompletionPort()函数创建一个完成端口,而且在一般情况下,我们需要并且只需要

建立一个完成端口,把他的句柄保存好, 之后只要使用这一个句柄就行了。

2根据和客户的I/O操作最好是自己建一个工作线程

3接入Socket连接, 有两种方式,1是和别的模型一样,有一个独立的线程,专门来accept客户端的连接,

二是用性能更好的异步AcceptEx()请求

4每当有客户端进来的时候,还是调用CreateIoCompletionPort()函数,这里不是建立完成端口,

而是把新连入的Socket句柄, 和你创建的完成端口绑定在一起。

5客户端连入后,我们要向这个客户端Socket提交一个请求,如接收文件要调用WSARecv()然后系统

就会去执行接收数据的操作, 就不用我们管了。

6 然后就要调用GetQueuedCompletionStatus()(是一个阻塞函数)里面是扫描端口的队列里是否有

网络通信的请求存在(例如读取数据,发送数据等),一旦有,就会将这个请求从完成端口的队列取回来,

继续执行本线程的后续代码,处理完毕后, 必须要再次 投递网络通信请求(WSARecv),如此循环。




5AcceptEx和Accept的区别

AcceptEx和Accept最大的区别,就是取消了阻塞方式的accept调用,也就是说AccentEx也是通过

完成端口来异步完成的。

这样做的好处就是:如果短时间内客户端并发连接请求不是很多,accept和AcceptEx在性能上区别不大,

虽然我们创建Socket只用一行SOCKET s =socket(...)一行代码,但是系统内部建立一个Socket是相当

耗费资源的,因为Winsock2是分层的机构体系,创建一个Socket需要用到多个Provider之间进行处理,

最终形成一个可用的套接字,总之,创建一个Socket的开心是相当高的。

AcceptEx比Accept强三点:

(1)最关键的是AcceptEx在客户端连入之前,就把客户端的Socket建立好了,也就是说,AcceptEx是先建立

Socket,然后发出的AcceptEx调用,也就是说,在进行客户端的通信之前,无论是否有客户端连入,

Socket都是提前建立好的,而不需要想accept是在客户端连入之后,在现场话费时间建立Socket,

(2)相比Accept只能阻塞方式建立一个连入的入口,对于大量的并发客户端来讲,是在是有点挤,

而AcceptEx可以在完成端口上投递多个请求,还有客户端连入的时候,就非常优雅而且从容.

(3)AcceptEx还有一个优点,就是投递AccepEx的时候,收取客户端发来的第一组数据,这是同时

进行的, 也就意味着,如果客户端只是连入但不发送数据的话,我们就不会收到这个AccepeEx完成的通知,

异步的AcceptEx使用起来比accept要麻烦。



由于时间关系,在下篇博客 会详细介绍完成端口的用法

实现图片中的功能

技术分享图片






参考博客地址 http://www.cnblogs.com/lancidie/archive/2011/12/19/2293773.html




以上是关于IOCP怎么正确关闭socket的主要内容,如果未能解决你的问题,请参考以下文章

发送后如何正确关闭套接字(使用 IOCP)?

怎么在服务器端关闭websocket连接

IOCP 的内存使用情况[关闭]

关闭和清理 Socket 连接的正确方法是啥?

ServerSocket卡在accept的时候怎么正确关闭

TCP Socket 未正确关闭