我需要等待超时的 UDP 广播回复,因此我尝试使用带有取消令牌的 ReadAsync 和 Threading.Timer 来发出取消信号。还有另外两台计算机对广播进行回复。如果我只使用“读取”,我会看到在传输后 0.05 秒和 0.27 秒收到回复。当使用 ReadAsync 并在广播后时间 T 取消时,将在 T 和 T-0.22 报告回复,时间为 T 0.5、1 和 1.5 秒。 换句话说,来自 Comp1 的回复始终报告为在取消时到达,而来自 Comp2 的回复始终报告为提前约 22 毫秒。
很奇怪的是,收到回复的时间取决于超时,知道为什么以及如何无论超时如何都能尽快收到回复吗?
我正在使用 .NET 8 Windows 应用程序,只是一个带有按钮的表单:
private async void button_Click(object sender, EventArgs e) {
udp2 u = new udp2(local);
List<udp2.Reply> rs = await u.Broadcast_as("hPC",TimeSpan.FromSeconds(waittime_sec));
}
public class udp2
{
public readonly struct Reply {
public readonly EndPoint From;
public readonly DateTime When;
public readonly string ReplyString;
public Reply(EndPoint rrom, DateTime when, string reply) {
From = rrom;
When = when;
ReplyString = reply;
}
}
private readonly Socket m_socket;
private readonly List<Reply> m_replies;
public IList<Reply> Replies => m_replies;
public udp2(IPEndPoint local) {
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
m_socket.Bind(local);
m_socket.EnableBroadcast = true;
m_replies = new List<Reply>();
}
public void Shutdown() { if (m_socket.IsBound) m_socket.Shutdown(SocketShutdown.Both); m_socket.Dispose();}
public async Task<List<Reply>> Broadcast_as(string command, TimeSpan timeout) {
List<Reply> replies = [];
byte[] dg = Encoding.ASCII.GetBytes(command);
IPEndPoint dst = new(IPAddress.Broadcast, 4321);
m_socket.SendTo(dg,dst);// m_client.Send(dg,"255.255.255.255",4321);
byte[] rq = new byte[1024];
t_bs x = new t_bs();
CancellationToken ct = x.GetToken();
bool cancelled = false;
Stopwatch sw = Stopwatch.StartNew();
using (Timer t = new Timer(t_b,x,(int)timeout.TotalMilliseconds,Timeout.Infinite)) {
do {
IPEndPoint remote = new IPEndPoint(IPAddress.Any,0);
EndPoint epremote = (EndPoint)remote;
try {
SocketReceiveFromResult rres = await m_socket.ReceiveFromAsync(rq,epremote, ct);
if (rres.ReceivedBytes>0) {
Reply re = new Reply(rres.RemoteEndPoint,DateTime.Now,Encoding.ASCII.GetString(rq,0,rres.ReceivedBytes));
replies.Add(re);
}
}
catch (OperationCanceledException) { cancelled = true; sw.Stop();}
} while (!cancelled);
}
Debug.WriteLine("cancelled after "+sw.ElapsedMilliseconds+" ms");
return replies;
}
public List<Reply> Broadcast_syn(string command, TimeSpan timeout) {
List<Reply> replies = new List<Reply>();
byte[] dg = Encoding.ASCII.GetBytes(command);
IPEndPoint dst = new IPEndPoint(IPAddress.Broadcast,4321);
m_socket.SendTo(dg,dst);// m_client.Send(dg,"255.255.255.255",4321);
byte[] rq = new byte[1024];
Closocket x = new Closocket(m_socket);
bool cancelled = false;
using (Timer t = new Timer(t_clo,x,(int)timeout.TotalMilliseconds,System.Threading.Timeout.Infinite)) {
do {
IPEndPoint remote = new IPEndPoint(IPAddress.Any,0);
EndPoint epremote = (EndPoint)remote;
try {
int recount = m_socket.ReceiveFrom(rq,ref epremote);
if (recount>0) {
Reply re = new Reply(epremote,DateTime.Now,Encoding.ASCII.GetString(rq,0,recount));
replies.Add(re);
}
}
catch (OperationCanceledException) { cancelled = true; }
catch (Exception ex) { Debug.WriteLine(ex.Message); cancelled = true;}
} while (!cancelled);
}
return replies;
}
private class t_bs {
private int m_cnt;
private CancellationTokenSource m_cts;
public t_bs() {
m_cts = new CancellationTokenSource();
m_cnt = 0;
}
public CancellationToken GetToken() { return m_cts.Token; }
public void ReqCancellation() { if (m_cnt==0) m_cts.Cancel(); m_cnt=1;}
};
private void t_b(Object? o) {
if (o is t_bs tbs)
tbs.ReqCancellation();
}
}
这是因为任务取消是一个请求。
ReadAsync
代码在收到取消请求时不会立即退出。
要在 .Net C# 库之上实现心跳/udp ping 功能,以及它们所做的所有智能缓冲,您确实需要使用一个连续发送和接收的独立线程。 然后让该线程提供主程序可以使用的状态。