客户端关闭套接字

时间:2017-10-17 17:08:58

标签: c# sockets tcp

我有一个侦听器套接字,它接受,接收和发送通常是TCP服务器。我已经接受了下面的接受和接收代码,它与example on Microsoft's documentation没有什么不同。主要的区别是我的服务器在停止接收数据后没有终止连接(我不知道这是不是一个糟糕的设计?)。

private void on_accept(IAsyncResult xResult)
{
    Socket listener = null;
    Socket handler = null;
    TStateObject state = null;
    Task<int> consumer = null;

    try
    {
        mxResetEvent.Set();
        listener = (Socket)xResult.AsyncState;
        handler = listener.EndAccept(xResult);
        state = new TStateObject()
        {
            Socket = handler
        };
        consumer = async_input_consumer(state);
        OnConnect?.Invoke(this, handler);
        handler.BeginReceive(state.Buffer, 0, TStateObject.BufferSize, 0, new AsyncCallback(on_receive), state);
    }
    catch (SocketException se)
    {
        if (se.ErrorCode == 10054)
        {
            on_disconnect(state);
        }
    }
    catch (ObjectDisposedException)
    {
        return;
    }
    catch (Exception ex)
    {
        System.Console.WriteLine("Exception in TCPServer::AcceptCallback, exception: " + ex.Message);
    }
}

private void on_receive(IAsyncResult xResult)
{
    Socket handler = null;
    TStateObject state = null;
    try
    {
        state = xResult.AsyncState as TStateObject;
        handler = state.Socket;
        int bytesRead = handler.EndReceive(xResult);
        UInt16 id = TClientRegistry.GetIdBySocket(handler);
        TContext context = TClientRegistry.GetContext(id);

        if (bytesRead > 0)
        {
            var buffer_data = new byte[bytesRead];
            Array.Copy(state.Buffer, buffer_data, bytesRead);
            state.BufferBlock.Post(buffer_data);
        }
        Array.Clear(state.Buffer, 0, state.Buffer.Length);
        handler.BeginReceive(state.Buffer, 0, TStateObject.BufferSize, 0, new AsyncCallback(on_receive), state);
    }
    catch (SocketException se)
    {
        if(se.ErrorCode == 10054)
        {
            on_disconnect(state);
        }
    }
    catch (ObjectDisposedException)
    {
        return;
    }
    catch (Exception ex)
    {
        System.Console.WriteLine("Exception in TCPServer::ReadCallback, exception: " + ex.Message);
    }
}

此代码用于连接嵌入式设备,并且(大部分)工作正常。我正在调查内存泄漏并尝试通过复制设备的确切速度来加快这一过程(我们的连接速度在我们设备的大约70kbps范围内,并且需要整整一周的压力测试才能获得内存泄漏以使服务器的内存占用量翻倍。)

所以我写了一个C#程序来复制数据事务,但是我遇到了一个问题,当我断开测试程序时,服务器陷入一个循环,它无休止地调用它的on_receive回调。我的印象是BeginReceive在收到内容之前不会被触发,它似乎调用on_receive,结束接收就像异步回调应该做的那样,处理数据,然后我想要等待更多数据的连接,所以我再次呼叫BeginReceive

我的测试程序出现问题的部分在这里:

private static void read_write_test()
{
    mxConnection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    mxConnection.Connect("12.12.12.18", 10);
    if (mxConnection.Connected)
    {
        byte[] data = Encoding.ASCII.GetBytes("HANDSHAKESTRING"); //Connect string
        int len = data.Length;
        mxConnection.Send(data);
        data = new byte[4];
        len = mxConnection.Receive(data);

        if (len == 0 || data[0] != '1')
        {
            mxConnection.Disconnect(false);
            return;
        }
    }

    //Meat of the test goes here but isn't relevant

    mxConnection.Shutdown(SocketShutdown.Both);
    mxConnection.Close();
}

直到Shutdown(SocketShutdown.Both)来电,一切都按预期工作。然而,当我进行该调用时,似乎服务器永远不会收到客户端已关闭套接字并且陷入无休止地试图接收的循环的通知。我完成了我的作业,我认为我按照this discussion正确关闭了我的连接。我已经搞乱了断开部分,只做了mxConnection.Disconnect(false),但同样的事情发生了。

