【问题标题】:some packets lost in async tcp Socket在异步 tcp 套接字中丢失了一些数据包
【发布时间】:2017-05-30 17:25:18
【问题描述】:

在我的服务器应用程序的网络协议中,我在每个发送的包含长度的缓冲区的开头有 2 个字节。 TcpReadCallback 方法读取 BeginReceive 写入的字节,直到读取完整的缓冲区。但是,使用我的实现,数据包似乎丢失了,有时空缓冲区被发送到 ProcessReceiveBuffer,特别是当我发送消息的间隔小于 5 毫秒时,并且在通过 Internet 发送时更常见(将发生 10 秒左右)每半秒发送一次)。有没有人能发现我在这里做错了什么?

谢谢!

private Socket _tcpWorkSocket;
private byte[] _tcpWorkBuffer;
private byte[] _tcpReceiveBuffer;

public void BeginTcpReceive() {
    if (!Connected)
        return;

    _tcpWorkBuffer = new byte[BufferSize]; // 1024
    _tcpWorkSocket.BeginReceive(_tcpWorkBuffer, 0, BufferSize, 0, new AsyncCallback(TcpReadCallback), this);
}

private void TcpReadCallback(IAsyncResult ar) {
    try {
        if (!Connected)
            return;

        int readBytes = _tcpWorkSocket.EndReceive(ar);
        if (readBytes > 0) {
            byte[] buffer = new byte[readBytes];
            Array.Copy(_tcpWorkBuffer, 0, buffer, 0, readBytes);
            if (User != null)
                User.SessionTimerReset();
            Proccess_Buffer(buffer);
        }
        BeginTcpReceive();
    }
    catch(SocketException ex) {
        Close(false);
    }
    catch (Exception ex) {
        Logger.LogError("{0}: {1}\n{2}", ex.GetType(), ex.Message, ex.StackTrace);
        Close(false);
    }
}

private void Proccess_Buffer(byte[] buffer) {
    if (!Receiving) {
        Receiving = true;
        _tcpPacketLength = BitConverter.ToUInt16(buffer, 0);
        _tcpBytesNeeded = _tcpPacketLength;
        _tcpCurIndex = 0;
        _tcpReceiveBuffer = new byte[_tcpPacketLength];
    }
    int origSize = buffer.Length;
    int copyLength = _tcpBytesNeeded;
    if (_tcpBytesNeeded > BufferSize)
        copyLength = BufferSize;
    Array.Copy(buffer, 0, _tcpReceiveBuffer, _tcpCurIndex, copyLength);
    _tcpCurIndex += buffer.Length;
    _tcpBytesNeeded -= buffer.Length;

    if (_tcpBytesNeeded <= 0) {
        Receiving = false;
        ProcessReceiveBuffer(_tcpReceiveBuffer, Protocal.Tcp);
    }

    if (origSize > buffer.Length) {
        // next packet, incase 2 messages are sent in the same packet.
        byte[] nextBuffer = new byte[buffer.Length - origSize ];
        if (nextBuffer.Length > 1) {
            Array.Copy(buffer, origSize , nextBuffer, 0, nextBuffer.Length);
            Proccess_Buffer(nextBuffer);
        }
        else {
            Logger.LogError("Next buffer length split!");
        }
    }
}

例子:

“received”后的第一个数字是客户端发送的顺序,第二个是大小。当通过 Internet 发送时,TCP 中经常会突然丢弃一些数据包。

[17:29:49]: received: 1349, 514
[17:29:50]: received: 1350, 514
[17:29:50]: received: 1351, 514
[17:29:51]: received: 1352, 514
[17:29:51]: received: 1353, 514
[17:29:52]: received: 1355, 514
[17:29:52]: Skipped! expected 1354, got 1355
[17:29:53]: received: 1357, 514
[17:29:53]: Skipped! expected 1356, got 1357
[17:29:54]: received: 1359, 514
[17:29:54]: Skipped! expected 1358, got 1359
[17:29:56]: received: 1362, 514
[17:29:56]: Skipped! expected 1360, got 1362
[17:29:56]: received: 1363, 514
[17:29:57]: received: 1364, 514

【问题讨论】:

  • 您在 cmets 中的缓冲区大小为 1024。请尝试增大。 TCP 最大数据报大小 ~1500 字节最大。较大的消息被分解成小于 1500 的较小尺寸。路由器和 PC 可以将 1500 分解成更小的部分。您还可以获得包含 0 字节数据的保持活动消息。您的代码必须能够处理所有这些条件。

标签: c# sockets tcp


【解决方案1】:

由于网络拥塞,EndReceive 可能无法一次性收到完整的消息。因此TcpReadCallback 应该重复读取操作,直到不再接收到字节。

这是一个相关的MSDN example,它证明了这一点:

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

related post 讨论了在操作系统级别的基本 tcp socket send()recv() 调用中的类似效果。

【讨论】:

  • 它已经尝试过这样做。 _tcpBytesNeeded 设置为消息的长度(在每条消息的开头发送),并减去接收缓冲区的长度。如果减少后大于 0,则将下一个 TcpReadCallback 应用于该消息。
【解决方案2】:

我已经通过使用https://github.com/Luaancz/Networking 的实现解决了这个问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 2017-01-27
    • 2018-07-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多