异步 TCP 套接字中的某些数据包丢失



在我的服务器应用程序的网络协议中,我在每个发送的缓冲区的开头都有 2 个字节,其中包含长度。TcpReadCallback 方法读取 BeginReceive 写入的字节,直到读取完整的缓冲区。但是,使用我的实现,数据包似乎丢失了,有时空缓冲区会发送到 ProcessReceiveBuffer,尤其是当我发送相隔不到 5 毫秒的消息时,在通过互联网发送时更常见(每 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!");
        }
    }
}

例:

"已收到"后的第一个数字是客户端发送的订单,第二个是大小。当通过互联网发送时,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

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

下面是一个相关的 MSDN 示例,它演示了这一点:

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

操作系统级别的基本tcp socket send()recv()调用中,将在相关文章中讨论类似的效果。

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

最新更新