简体   繁体   English

客户端关闭套接字后,C#tcp异步监听器卡在我的on_receive回调上

[英]C# tcp async listener gets stuck on my on_receive callback after client closes socket

I've got a listener socket that accepts, receives and sends as a TCP server typically does. 我有一个侦听器套接字,它接受,接收和发送通常作为TCP服务器。 I've given my accept and receive code below, it's not that different from the example on Microsoft's documentation . 我已经接受了下面的接受和接收代码,它与微软文档中示例没有什么不同。 The main difference is that my server doesn't kill a connection after it stops receiving data (I don't know if this is a bad design or not?). 主要区别在于我的服务器在停止接收数据后没有终止连接(我不知道这是不是一个糟糕的设计?)。

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);
    }
}

This code is used to connect to an embedded device and works (mostly) fine. 此代码用于连接到嵌入式设备,并且(大部分)工作正常。 I was investigating a memory leak and trying to speed up the process a bit by replicating exactly what the device does (our connection speeds are in the realm of about 70kbps to our device, and it took an entire weekend of stress testing to get the memory leak to double the memory footprint of the server). 我正在调查内存泄漏并尝试通过复制设备的确切速度来加快这一过程(我们的连接速度在我们设备的大约70kbps范围内,并且需要整整一周的压力测试才能获得内存泄漏以使服务器的内存占用量增加一倍)。

So I wrote a C# program to replicate the data transactions, but I've run into an issue where when I disconnect the test program, the server gets caught in a loop where it endlessly has its on_receive callback called. 所以我写了一个C#程序来复制数据事务,但是我遇到了一个问题,当我断开测试程序时,服务器陷入一个循环,它无休止地调用它的on_receive回调。 I was under the impression that BeginReceive wouldn't be triggered until something was received, and it seems to call on_receive , ends the receiving like an async callback should do, process the data, and then I want the connection to await more data so I call BeginReceive again. 我的印象是,在接收到某些内容之前不会触发BeginReceive ,并且它似乎调用on_receive ,结束接收就像异步回调应该做的那样,处理数据,然后我希望连接等待更多数据,所以我再次调用BeginReceive

The part of my test program where the issue occurs is in here: 我的测试程序出现问题的部分在这里:

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();
}

Up until the Shutdown(SocketShutdown.Both) call, everything works as expected. 直到Shutdown(SocketShutdown.Both)调用,一切都按预期工作。 When I make that call however, it seems like the server never gets notification that the client has closed the socket and gets stuck in a loop of endlessly trying to receive. 然而,当我进行该调用时,似乎服务器永远不会收到客户端已关闭套接字并且陷入无休止地试图接收的循环的通知。 I've done my homework and I think I am closing my connection properly as per this discussion . 我完成了我的作业,我认为我正在按照这个讨论正确地关闭我的连接。 I've messed around with the disconnect section to just do mxConnection.Disconnect(false) as well, but the same thing occurs. 我已经搞乱了断开部分,只是做了mxConnection.Disconnect(false) ,但同样的事情发生了。

When the device disconnects from the server, my server catches a SocketException with error code 10054, which documentation says: 当设备与服务器断开连接时,我的服务器捕获到一个错误代码为10054的SocketException ,文档说:

Connection reset by peer. 连接由同行重置。

An existing connection was forcibly closed by the remote host. 远程主机强制关闭现有连接。 This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). 如果远程主机上的对等应用程序突然停止,主机重新启动,主机或远程网络接口被禁用,或者远程主机使用硬关闭,则通常会产生这种情况(有关远程主机上SO_LINGER选项的更多信息,请参阅setsockopt)插座)。 This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. 如果由于保持活动活动在一个或多个操作正在进行时检测到故障而导致连接中断,则也可能导致此错误。 Operations that were in progress fail with WSAENETRESET. 正在进行的操作因WSAENETRESET而失败。 Subsequent operations fail with WSAECONNRESET. 后续操作因WSAECONNRESET而失败。

I've used this to handle the socket being closed and has worked well for the most part. 我已经用它来处理关闭的插座,并且大部分都运行良好。 However, with my C# test program, it doesn't seem like it works the same way. 但是,使用我的C#测试程序,似乎它的工作方式不一样。

