C# 套接字:异步接收损坏接收缓冲区

C# socket: async receiving corrupts receiving buffer

[问题]

System.Net.Sockets.Socket 的 async 接收破坏了接收缓冲区,将随机突发的字节注入到接收数据的中间。

[背景]

我有一个 TCP 连接服务器。客户端与服务器建立套接字连接,并向服务器发送加密数据包。

最初,服务器使用线程操作。每个传入连接都由专用线程提供服务。通过套接字发送和接收的所有缓冲区都分配在堆上并收集垃圾。一切正常。

然后,为了提高性能,我做了两个改变:

  1. 我没有在堆上分配缓冲区,而是使用 ArrayPool.Shared.Rent().

    进行分配
  2. 我把socket的收发改成了async.

我没有更改数据包序列化、反序列化、加密和解密的方式。

更改后,我的开发PC一切正常。当部署到 AWS 上的生产服务器时,当数据包很小(50 字节左右)时,一切都很好。

当数据包很大时,每个 16 KB,服务器将正常接收一些这样的数据包,然后,随机地,接收到的数据包中的一个将被损坏,突发一千个左右的未知字节在接收到的数据中间注入,导致AES解密抛出“无效填充”异常:

我不知道注入的额外字节是从哪里来的。

socket收发代码如下:

    public static Task<int> ReceiveBytesAsync(this Socket socket, SocketAsyncEventArgs args) // , int offset, int count)
    {
        var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
        EventHandler<SocketAsyncEventArgs> handler = null;

        handler = (s, e) =>
        {
            args.Completed -= handler;

            if (args.SocketError != SocketError.Success)
                tcs.SetException(new InvalidOperationException(args.SocketError.ToString()));
            else
                tcs.SetResult(args.BytesTransferred);
        };

        args.Completed += handler;

        if (!socket.ReceiveAsync(args))
        {
            args.Completed -= handler;
            if (args.SocketError != SocketError.Success)
                tcs.SetException(new InvalidOperationException(args.SocketError.ToString()));
            else
                tcs.SetResult(args.BytesTransferred);
        }

        return tcs.Task;
    }

    private static async Task<bool> ReadBufferFromSocketAsync(Socket socket, SocketAsyncEventArgs socketArgs) // , int offset, int length)
    {
        for (int bytesRead = 0; bytesRead < socketArgs.Count;)
        {
            // If bytesRead is 0, SetBuffer would have been called by the caller of this method.
            if (bytesRead > 0)
                socketArgs.SetBuffer(socketArgs.Offset + bytesRead, socketArgs.Count - bytesRead);

            int nRead = await socket.ReceiveBytesAsync(socketArgs);
            bytesRead += nRead;

            if (nRead == 0)
            {
                socket.Close();
                return false;
            }
        }

        return true;
    }

    public static Task<int> SendBytesAsync(this Socket socket, SocketAsyncEventArgs args) // , int offset, int count)
    {
        var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
        EventHandler<SocketAsyncEventArgs> handler = null;

        handler = (s, e) =>
        {
            args.Completed -= handler;
            if (args.SocketError != SocketError.Success)
                tcs.SetException(new InvalidOperationException(args.SocketError.ToString()));
            else
                tcs.SetResult(args.BytesTransferred);
        };

        args.Completed += handler;

        if (!socket.SendAsync(args)) // means the operation completed synchronously & Completed handler won't fire
        {
            args.Completed -= handler;
            if (args.SocketError != SocketError.Success)
                tcs.SetException(new InvalidOperationException(args.SocketError.ToString()));
            else
                tcs.SetResult(args.BytesTransferred);
        }

        return tcs.Task;
    }

    private static async Task<bool> SendBufferToSocketAsync(Socket socket, SocketAsyncEventArgs socketArgs)
    {
        int retry = 0;

        for (int ixWrite = 0; ixWrite < socketArgs.Count;)
        {
            if (ixWrite > 0)
                socketArgs.SetBuffer(socketArgs.Offset + ixWrite, socketArgs.Count - ixWrite);

            int nSent = await socket.SendBytesAsync(socketArgs);
            ixWrite += nSent;

            if (retry >= 10)
            {
                socket.Close();
                return false;
            }
            if (nSent == 0)
            {
                retry++;
                await Task.Delay(10 * retry);
            }
            else
                retry = 0;
        }

        return true;
    }

我将 ReadBufferFromSocketAsync 更改为以下内容后问题消失了

    private static async Task<bool> ReadBufferFromSocketAsync(Socket socket, byte[] buffer, int offset, int totalToRead) 
    {
        using (SocketAsyncEventArgs socketArgs = new SocketAsyncEventArgs())
        {
            int iAllRead = 0, iOneRead = 0;
            socketArgs.SetBuffer(buffer, offset, totalToRead);

            while (true)
            {
                iOneRead = await socket.ReceiveBytesAsync(socketArgs);

                if (iOneRead == 0)
                {
                    socket.Close();
                    return false;
                }

                iAllRead += iOneRead;

                if (iAllRead == totalToRead)
                    return true;

                offset += iOneRead;
                socketArgs.SetBuffer(offset, totalToRead - iAllRead);
            }
        }
    }