现在的位置: 首页 > 综合 > 正文

IOCP 服务端主动关闭连接

2014年02月27日 ⁄ 综合 ⁄ 共 3070字 ⁄ 字号 评论关闭

          最近在写一个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调试助手还是踢不掉,踢掉会自动重连。而换其他的都可以断开。这个软件截图如下:

这样说来,我之前设置标志位的做法可能是能够成功的。懒的再去测试了。

抱歉!评论已关闭.