文章目錄
- 1 UDP 概述
- 1.1 通信流程
- 1.2 TCP 與 UDP
- 1.3 UDP 分包
- 1.4 UDP 黏包
- 2 同步通信
- 2.1 服務端
- 2.2 客戶端
- 2.3 測試
- 3 異步通信
- 3.1 Bgin / End 方法
- 3.2 Async 方法
1 UDP 概述
1.1 通信流程
? 客戶端和服務端的流程如下:
- 創建套接字 Socket。
- 用
Bind()
方法將套接字與本地地址進行綁定。 - 用
ReceiveFrom()
和SendTo()
方法在套接字上收發消息。 - 用
Shutdown()
方法釋放連接。 - 關閉套接字。

1.2 TCP 與 UDP


1.3 UDP 分包
? UDP 是不可靠的連接,消息傳遞過程中可能出現無序、丟包等情況。
? 為了避免其分包,建議在發送 UDP 消息時 控制消息的大小在 MTU(最大傳輸單元)范圍內。
MTU(Maximum Transmission Unit)
? 最大傳輸單元,用來通知對方所能接受數據服務單元的最大尺寸,不同操作系統會提供用戶一個默認值。
? 以太網和 802.3 對數據幀的長度限制,其最大值分別是 1500 字節和 1492 字節。
? 由于 UDP 包本身帶有一些信息,因此建議:
局域網環境下:1472 字節以內(1500 減去 UDP 頭部 28 為 1472)。
互聯網環境下:548 字節以內(老的 ISP 撥號網絡的標準值為 576 減去 UDP 頭部 28 為 548)。
只要遵守這個規則,就不會出現自動分包的情況。
? 如果想要發送的消息確實比較大,可以進行手動分包,將其拆分成多個消息,每個消息不超過限制。但手動分包的前提是要解決 UDP 的丟包和無序問題。
1.4 UDP 黏包
? UDP 本身作為無連接的不可靠的傳輸協議(適合頻繁發送較小的數據包),不會對數據包進行合并發送。一端直接發送數據,不會對數據合并。
? 因此 UDP 當中不會出現黏包問題(除非手動進行黏包)。
2 同步通信
? 區別于 TCP,UDP 發送和接收消息的方式為 SendTo()
和 ReceiveFrom()
,需要傳入指定的 EndPoint 以指明將消息發送到哪和從哪里接收消息。
SendTo()

-
參數
-
buffer
:要發送的數據緩沖區。 -
size
:要發送的數據的字節數。 -
socketFlags
:發送操作的控制標志。 -
remoteEP
:遠程終結點,指定數據要發送到的目標地址。
-
-
返回值
- 發送的字節數。
ReceiveFrom()

