我正在使用 Async/Await 编写一个 TCP 服务器,该服务器需要根据从每个客户端接收到的内容向连接的客户端发送消息列表。在发送给客户端的每条消息之间,我需要:
为此,我设置了
ResponseReceived
当预期的响应到来时,我的 ConnClient 类上的属性。然后,在 ConnClient.SendListAsync
例程,我正在检查发送每个命令后属性是否已更改。但是,直到 SendListAsync
才会读取传入的响应。发送所有消息,如下面的调试语句所示:Sending Initial Message.
Received response, generate list of 3 initial commands and send them.
SendListAsync 5 second timeout w/o response.
SendListAsync 5 second timeout w/o response.
SendListAsync 5 second timeout w/o response.
Received response.
Received response.
Received response.
问题:如何正确预防
ConnClient.SendListAsync
阻止传入的读取?public class Svr
{
TcpListener listener;
public async Task Listen(IPAddress iP, int port)
{
listener = new TcpListener(iP, port);
listener.Start();
while (true)
{
TcpClient client = await listener.AcceptTcpClientAsync();
ConnClient cc = new ConnClient(client);
await Receive(ConnClient);
}
}
async Task Receive(ConnClient cc)
{
var headerSize = sizeof(short);
byte[] buffer = new byte[4000];
//Send initial msg
await cc.socket.GetStream().WriteAsync(Strings.InitialMsg, 0, Strings.InitialMsg.Length);
while (true)
{
buffer = new byte[headerSize];
if (!await ReadToBuffer(cc.socket.GetStream(), buffer, headerSize))
return;
var length = BitConverter.ToUInt16(new byte[2] { buffer[1], buffer[0] }, 0 );
buffer = new byte[length];
if (!await ReadToBuffer(cc.socket.GetStream(), buffer, length))
return;
await DoSomethingBasedOnReceived(messageBuffer, cc);
}
}
async Task<Boolean> ReadToBuffer(NetworkStream stream, byte[] buffer, int bytesToRead)
{
int offset = 0;
while (offset < bytesToRead)
{
var length = await stream.ReadAsync(buffer, offset, bytesToRead - offset);
if (length == 0)
return false;
offset += length;
}
return true;
}
public async Task DoSomethingBasedOnReceived(byte[] messageBuffer, ConnClient cc)
{
await SomeLogicToSetTheRRFlagIfMessageApplicable(messageBuffer, cc);
List<byte[]> ListOfMessagesToSend = SomeLogicToDetermineListOfMessages(messageBuffer);
await cc.SendListAsync(ListOfMessagesToSend);
}
}
ConnClient 类,代表单个连接的客户端。
public class ConnClient
{
public TcpClient socket { get; set; }
public Boolean ResponseReceived { get; set; }
public ConnClient (TcpClient cc)
{socket = cc}
public async Task SendListAsync(List<byte[]> messageList)
{
foreach (byte[] msg in messageList)
{
this.ResponseReceived = false;
await stream.WriteAsync(msg, 0, msg.Length);
int waitedSoFar = 0;
while (waitedSoFar < 5000)
{
if (this.ResponseReceived == true)
{
break;
}
waitedSoFar += 100;
await Task.Delay(100);
}
}
}
}
最佳答案
您的第一个问题是您将无法接受新客户。
while (true)
{
// accept the next connection
TcpClient client = await listener.AcceptTcpClientAsync();
// receive and send list
ConnClient cc = new ConnClient(client);
await Receive(ConnClient);
// the loop cannot continue to receive the next connection
// until you have done with your receive
}
您需要执行
Receive
独立,因此您可以等待下一个连接,您可以在没有 await
的情况下调用它( 将 作为异步 void 运行),或将其卸载到新任务。删除等待
Receive(ConnClient);
卸载
Task.Run(() => Receive(ConnClient));
您的第二个问题是您的客户在发送时被阻止并且无法接收。再一次,您将要么卸载,要么在没有等待的情况下运行。
如@PeterDuniho提到
Given that the OP is already using
async
/await
, and given thatReceive()
is already async, there's no reason to useTask.Run()
. It's fire-and-forget either way (unless they change their code to store the returned task), so they might as well just fire-and-forget the call toReceive()
as wrap it in a call toTask.Run()
.
备注 :创建可扩展的客户端/服务器套接字解决方案并非易事,我也不想展示这一点。但是,它将解决您当前的问题。
无论哪种方式,都要非常注意错误。由于两种建议的解决方案都将在未观察到的情况下运行,因此会出现 异常。需要处理
关于c# - .NET Socket ReadAsync 在写循环 Async/Await 期间被阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59042316/