首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

.NET 高效 UDP 非同步程式設計實戰:解決執行緒耗盡與進程殘留問題

     編輯:ASP.NET 基礎

背景與痛點

UDP 協定無連線、不可靠,但在即時通訊、遊戲、串流等場景極為常見。早期採用「每次接收開新執行緒同步阻塞」的寫法,實測會造成兩大問題:

  1. 記憶體飆升:頻繁建立/銷毀執行緒導致 Gen 2 GC 壓力大、非受控堆積膨脹。
  2. 進程殘留:主視窗關閉後,背景執行緒因未正確結束(IsBackground=false 或未攔截 ThreadAbortException)導致進程無法退出。

非同步模型(APM)重構方案

.NET Framework 提供 UdpClient.BeginReceive/EndReceive 非同步配對,配合 I/O 完成埠(IOCP),單一執行緒即可處理高併發接收,徹底解決上述問題。

核心程式碼實作

using System;
using System.Net;
using System.Net.Sockets;

public class UdpAsyncReceiver
{
    private readonly UdpClient _client;
    private readonly IPEndPoint _remoteEp;
    private readonly AsyncCallback _recvCallback;

    public UdpAsyncReceiver(string remoteHost, int remotePort)
    {
        _client = new UdpClient();
        _remoteEp = new IPEndPoint(IPAddress.Parse(remoteHost), remotePort);
        _client.Connect(_remoteEp); // 綁定遠端,僅接收該端資料
        _recvCallback = ReceiveCallback;
        StartReceive();
    }

    private void StartReceive()
    {
        _client.BeginReceive(_recvCallback, null);
    }

    private void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            byte[] buffer = _client.EndReceive(ar, ref _remoteEp);
            if (buffer.Length == 0) return;

            // 業務邏輯:封包解析與分派
            ProcessPacket(buffer);
        }
        catch (ObjectDisposedException) { /* 已關閉,忽略 */ }
        catch (SocketException) { /* 網路錯誤,視需求重連或記錄 */ }
        finally
        {
            // 關鍵:遞迴發起下一次接收,形成持續監聽迴路
            StartReceive();
        }
    }

    private void ProcessPacket(byte[] data)
    {
        // 範例:簡易指令碼分派
        if (data[0] != 0x02 && data[0] != 0x03) return; // 非預期封包

        ushort cmd = BitConverter.ToUInt16(data, 3);
        switch (cmd)
        {
            case 0x0001: OnTokenRequest(data); break;
            case 0x0002: OnLoginRequest(data); break;
            case 0x0003: OnFriendList(data); break;
            case 0x0004: OnKeepAlive(data); break;
            case 0x0005: OnSendMessage(data); break;
            case 0x0006: OnReceiveMessage(data); break;
            default: LogUnknown(cmd, data); break;
        }
    }

    // 以下為業務處理存根
    private void OnTokenRequest(byte[] d) { /* ... */ }
    private void OnLoginRequest(byte[] d) { /* ... */ }
    private void OnFriendList(byte[] d) { /* ... */ }
    private void OnKeepAlive(byte[] d) { /* ... */ }
    private void OnSendMessage(byte[] d) { /* ... */ }
    private void OnReceiveMessage(byte[] d) { /* ... */ }
    private void LogUnknown(ushort cmd, byte[] d) { /* ... */ }

    public void Close() => _client.Close();
}

關鍵設計細節

要點說明
Connect() 綁定遠端僅接收指定 IP:Port 的封包,自動過濾雜訊,減少 EndReceive 後的判斷成本。
回呼中再次呼叫 BeginReceive形成「接收→處理→再接收」的無限迴路,無需輪詢或 Timer。
try/finally 保證重啟接收即使業務邏輯拋出例外,也能繼續監聽,避免靜默掛掉。
單一 UdpClient 實例避免重複建立 Socket,節省非受控資源。

進階:遷移至 Task-based Asynchronous Pattern (TAP)

.NET 4.5+ 建議使用 ReceiveAsync() 搭配 await,語意更清晰:

public async Task StartListeningAsync(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        try
        {
            var result = await _client.ReceiveAsync();
            ProcessPacket(result.Buffer);
        }
        catch (OperationCanceledException) { break; }
        catch (Exception ex) { /* 記錄並決定是否重試 */ }
    }
}

效能對比實測(參考值)

指標同步多執行緒APM 非同步TAP 非同步
1 萬封包/秒 記憶體~180 MB~35 MB~30 MB
CPU 使用率12%3%3%
進程退出殘留頻發無無

總結

UDP 非同步接收的核心在於「一個 Socket、一個回呼迴路、零額外執行緒」。採用 APM 或 TAP 模式,不僅解決資源耗盡與進程殘留,更能以極低延遲支撐高併發即時通訊場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved