我正在嘗試將一些 NET Framework 4.8 異步 UDP 偵聽器代碼移植到 NET 5。該代碼位于一個 Windows 表單應用程式中,該應用程式接收傳入的 UDP 訊息并將它們顯示在一個簡單的 ListBox 控制元件中。代碼在 NET Framework 上運行良好一兩年,沒有出現任何故障。
問題是它不能在 NET 5 上可靠地運行。顯示視窗顯示 1 或 2 條傳入的 UDP 訊息,然后停止顯示任何內容。有時它甚至不會顯示第一個傳入的 UDP 訊息。
我的理論(沒有任何真實證據支持)是異步偵聽器事件出現問題,應用程式在收到資料包后不會在回呼中啟動新的偵聽操作,因此停止偵聽埠。
我已經在網上掃描了與我的代碼明顯不同的 UDP 異步示例,搜索了熱門示例站點,閱讀了 Microsoft TCP 示例(周圍有很多 TCP 示例),并在 SO 上搜索,但都沒有成功。
我嘗試的最大更改是將簡單的 ReceiveFromAsync 呼叫更改為監視可能回傳的 willRaiseEvent 標志的呼叫,但這也無濟于事。這是我嘗試但未成功的修改后呼叫代碼的示例。
var willRaiseEvent = _sockReceiver.ReceiveFromAsync(newRxArgs);
if (!willRaiseEvent) ProcessMyData(newRxArgs);
我所做的另一個更改是在回呼方法中分配新的 SocketAsyncEventArgs,而不是重用舊的。這有時似乎有所幫助,并且在應用程式停止顯示更多傳入的 UDP 訊息之前會顯示多條日志訊息。(下面的代碼是原始代碼,因此不顯示我除錯 NET 5 的修改。)
誰能看到下面已經運行了很長時間的代碼可能有什么問題/不足,或者我在 NET 5 上可能缺少什么?謝謝你。
更新:
繼續除錯作業表明,回呼方法中的 ReceiveFromAsync 呼叫總是回傳 false(表示它不會引發接收完成事件)。如果它從不引發事件,那就可以解釋為什么不顯示日志訊息。
另一個令人不安的事情是,ReceiveFromAsync 立即回傳的事件引數包含剛剛處理的傳入訊息的尾隨部分,即使我在呼叫 ReceiveFromAsync 之前為 SetBuffer 分配了新的 SocketEventArgs 和新緩沖區。據我所知,在回呼中,ReceiveFromAsync 呼叫認為實際上已經發生了第二個接收 IO 事件,而實際上并沒有發生。(我在除錯器中,只向代碼發送了 1 個資料包。)很奇怪。
我希望對 UDP ReceiveFromAsync 有更多了解的人可以對正在發生的事情有所了解,特別是關于 ReceiveFromAsync 應該和不應該回傳 true 或 false 的條件。
public void UdpReceivePacket48() {
try {
// listen on the port from any IP address
_endPointReceiveFrom = new IPEndPoint(IPAddress.Any, Port);
_sockReceiver.Bind(_endPointReceiveFrom);
// make a localhost endpoint for the receive operation
var myReturnAddress = IPAddress.Parse(Localhost);
_endPointSendTo = new IPEndPoint(myReturnAddress, Port);
// fill out the event arguments and callback method
var rxEventArgs = new SocketAsyncEventArgs();
rxEventArgs.RemoteEndPoint = _endPointSendTo;
rxEventArgs.SetBuffer(new byte[_cbuffersize], 0, _cbuffersize);
rxEventArgs.Completed = UdpReceiveCallback48;
// initiate a receive operation
_sockReceiver.ReceiveFromAsync(rxEventArgs);
}
catch (Exception e) {
LogTheFailure();
throw;
}
}
void UdpReceiveCallback48(object sock, SocketAsyncEventArgs rxArgs) {
var bytesRx = rxArgs.BytesTransferred;
var textReceived = Encoding.ASCII.GetString(rxArgs.Buffer, 0, bytesRx);
Console.WriteLine($@"Text received: {textReceived}");
// clear the buffer before you start another receive operation
Array.Clear(rxArgs.Buffer, 0, rxArgs.Count);
// The event args are the original ones, so you can reuse them all.
((Socket) sock).ReceiveFromAsync(rxArgs);
// process the received data (not on the UI thread)
Program.ListBoxLog.AddToLog(textReceived);
}
uj5u.com熱心網友回復:
我仍然不確定為什么原始代碼不能在 NET 5 上運行。最后,我將回呼方法重寫為 1)引發事件以通過回呼執行緒和 2 之外的某些非 UI 執行緒處理傳入資料) 回圈和處理資料包,直到 ReceiveFromAsync 最終回傳 true。我的新代碼如下所示。
重用事件 args 物件或分配一個新物件根本沒有明顯的區別。出于性能原因,我重用了同一個物件,而不是為可重用物件池添加代碼。
清除事件 args 物件中的緩沖區(在原始代碼中)也沒有任何區別(所以我也將其洗掉)。
下面的代碼在 NET 5 上似乎可以正常作業。我添加了一個回圈來處理高流量的突發。回圈旋轉,直到 ReceiveFromAsync 最終回傳 true 表示它將在收到下一個資料包時引發事件。
private void
UdpReceiveCallback(object sock, SocketAsyncEventArgs rxArgs) {
// the SockReceiver is the original socket that initiated the receive
// the rxArgs are the same ones passed in by the original caller
if (rxArgs.SocketError == SocketError.Success) {
// fire an event for asynch processing elsewhere
var textData = ExtractData(rxArgs);
var eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
// ReceiveFromAsync returns true if it will notify you in the future
var willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
// if it returns false it means it will not raise a future event because
// it has received a packet already. Since the listener can get a burst
// of packets from apps on the localhost, the loop below hands the
// processing off to another thread until ReceiveFromAsync finally
// returns true and says that it will raise an event in the future
// when the next packet arrives.
while (! willRaiseEvent) {
// fire an event for asynch processing elsewhere
textData = ExtractData(rxArgs);
eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/405116.html
標籤:
