最近在写一个CS程序的Demo,其中用到了SocketAsyncEventArgs,据说这个封装了IOCP模型,而且使用方便。
确实,在看了MSDN的例子之后,把微软的列子(在msdn查找SocketAsyncEventArgs就能看到例子程序)稍加修改,完善就写好了服务端。但是在完成用户管理功能时,服务端主动关闭客户端的连接也就是踢掉指定用户遇到了麻烦。
1.例子中包含一个CloseSocket函数,但是此函数并不能主动调用,如果主动调用就会在执行此函数的同时触发SocketAsyncEventArgs的Completed事件,并且标志是Receive,接收字符长度是0,因此再次进入CloseSocket函数重复关闭同一个Socket,于是发生错误,程序退出。
2.客户端主动中断时,是直接接收到0长度数据,然后进入CloseSocket,结果就是正常关闭了。
搜索了半天也没找到一个说的清楚一点的。只依稀记得csdn有个帖子中说道,关闭socket之后立刻处理接收事件,然后再关闭socket就是一个graceful close了。但是明显的,这里不能直接在CloseSocket函数中加入ProcessRecv。于是我又想到设个标志,在userToken中增加一个标志位,进入Closesocket函数之后就置标志,如果发生重入则直接退出。结果异常没有了。但是用户被踢掉之后立刻又重连了。体现在用户的SessionID(我给每一个SocketAsyncEventArgs加了一个ID)换了。吐血。。。
晚上看动画片时还一直在琢磨这事,然后又到msdn把Socket类相关的狂看一遍。看到Socket类有Shutdown和Close,突然想到上面的先关闭,然后处理接收,然后关闭Socket。一下子开窍了。果断地,把Try部分单独移出来。然后踢用户时只是shutdown一下,让它自动触发Completed事件进入ProcessRecv中,然后调用CloseSocket。正常关闭了!
下面是代码的对比
改前:
private void ProcessReceive(SocketAsyncEventArgs e) { // check if the remote host closed the connection AsyncUserToken token = (AsyncUserToken)e.UserToken; if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { //increment the count of the total bytes receive by the server Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred); Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead); //echo the data received back to the client e.SetBuffer(e.Offset, e.BytesTransferred); bool willRaiseEvent = token.Socket.SendAsync(e); if (!willRaiseEvent) { ProcessSend(e); } } else { CloseClientSocket(e); } } private void CloseClientSocket(SocketAsyncEventArgs e) { AsyncUserToken token = e.UserToken as AsyncUserToken; // close the socket associated with the client //就是下面这里了 try { token.Socket.Shutdown(SocketShutdown.Send); } // throws if client process has already closed catch (Exception) { } token.Socket.Close(); // decrement the counter keeping track of the total number of clients connected to the server Interlocked.Decrement(ref m_numConnectedSockets); m_maxNumberAcceptedClients.Release(); Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets); // Free the SocketAsyncEventArg so they can be reused by another client m_readWritePool.Push(e); }
改后:
private void CloseClientSocket(SocketAsyncEventArgs e) { AsyncUserToken token = e.UserToken as AsyncUserToken; token.Socket.Close(); // decrement the counter keeping track of the total number of clients connected to the server Interlocked.Decrement(ref m_numConnectedSockets); m_maxNumberAcceptedClients.Release(); Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets); // Free the SocketAsyncEventArg so they can be reused by another client m_readWritePool.Push(e); } private void tryCloseSocket(SocketAsyncEventArgs e) { AsyncUserToken token = e.UserToken as AsyncUserToken; // close the socket associated with the client //就是下面这里了 try { token.Socket.Shutdown(SocketShutdown.Send); } // throws if client process has already closed catch (Exception) { } }
原来的执行顺序是
CloseClientSocket => ProcessReceive => CloseClientSocket(重复关闭)
改过之后是
tryCloseSocket => ProcessReceive => CloseClientSocket (graceful Close)
Oh,Yeah!
现在发现一个问题,有个TCP调试助手还是踢不掉,踢掉会自动重连。而换其他的都可以断开。这个软件截图如下:
这样说来,我之前设置标志位的做法可能是能够成功的。懒的再去测试了。