C# 套接字:异步接收损坏接收缓冲区
C# socket: async receiving corrupts receiving buffer
[问题]
System.Net.Sockets.Socket 的 async 接收破坏了接收缓冲区,将随机突发的字节注入到接收数据的中间。
[背景]
我有一个 TCP 连接服务器。客户端与服务器建立套接字连接,并向服务器发送加密数据包。
最初,服务器使用线程操作。每个传入连接都由专用线程提供服务。通过套接字发送和接收的所有缓冲区都分配在堆上并收集垃圾。一切正常。
然后,为了提高性能,我做了两个改变:
我没有在堆上分配缓冲区,而是使用 ArrayPool.Shared.Rent().
进行分配
我把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);
}
}
}
[问题]
System.Net.Sockets.Socket 的 async 接收破坏了接收缓冲区,将随机突发的字节注入到接收数据的中间。
[背景]
我有一个 TCP 连接服务器。客户端与服务器建立套接字连接,并向服务器发送加密数据包。
最初,服务器使用线程操作。每个传入连接都由专用线程提供服务。通过套接字发送和接收的所有缓冲区都分配在堆上并收集垃圾。一切正常。
然后,为了提高性能,我做了两个改变:
我没有在堆上分配缓冲区,而是使用 ArrayPool.Shared.Rent().
进行分配我把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);
}
}
}