UDP 協議簡單瞭解及應用
UDP 協議簡單瞭解及應用
- udp 是 User Datagram Protocol 的簡稱,意思是使用者資料包協議。這是一種無連線的傳輸協議,在 OSI(Open System Interconnect,開放系統互聯)參考模型的傳輸層,提供簡單不可靠資訊傳送服務。udp 為應用程式提供無需建立連線就可以傳送封裝的 IP 資料包的方法,只管傳送,甭管對方是否收到,它在 IP 報文的協議號是 17,正式規範是
- UDP 報文沒有可靠性保證,不確保資料順序和流量控制,有資料直接傳送,無連線無狀態,所以限制少延遲小速度快傳輸效率高,適合快速傳送少量資料,可靠性要求不高的應用。在接收端,udp 把每個訊息段放在佇列,應用程式每次從佇列中讀一個訊息段。udp 雖然能檢測錯誤,但不校正,只是簡單扔掉損壞訊息段或者給程式提供警告資訊
- 基於以上特點,udp 是一個理想的訊息分發協議,在網路好的環境中,比如同一個網路的主機之間通訊,或者同一主機的多個應用,在網路差的環境中,丟包嚴重,而一些惡劣的檢測場景下,實時抗干擾等要求高,udp 能達到較高通訊速率。常用的場景有:聊天室,投屏資訊顯示,音訊影片等多媒體傳輸。不要求傳輸完整而是要求傳輸速率,即使有損壞也不影響
實踐操作
- 光說不練假把式,下面我們用 C#程式碼實現一下接收 udp 訊息的服務端,直觀感受一下
- 祭出文件,使用 UDP 服務
- 最現成的就是使用
UdpClient
,可傳送和接收訊息
傳送端
- 傳送的程式碼比較簡單
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace UdpSender
{
class Program
{
static void Main(string[] args)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress broadcast = IPAddress.Loopback;
IPEndPoint ep = new IPEndPoint(broadcast, 514);
Console.WriteLine("請輸入要傳送的內容:");
while (true)
{
byte[] sendbuf = Encoding.UTF8.GetBytes(Console.ReadLine());
s.SendTo(sendbuf, ep);
}
}
}
}
接收端
- 接收端同樣簡單,接收方法是一個同步方法,會一直等到接收到訊息才繼續執行
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Udp
{
class Program
{
static void Main(string[] args)
{
var port = 514;
var server =
new UdpClient(port);
// new SocketReceiver(port);
var remoteEP = new IPEndPoint(IPAddress.Broadcast, port);
try
{
while (true)
{
Console.WriteLine("等待廣播");
var bytes = server.Receive(ref remoteEP);
var msg = Encoding.UTF8.GetString(bytes);
Console.WriteLine($"接收來自 {remoteEP} 的廣播:");
Console.WriteLine($"{msg}");
Console.WriteLine();
}
}
catch (SocketException e)
{
Console.WriteLine(e);
}
finally
{
server.Close();
}
}
}
}
自定義接收
- 現在玩點不一樣了,因為傳送接收都比較簡單,自己寫程式碼接收,加深理解,傳送端程式碼同理
- 參考 UdpClient、Socket 的程式碼,摳出關鍵性程式碼,只需要三步即可
- 根據網路型別,socket 接收型別,協力型別獲取一個控制程式碼
- 在該控制程式碼上繫結埠,監聽訊息
- 從控制程式碼獲取訊息以及客戶端地址資訊
using System;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
namespace Udp
{
public class SocketReceiver
{
private IntPtr _handle;
public SocketReceiver() : this(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
}
public SocketReceiver(int port) : this(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
var localEP = new IPEndPoint(IPAddress.Any, port);
Bind(localEP);
}
public SocketReceiver(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
{
Dns.GetHostName(); // 初始化必須
_handle = Interop.Winsock.WSASocketW(addressFamily, socketType, protocolType, IntPtr.Zero, 0u, SocketConstructorFlags.WSA_FLAG_OVERLAPPED);
}
public void Bind(EndPoint localEP)
{
var remoteEP = localEP;
var socketAddress = remoteEP.Serialize();
var buffer = socketAddress.GetValue("Buffer") as byte[];
var socketError = Interop.Winsock.bind(_handle, buffer, socketAddress.Size);
}
public unsafe byte[] Receive(ref IPEndPoint remoteEP)
{
var socketAddress = remoteEP.Serialize();
var socketAddressBuffer = socketAddress.GetValue("Buffer") as byte[];
var socketAddressSize = socketAddress.Size;
var maxSize = 0x10000;
var buffer = new byte[maxSize];
var received = 0;
fixed (byte* pinnedBuffer = &buffer[0])
{
received = Interop.Winsock.recvfrom(_handle, pinnedBuffer, maxSize, SocketFlags.None,
socketAddressBuffer, ref socketAddressSize);
}
socketAddress.SetValue("Buffer", socketAddressBuffer);
socketAddress.SetValue("InternalSize", socketAddressSize);
remoteEP = remoteEP.Create(socketAddress) as IPEndPoint;
// 不返回全部長度,只返回全部接受長度
if (received < maxSize)
{
byte[] newBuffer = new byte[received];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, received);
return newBuffer;
}
return buffer;
}
public void Close()
{
GC.SuppressFinalize(this);
}
}
public static class ReflectionHelper
{
public static object GetValue(this object obj, string name)
{
var value = obj.GetType().InvokeMember(name,
BindingFlags.Instance | BindingFlags.GetField |BindingFlags.NonPublic,
null, obj, null);
return value;
}
public static void SetValue(this object obj, string name, object value)
{
obj.GetType().InvokeMember(name,
BindingFlags.Instance | BindingFlags.SetField | BindingFlags.NonPublic,
null, obj, new[] { value });
}
}
internal static class Interop
{
internal static class Winsock
{
/// <summary>
/// 繫結埠
/// </summary>
/// <param name="socketHandle"></param>
/// <param name="socketAddress"></param>
/// <param name="socketAddressSize"></param>
/// <returns></returns>
[DllImport("ws2_32.dll", SetLastError = true)]
internal static extern SocketError bind([In] IntPtr socketHandle, [In] byte[] socketAddress, [In] int socketAddressSize);
/// <summary>
/// 接收
/// </summary>
/// <param name="socketHandle"></param>
/// <param name="pinnedBuffer"></param>
/// <param name="len"></param>
/// <param name="socketFlags"></param>
/// <param name="socketAddress"></param>
/// <param name="socketAddressSize"></param>
/// <returns></returns>
[DllImport("ws2_32.dll", SetLastError = true)]
internal unsafe static extern int recvfrom([In] IntPtr socketHandle, [In] byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In] [Out] ref int socketAddressSize);
/// <summary>
/// 申請
/// </summary>
/// <param name="addressFamily"></param>
/// <param name="socketType"></param>
/// <param name="protocolType"></param>
/// <param name="protocolInfo"></param>
/// <param name="group"></param>
/// <param name="flags"></param>
/// <returns></returns>
[DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr WSASocketW([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] SocketConstructorFlags flags);
}
}
[Flags]
internal enum SocketConstructorFlags
{
WSA_FLAG_OVERLAPPED = 0x1,
WSA_FLAG_MULTIPOINT_C_ROOT = 0x2,
WSA_FLAG_MULTIPOINT_C_LEAF = 0x4,
WSA_FLAG_MULTIPOINT_D_ROOT = 0x8,
WSA_FLAG_MULTIPOINT_D_LEAF = 0x10
}
}
- 注意上面自定義的接收程式碼包含不安全程式碼,需要在專案檔案設定一下
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
總結
- 上面自定義程式碼比較長,經過手動實踐,收穫還是不少的。比如:
- P/Invoke 操作呼叫系統 api,還有控制程式碼型別 IntPtr 的理解
- 網路地址的知識,比如傳送地址是
192.168.1.255
則是廣播訊息到網路段192.168.1
的所有主機 - SocketAddress 套接字地址,不依賴於具體協議,不論是 IPV4 還是 IPV6 都可以表示
- 反射操作,反射獲取或者設定值,Type.InvokeMember 方法加上 BindingFlags 可描述想要的反射操作
- 指標操作,獲取 buffer 的記憶體地址
- 具體除錯的時候發現,服務端繫結監聽埠之後,還沒開始接收資料,客戶端先發資料,服務端後續仍然收到所有資料,這可直觀感受,訊息到達後,存放於緩衝區佇列,等待程式獲取。這一點可有注意於理解
NetworkStream
網路流,可將接收改為非同步+回撥的方式接收訊息該流的讀取操作,實際就是從系統緩衝區讀取資料。其中還請教了大神,UDP 資料緩衝區資料滿了之後就會有資料丟失的情況,這是因為 udp 沒有流量控制,而 TCP 則不會丟失。其它緩衝區滿的情況還要繼續摸索,不過相關引數還是可以調節的,比如緩衝區大小、TCP 的客戶端連線數等 - 從最簡單的 udp 協議開始瞭解網路通訊,再逐漸延伸到流量控制、可靠性保證、安全性等方面,各個概念就會相對容易理解,所以 udp 應該是個很不錯的切入點
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4369/viewspace-2823860/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- TCP對應的協議和UDP對應的協議(簡單概述)TCP協議UDP
- 快應用簡單瞭解
- UDP協議詳解UDP協議
- 透過wireshark簡單瞭解S7協議協議
- TCP 和 UDP 協議簡介TCPUDP協議
- TCP、UDP、HTTP及Socket的簡單講解TCPUDPHTTP
- UDP 和 TCP 兩種協議簡介UDPTCP協議
- 瞭解HTTP協議HTTP協議
- Socket:UDP協議小白UDP協議
- TCP和UDP協議TCPUDP協議
- 《圖解HTTP》——簡單的HTTP協議圖解HTTP協議
- 瞭解HTTP/2協議HTTP協議
- 02 前端HTTP協議(圖解HTTP) 之 簡單的HTTP協議前端HTTP協議圖解
- TCP/IP、UDP/IP協議TCPUDP協議
- Java用UDP實現簡單聊天JavaUDP
- UDP協議抓包分析 -- wiresharkUDP協議
- 系列TCP/IP協議-UDP(009)TCP協議UDP
- netty系列之:使用UDP協議NettyUDP協議
- 應用層協議協議
- 實用TCP協議(1):TCP 協議簡介TCP協議
- Lucene介紹及簡單應用
- 簡單談談DNS協議DNS協議
- WebSocket 簡單瞭解Web
- JWT簡單瞭解JWT
- IPIDEA帶你瞭解HTTP協議和SOCKS5協議IdeaHTTP協議
- 傳輸層協議 TCP 和 UDP協議TCPUDP
- CAP一致性協議及應用解析協議
- 在Linux中,我們都知道,dns採用了tcp協議,又採用了udp協議,什麼時候採用tcp協議?什麼 時候採用udp協議?為什麼要這麼設計?LinuxDNSTCP協議UDP
- TCP應用層協議TCP協議
- 帶你瞭解TCP/IP協議族TCP協議
- 01 . SaltStack部署配置及簡單應用
- 為什麼 DNS 協議使用 UDP?只使用了 UDP 嗎?DNS協議UDP
- 簡單瞭解組策略
- 防火牆-簡單瞭解防火牆
- Golang介面簡單瞭解Golang
- DNS何時使用TCP與UDP協議?DNSTCPUDP協議
- 網路程式設計UDP協議方式程式設計UDP協議
- RabbitMQ系列(二)深入瞭解RabbitMQ工作原理及簡單使用MQ