- 參數
buffer
:字節數組,用于存儲接收到的數據。size
:指定從接收緩沖區中讀取的最大字節數。socketFlags
:枚舉值,用于指定接收操作的行為。remoteEP
:EndPoint
對象,用于存儲發送方的網絡地址。這個參數是引用類型,所以方法調用后,它將包含發送方的地址信息。
- 返回值
- 接收到的字節數。
2.1 服務端
// See https://aka.ms/new-console-template for more informationusing System.Net;
using System.Net.Sockets;
using System.Text;// 1.創建套接字
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 2.綁定本機地址
var ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(ipPoint);
Console.WriteLine("服務器開啟,等待消息中...");// 3.接受消息
var buffer = new byte[512];
EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
var length = socket.ReceiveFrom(buffer, ref remoteIpPoint2);
Console.WriteLine("IP: " + (remoteIpPoint2 as IPEndPoint).Address +" Port: " + (remoteIpPoint2 as IPEndPoint).Port +" 發來了 " +Encoding.UTF8.GetString(buffer, 0, length));// 4.發送到指定目標
var remoteIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socket.SendTo(Encoding.UTF8.GetBytes("hi"), remoteIpPoint);// 5.釋放關閉
socket.Shutdown(SocketShutdown.Both);
socket.Close();Console.ReadKey();
2.2 客戶端
using UnityEngine;namespace Lesson
{using System;using System.Net;using System.Net.Sockets;using System.Text;public class Lesson14 : MonoBehaviour{private void Start(){// 1.創建套接字var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 2.綁定本機地址var ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socket.Bind(ipPoint);// 3.發送到指定目標var remoteIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);socket.SendTo(Encoding.UTF8.GetBytes("hello"), remoteIpPoint);// 4.接受消息var buffer = new byte[512];EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);var length = socket.ReceiveFrom(buffer, ref remoteIpPoint2);print("IP: " + (remoteIpPoint2 as IPEndPoint).Address +" Port: " + (remoteIpPoint2 as IPEndPoint).Port +" 發來了 " +Encoding.UTF8.GetString(buffer, 0, length));// 5.釋放關閉socket.Shutdown(SocketShutdown.Both);socket.Close();}}
}
2.3 測試
? 先運行服務器,再運行 Unity,可以看到雙端互發消息。


3 異步通信
3.1 Bgin / End 方法
BeginSendTo()

-
參數
buffer
:要發送的數據緩沖區。offset
:緩沖區中開始發送數據的偏移量。size
:要發送的數據字節數。socketFlags
:用于指定發送操作的選項。例如,可以用來指定是否使用緊急數據。remoteEP
:遠程終結點。它指定了要發送數據的目標地址。callback
:異步操作完成時要調用的回調方法。state
:一個用戶定義的對象,它包含異步操作的狀態信息。
-
返回值
- 返回
IAsyncResult
對象,表示異步操作的狀態和結果。可以通過調用EndSendTo()
方法來獲取異步操作的結果。
- 返回
BeginReceiveFrom()

-
參數
buffer
:字節數組,用于存儲接收到的數據。offset
:在buffer
數組中開始存儲接收數據的偏移量。size
:要接收的數據的字節數。socketFlags
:控制接收操作的標志。例如,SocketFlags.Partial
表示接收的數據可能不完整。remoteEP
:EndPoint
對象,用于存儲發送方的地址。這個參數是引用類型,所以方法調用`后,它會被更新為發送方的地址。callback
:異步操作完成時要調用的回調方法。state
:用戶定義的對象,包含與異步操作相關的狀態信息。
-
返回值
- 返回
IAsyncResult
對象,表示異步操作的狀態。通過這個對象,可以檢查異步操作是否完成,或者等待操作完成。
- 返回
代碼示例
using UnityEngine;namespace Lesson
{using System;using System.Net;using System.Net.Sockets;using System.Text;public class Lesson16 : MonoBehaviour{private byte[] _buffer = new byte[512];private void Start(){// 創建一個UDP套接字var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 創建一個IP地址和端口號的EndPointEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);// 將字符串轉換為字節數組byte[] bytes = Encoding.UTF8.GetBytes("123123lkdsajlfjas");// 開始發送數據到指定的EndPointsocket.BeginSendTo(bytes, 0, bytes.Length, SocketFlags.None, ipPoint, SendCallback, socket);// 開始接收數據socket.BeginReceiveFrom(_buffer, 0, _buffer.Length, SocketFlags.None, ref ipPoint, ReceiveCallback, (socket, ipPoint));}private void ReceiveCallback(IAsyncResult ar){try{(Socket s, EndPoint ipPoint) info = ((Socket, EndPoint)) ar.AsyncState;// 返回值 就是接收了多少個 字節數int length = info.s.EndReceiveFrom(ar, ref info.ipPoint);// 處理消息// ...// 處理完消息 又繼續接受消息info.s.BeginReceiveFrom(_buffer, 0, _buffer.Length, SocketFlags.None, ref info.ipPoint, ReceiveCallback, info);}catch (SocketException s){print("接受消息出問題: " + s.SocketErrorCode + " : " + s.Message);}}private void SendCallback(IAsyncResult ar){try{Socket s = ar.AsyncState as Socket;s.EndSendTo(ar);print("發送成功");}catch (SocketException s){print("發送失敗: " + s.SocketErrorCode + " : " + s.Message);}}}
}
3.2 Async 方法
SendToAsync()

ReceiveFromAsync()

代碼示例
using UnityEngine;namespace Lesson
{using System;using System.Net;using System.Net.Sockets;using System.Text;public class Lesson16 : MonoBehaviour{private byte[] _buffer = new byte[512];private void Start(){// 創建一個UDP套接字var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);// 創建一個IP地址和端口號的EndPointEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);// 將字符串轉換為字節數組byte[] bytes = Encoding.UTF8.GetBytes("123123lkdsajlfjas");var args = new SocketAsyncEventArgs();// 設置發送數據的緩沖區args.SetBuffer(bytes, 0, bytes.Length);// 添加發送完成事件args.Completed += SendToAsyncCompleted;socket.SendToAsync(args);var args2 = new SocketAsyncEventArgs();// 設置接收數據的緩沖區args2.SetBuffer(_buffer, 0, _buffer.Length);// 添加接收完成事件args2.Completed += ReceiveFromAsyncCompleted;socket.ReceiveFromAsync(args2);}private void SendToAsyncCompleted(object s, SocketAsyncEventArgs args){if (args.SocketError == SocketError.Success){print("發送成功");}else{print("發送失敗");}}private void ReceiveFromAsyncCompleted(object s, SocketAsyncEventArgs args){if (args.SocketError == SocketError.Success){print("接收成功");// 具體收了多少個字節// args.BytesTransferred// 可以通過以下兩種方式獲取到收到的字節數組內容// args.Buffer// cacheBytes// 解析消息// ...Socket socket = s as Socket;//只需要設置 從第幾個位置開始接 能接多少args.SetBuffer(0, _buffer.Length);socket.ReceiveFromAsync(args);}else{print("接收失敗");}}}
}