UDP 特性回顧
- **無連線**、不可靠、無序、無流量控制
- **低延遲、低開銷**,適合即時通訊、廣播、遊戲、串流
- **無明確客戶端/伺服器**,雙方皆可收發 (Peer-to-Peer)
核心類別:UdpClient
封裝 SocketType.Dgram, ProtocolType.Udp,提供簡化 API:Send, Receive, JoinMulticastGroup。
完整 P2P 聊天範例 (WinForms)
public partial class ChatForm : Form
{
private UdpClient _udp;
private IPEndPoint _remoteEp;
private Thread _listenThread;
public ChatForm()
{
InitializeComponent();
_udp = new UdpClient(8888); // 綁定本地埠 8888
CheckForIllegalCrossThreadCalls = false; // 簡化跨執行緒 UI (教學用,生產請用 Invoke)
}
private void btnSend_Click(object sender, EventArgs e)
{
if (!IPAddress.TryParse(txtRemoteIP.Text, out var ip)) { MessageBox.Show("IP 格式錯誤"); return; }
_remoteEp = new IPEndPoint(ip, 8888);
if (_listenThread == null || !_listenThread.IsAlive)
{
_listenThread = new Thread(Listen) { IsBackground = true };
_listenThread.Start();
}
byte[] data = Encoding.UTF8.GetBytes(txtMessage.Text);
_udp.Send(data, data.Length, _remoteEp);
lstMessages.Items.Add($"Me: {txtMessage.Text}");
txtMessage.Clear();
}
private void Listen()
{
try
{
while (true)
{
IPEndPoint senderEp = null;
byte[] data = _udp.Receive(ref senderEp); // 封鎖等待
string msg = Encoding.UTF8.GetString(data);
lstMessages.Items.Add($"{senderEp}: {msg}");
}
}
catch (ThreadAbortException) { }
catch (Exception ex) { MessageBox.Show($"Listen error: {ex.Message}"); }
}
private void btnClear_Click(object sender, EventArgs e) => lstMessages.Items.Clear();
protected override void Dispose(bool disposing)
{
if (disposing)
{
_listenThread?.Abort(); // 簡單粗暴,生產請用 CancellationToken
_udp?.Close();
components?.Dispose();
}
base.Dispose(disposing);
}
}
UdpClient 關鍵方法
| 方法 | 用途 | 參數 |
| Send(byte[], int, IPEndPoint) | 發送資料 | 緩衝區、長度、目標端點 |
| Send(byte[], int, string, int) | 發送資料 (主機名) | 緩衝區、長度、主機、埠 |
| Receive(ref IPEndPoint) | 接收資料 (封鎖) | ref 回傳發送端端點 |
| BeginSend / EndSend | 非同步發送 (APM) | 回調、狀態 |
| BeginReceive / EndReceive | 非同步接收 (APM) | 回調、狀態 |
| JoinMulticastGroup(IPAddress) | 加入多播群組 | 多播位址 (如 239.0.0.1) |
| DropMulticastGroup(IPAddress) | 離開多播群組 | 多播位址 |
多播 (廣播) 範例
var udp = new UdpClient();
udp.JoinMulticastGroup(IPAddress.Parse("239.255.0.1")); // 組播位址
// 發送至組播位址,所有加入該組的主機皆收到
udp.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("239.255.0.1"), 9999));
Socket 直接使用 UDP (進階控制)
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
sock.Bind(new IPEndPoint(IPAddress.Any, 8888));
// 設定 Socket 選項
sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); // 允許廣播
sock.SendTo(data, new IPEndPoint(IPAddress.Broadcast, 8888)); // 發送廣播
常見問題與解決
| 問題 | 原因 | 解決 |
| 資料遺失/亂序 | UDP 本質不可靠 | 應用層實作 ACK/重傳/序號 (如 KCP, RUDP, QUIC) |
| 防火牆阻擋 | UDP 埠未開放 | 開放雙向 UDP 埠 |
| NAT 穿透失敗 | 雙方皆在 NAT 後 | STUN/TURN/ICE 或中繼伺服器 |
| 大封包分片/丟失 | 超過 MTU (通常 1500) | 應用層分片 < 1400 bytes,或啟用 PMTUD |
| 跨執行緒 UI 例外 | Listen 執行緒直接操作控制項 | 用 Invoke/BeginInvoke 或 SynchronizationContext |
現代非同步寫法 (TAP)
public async Task SendAsync(string message, IPEndPoint target)
{
byte[] data = Encoding.UTF8.GetBytes(message);
await _udp.SendAsync(data, data.Length, target);
}
public async Task ListenAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var result = await _udp.ReceiveAsync(ct);
string msg = Encoding.UTF8.GetString(result.Buffer);
// 更新 UI 需 Marshal 到 UI 執行緒
this.Invoke(() => lstMessages.Items.Add($"{result.RemoteEndPoint}: {msg}"));
}
}
UDP 適合**高即時性、可容忍少量丟失**的場景;需可靠傳輸請用 TCP 或應用層可靠協定 (KCP, QUIC)。
- 上一篇文章: C# Tuple 型別增強:基類、型別判斷與預設建構函式
- 下一篇文章: WinForms 動態建立 ContextMenu (右鍵選單) 完整教學
Copyright ©
程式碼之家 All Rights Reserved