【发布时间】:2019-11-26 01:11:04
【问题描述】:
我正在编写一个使用 Async / Await 的 TCP 服务器,它需要根据从每个客户端接收到的信息向连接的客户端发送消息列表。 在发送给客户端的每条消息之间,我需要:
- 等待确认/响应,然后发送下一条消息
- 如果 5 秒后没有确认,则重新发送命令
为此,我在我的 ConnClient 类上设置一个ResponseReceived 属性,当预期的响应到来时。然后,在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);
}
}
}
}
【问题讨论】:
-
“我需要:……如果 5 秒后没有确认,则重新发送命令”——这是 TCP 的可笑要求——基于实施。 TCP 已经在协议中实现了数据确认。您不应该尝试在此之上添加您自己的,并且您当然不应该在无法返回 ack 时重新发送数据。如果远程端点获得了重新发送的数据,它现在将不得不处理相同数据的两个副本。
-
彼得,如果在指定的时间范围内未收到确认,您是否建议最好的方法是简单地关闭与客户端的连接?
-
“你是否建议最好的方法是简单地关闭连接” - 实际上,不。我建议最好的方法是什么都不做。响应延迟的原因有很多,其中一些可以恢复,有些则不能。在可恢复的场景中,只要您什么都不做,TCP 就会为您恢复。在不可恢复的场景中,只要你什么都不做,你的连接最终会自然而然的异常失败。然后,只有到那时,你才会关闭套接字。一旦异常发生,你就知道socket已经没有用了。
标签: c# sockets asynchronous async-await