Am I missing something here? 我在这里错过了什么吗? I'd appreciate any input. 我很感激任何意见。 Thanks. 谢谢。

You missed this in the documentation for EndReceive() and Receive() : 你在EndReceive()Receive()的文档中错过了这个:

If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes. 如果远程主机使用Shutdown方法关闭Socket连接,并且已收到所有可用数据,则Receive方法将立即完成并返回零字节。

When you read zero bytes, you still start another BeginReceive(), instead of shutting down: 当您读取零字节时,仍然会启动另一个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);

Since you keep calling BeginReceive on a socket that's 'shutdown', you're going to keep getting callbacks to receive zero bytes. 由于你继续在一个'shutdown'的套接字上调用BeginReceive,你将继续获得回调来接收零字节。

Compare with the example from Microsoft in the documentation for EndReceive() : 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();
    }
}

The main difference is that my server doesn't kill a connection after it stops receiving data (I don't know if this is a bad design or not?). 主要区别在于我的服务器在停止接收数据后没有终止连接(我不知道这是不是一个糟糕的设计?)。

Of course it is. 当然如此。

it seems like the server never gets notification that the client has closed the socket and gets stuck in a loop of endlessly trying to receive 似乎服务器永远不会收到客户端关闭套接字的通知,并且陷入无休止地试图接收的循环中

The server does get notification. 服务器确实收到通知。 It's just that you ignore it. 只是你忽略了它。 The notification is that your receive operation returns 0 . 通知是您的接收操作返回0 When that happens, you just call BeginReceive() again. 当发生这种情况时,您只需再次调用BeginReceive() Which starts a new read operation. 这会启动一个新的读操作。 Which…returns 0 ! 哪个...返回0 You just keep doing that over and over again. 你只是一遍又一遍地这样做。

When a receive operation returns 0 , you're supposed to complete the graceful closure (with a call to Shutdown() and Close() ) that the remote endpoint started. 当接收操作返回0 ,您应该完成远程端点启动的正常关闭(通过调用Shutdown()Close() )。 Do not try to receive again. 不要试图再次接收。 You'll just keep getting the same result. 你会得到相同的结果。

I strongly recommend you do more homework. 我强烈建议你做更多的功课。 A good place to start would be the Winsock Programmer's FAQ . 一个好的起点是Winsock程序员的常见问题解答 It is a fairly old resource and doesn't address .NET at all. 它是一个相当古老的资源,根本不涉及.NET。 But for the most part, the things that novice network programmers are getting wrong in .NET are the same things that novice Winsock programmers were getting wrong twenty years ago. 但在大多数情况下,新手网络程序员在.NET中出错的事情与二十年前新手Winsock程序员出错的情况相同。 The document is still just as relevant today as it was then. 该文件今天仍然与当时一样重要。

By the way, your client-side code has some issues as well. 顺便说一句,您的客户端代码也存在一些问题。 First, when the Connect() method returns successfully, the socket is connected. 首先,当Connect()方法成功返回时,套接字已连接。 You don't have to check the Connected property (and in fact, should never have to check that property). 您不必检查Connected属性(事实上, 永远不必检查该属性)。 Second, the Disconnect() method doesn't do anything useful. 其次, Disconnect()方法没有做任何有用的事情。 It's used when you want to re-use the underlying socket handle, but you should be disposing the Socket object here. 当你想重用底层套接字句柄时使用它,但是你应该在这里处理Socket对象。 Just use Shutdown() and Close() , per the usual socket API idioms. 按照通常的套接字API惯用法,使用Shutdown()Close() Third, any code that receives from a TCP socket must do that in a loop, and make use of the received byte-count value to determine what data has been read and whether enough has been read to do anything useful. 第三,从TCP套接字接收的任何代码必须在循环中执行此操作,并利用接收的字节计数值来确定已读取的数据以及是否已读取足够的数据以执行任何有用的操作。 TCP can return any positive number of bytes on a successful read, and it's your program's job to identify the start and end of any particular blocks of data that were sent. TCP可以在成功读取时返回任何正数字节,并且程序的工作是识别发送的任何特定数据块的开始和结束。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM