C#網路程式設計入門之UDP
目錄:
C#網路程式設計入門系列包括三篇文章:
(一)C#網路程式設計入門之UDP
(二)C#網路程式設計入門之TCP
(三)C#網路程式設計入門之HTTP
一、概述
UDP和TCP是網路通訊常用的兩個傳輸協議,C#一般可以通過Socket來實現UDP和TCP通訊,由於.NET框架通過UdpClient、TcpListener 、TcpClient這幾個類對Socket進行了封裝,使其使用更加方便, 本文就通過這幾個封裝過的類講解一下相關應用。
二、UDP基本應用
與TCP通訊不同,UDP通訊是不分服務端和客戶端的,通訊雙方是對等的。為了描述方便,我們把通訊雙方稱為傳送方和接收方。
傳送方:
首先建立一個UDP物件:
string locateIP = "127.0.0.1"; //本機IP int locatePort = 9001; //傳送埠 IPAddress locateIpAddr = IPAddress.Parse(locateIP); IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort); UdpClient udpClient = new UdpClient(locatePoint);
傳送資料:
string remoteIP = "127.0.0.1"; //目標機器IP int remotePort = 9002; //接收埠 IPAddress remoteIpAddr = IPAddress.Parse(remoteIP); IPEndPoint remotePoint = new IPEndPoint(remoteIpAddr, remotePort); byte[] buffer = Encoding.UTF8.GetBytes(“hello”); udpClient.Send(buffer, buffer.Length, remotePoint);
以上就完成了一個傳送任務,一個較完整的傳送程式碼如下:
public partial class FormServer : Form { private UdpClient udpClient = null; private void btnConnect_Click(object sender, EventArgs e) { string locateIP = "127.0.0.1"; int locatePort = 9001; IPAddress locateIpAddr = IPAddress.Parse(locateIP); IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort); udpClient = new UdpClient(locatePoint); this.groupWork.Enabled = true; } private void Send_Click(object sender, EventArgs e) { string text = this.txtSend.Text.Trim(); string remoteIP = "127.0.0.1"; int remotePort = 9002; byte[] buffer = Encoding.UTF8.GetBytes(text); if (udpClient != null) { IPAddress remoteIp = IPAddress.Parse(remoteIP); IPEndPoint remotePoint = new IPEndPoint(remoteIp, remotePort); udpClient.Send(buffer, buffer.Length, remotePoint); } Debug.WriteLine("Send OK"); } }View Code
接收端:
首先建立一個UDP物件:
string locateIP = "127.0.0.1"; int locatePort = 9002; IPAddress locateIpAddr = IPAddress.Parse(locateIP); IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort); UdpClient udpClient = new UdpClient(locatePoint);
接收資料:
IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1); var received = udpClient.Receive(ref remotePoint); string info = Encoding.UTF8.GetString(received); string from=$” {remotePoint.Address}:{remotePoint.Port}”;
注意兩點:
1、remotePoint是獲得傳送方的IP資訊,定義時可以輸入任何合法的IP和埠資訊;
2、Receive方法是阻塞方法,所以需要在新的執行緒內執行,程式會一直等待接收資料,當接收到一包資料時程式就返回,要持續接收資料需要重複呼叫Receive方法。
一個較完整的接收端程式碼如下:
public partial class FormClent : Form { private UdpClient udpClient = null; private void btnConnect_Click(object sender, EventArgs e) { string locateIP = "127.0.0.1"; int locatePort = 9002; IPAddress locateIpAddr = IPAddress.Parse(locateIP); IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort); udpClient = new UdpClient(locatePoint); IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1); Task.Run(() => { while (true) { if (udpClient != null) { var received = udpClient.Receive(ref remotePoint); string info = Encoding.UTF8.GetString(received); string from=$” {remotePoint.Address}:{remotePoint.Port}”; } } }); } }View Code
三、丟包和亂序問題
當傳送端傳送一包資料時,不管對方是否接收都是傳送成功的,UDP協議本身並不會對傳送的可靠性進行驗證。(這裡的可靠性是指是否接收到,如果對方接收到資料包,其內容還是可靠的,這個在鏈路層進行了保證。)同時,由於網路延時等因素,先發送的包並不能確定先被接收到,所以由於這兩個原因,UDP通訊存在丟包和亂序的情況。
某些業務場景下,比如實時狀態監控,可能對丟包和亂序情況並不敏感, 可以不用處理,但大部分情況下還是介意丟包的,簡單的處理辦法就是把包的頭部固定長度的空間拿出來存放核對資訊,比如包編號,如果有缺失,可以要求傳送方重發,也可以進行排序。
四、將資料接收包裝為事件
我們對UdpClent又進行一次封裝,啟用一個執行緒進行接收資料,將接收到的資料包通過事件釋出出來,這樣使用起來就更方便了。
namespace Communication.UDPClient { public class UdpStateEventArgs : EventArgs { public IPEndPoint remoteEndPoint; public byte[] buffer = null; } public delegate void UDPReceivedEventHandler(UdpStateEventArgs args); public class UDPClient { private UdpClient udpClient; public event UDPReceivedEventHandler UDPMessageReceived; public UDPClient(string locateIP, int locatePort) { IPAddress locateIp = IPAddress.Parse(locateIP); IPEndPoint locatePoint = new IPEndPoint(locateIp, locatePort); udpClient = new UdpClient(locatePoint); //監聽建立好後,建立一個執行緒,開始接收資訊 Task.Run(() => { while (true) { UdpStateEventArgs udpReceiveState = new UdpStateEventArgs(); if (udpClient != null) { IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1); var received = udpClient.Receive(ref remotePoint); udpReceiveState.remoteEndPoint = remotePoint; udpReceiveState.buffer = received; UDPMessageReceived?.Invoke(udpReceiveState); } else { break; } } }); } } }
具體使用辦法:
private void btnConnect_Click(object sender, EventArgs e) { string locateIP = "127.0.0.1"; int locatePort = 9002; UDPClient udpClient = new UDPClient(locateIP, locatePort); udpClient.UDPMessageReceived += UdpClient_UDPMessageReceived; } private void UdpClient_UDPMessageReceived(UdpStateEventArgs args) { var remotePoint = args.remoteEndPoint; string info = Encoding.UTF8.GetString(args.buffer); }
限於篇幅,我們只封裝了資料接收,時間使用時需要把傳送功能也封裝進去,使這個類同時具備傳送和接收功能,傳送功能的封裝比較簡單就不貼程式碼