当设备与服务器断开连接时,我的服务器会捕获SocketException,错误代码为10054,文档说明:

  

通过对等方重置连接。

     

强行关闭现有连接   由远程主机。这通常会导致对等应用程序打开   远程主机突然停止,主机重启,主机或   远程网络接口已禁用,或远程主机使用硬盘   close(有关SO_LINGER选项的更多信息,请参阅setsockopt)   远程插座)。如果连接是,也可能导致此错误   由于保持活动活动在一个或多个时检测到故障而中断   更多的业务正在进行中。正在进行的操作失败   与WSAENETRESET。后续操作因WSAECONNRESET而失败。

我用它来处理被关闭的套接字,并且大部分都运行良好。但是,使用我的C#测试程序,似乎它的工作方式不一样。

我在这里遗漏了什么吗?我很感激任何意见。感谢。

2 个答案:

答案 0 :(得分:1)

您在EndReceive()Receive()

的文档中错过了此操作
  

如果远程主机使用Shutdown方法关闭Socket连接,并且已收到所有可用数据,则Receive方法将立即完成并返回零字节。

当你读取零字节时,你仍然会启动另一个BeginReceive(),而不是关闭:

    if (bytesRead > 0)
    {
        var buffer_data = new byte[bytesRead];
        Array.Copy(state.Buffer, buffer_data, bytesRead);
        state.BufferBlock.Post(buffer_data);
    }
    Array.Clear(state.Buffer, 0, state.Buffer.Length);
    handler.BeginReceive(state.Buffer, 0, TStateObject.BufferSize, 0, new AsyncCallback(on_receive), state);

由于你继续在一个'shutdown'的套接字上调用BeginReceive,你将继续获得回调来接收零字节。

EndReceive()的文档中与Microsoft的示例进行比较:

public static void Read_Callback(IAsyncResult ar){
    StateObject so = (StateObject) ar.AsyncState;
    Socket s = so.workSocket;

    int read = s.EndReceive(ar);

    if (read > 0) {
            so.sb.Append(Encoding.ASCII.GetString(so.buffer, 0, read));
            s.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, 
                                     new AsyncCallback(Async_Send_Receive.Read_Callback), so);
    }
    else{
         if (so.sb.Length > 1) {
              //All of the data has been read, so displays it to the console
              string strContent;
              strContent = so.sb.ToString();
              Console.WriteLine(String.Format("Read {0} byte from socket" + 
                               "data = {1} ", strContent.Length, strContent));
         }
         s.Close();
    }
}

答案 1 :(得分:1)

  

主要区别在于我的服务器在停止接收数据后没有终止连接(我不知道这是不是一个糟糕的设计?)。

当然是。

  

似乎服务器永远不会收到客户端关闭套接字的通知,并且陷入无休止地尝试接收的循环中

服务器 获取通知。只是你忽略了它。通知是您的接收操作返回0。当发生这种情况时,您只需再次致电BeginReceive()。这会启动一个新的读操作。哪个...返回0!你只是一遍又一遍地这样做。

当接收操作返回0时,您应该完成远程端点启动的正常闭包(通过调用Shutdown()Close())。 尝试再次接收。你只会得到相同的结果。

我强烈建议你做更多的功课。一个好的起点是Winsock Programmer's FAQ。它是一个相当古老的资源,根本不涉及.NET。但在大多数情况下,新手网络程序员在.NET中出错的事情与二十年前新手Winsock程序员出错的情况相同。该文件今天仍然与当时一样重要。

顺便说一句,您的客户端代码也有一些问题。首先,当Connect()方法成功返回时,套接字已连接。您不必检查Connected属性(事实上,从不必须检查该属性)。其次,Disconnect()方法没有做任何有用的事情。当您想要重用底层套接字句柄时使用它,但您应该在此处置Socket对象。根据通常的套接字API惯用法,只需使用Shutdown()Close()即可。第三,从TCP套接字接收的任何代码必须在循环中执行此操作,并利用接收到的字节计数值来确定已读取的数据以及是否已读取足够的数据以执行任何操作有用。 TCP可以在成功读取时返回任何正数字节,并且程序的工作是识别已发送的任何特定数据块的开始和结束。