在 C# 中建立一個 UDP 伺服器

Saad Aslam 2023年10月12日
在 C# 中建立一個 UDP 伺服器

本文將展示如何在 C# 中建立一個簡單的 UDP 伺服器。

C# 中建立一個 UDP 伺服器

簡單介紹一下背景,UDP 協議不需要與客戶端建立連線。資料只是傳輸而不驗證客戶端是否接收到它。

這種型別的協議通常用於廣播資料。由於 UDP 是無連線的,因此偵聽器無需線上即可接收訊息。

要使用 UDP 傳輸資料包,你需要知道託管服務的網路裝置的網路地址和服務的 UDP 埠號。流行服務的埠號由 Internet 號碼分配機構 (IANA) 定義;埠號的範圍是 1,024 到 65,535,可以分配給不在 IANA 列表中的服務。

在基於 IP 的網路上,特殊的網路地址用於處理 UDP 廣播訊息。以下解釋以 Internet 的 IP 版本 4 地址族為例。

通過設定主機標識的所有位,可以將廣播定向到網路的指定部分。使用地址 192.168.1.255 向網路上所有 IP 地址以 192.168.1 開頭的主機廣播。

我們現在準備建立或建立一個套接字,設定我們的 UDP 協議,並立即開始通訊。

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

這些將是用於設定伺服器的庫。這些庫支援與網路相關的所有基本功能,例如構建套接字、處理 IPV4 地址等等。

public class SimpleUdpSrvr {
  public static void Main() {
    int recv;
    byte[] data = new byte[1024];
    IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);
    Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  }
}

int recv 行建立一個整數型別的 recv 變數,其中包含客戶端收到的字串訊息的位元組數。

byte[] data = new byte[1024]; line 用於為每個包含 1024 個單元格的單元格建立一個名為 data 的位元組大小的陣列,也稱為陣列大小。該陣列是在堆中動態建立的。

IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050); 行告訴它為套接字建立一個 ipep 變數。傳入的資訊告訴它應該使用什麼樣的套接字。

IPEndPoint 類表示具有 IP 地址和埠號的網路端點;客戶端需要此配置資訊來連線我們的服務。傳入的 IPAddress.Any 引數告訴伺服器可以使用任何 IP 地址連線到 9050 埠號; ipep 變數也是動態建立的。

Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

這一行建立了一個 newsock 變數,一個我們將通過它進行通訊的套接字。傳入的引數告訴我們要建立什麼樣的套接字。

AddressFamily.InterNetwork 告訴我們我們需要本地 IP 地址。SocketType.Dgram 引數指出資料應該以資料包而不是資料包的形式流動。

最後,ProtocolType.Udp 引數告訴我們將使用的套接字的協議型別。現在,我們的套接字已建立。

newsock.Bind(ipep);
Console.WriteLine("Waiting for a client...");

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint Remote = (EndPoint)(sender);

到目前為止,我們已經在網路中建立了一個端點,以及一個用於通訊的套接字,newsock.Bind(ipep); line 現在將幫助我們將端點繫結到套接字。

ipep 是一個變數,它包含關於我們端點的資訊,並在我們的 newsock 物件的 bind() 函式中傳遞。

Console.WriteLine("Waiting for a client...");

此行在螢幕上列印伺服器正在等待客戶端傳送任何訊息。我們需要為將嘗試與伺服器通訊的傳送方建立另一個端點。

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);

這一行建立了另一個端點,變數稱為 sender。在它的 IPAddress.Any 中傳遞的引數告訴我們期待任何 IP 地址,而 0 意味著它是埠號的萬用字元,系統應該看到並找到任何合適的埠分配給我們。

EndPoint Remote = (EndPoint)(sender); 行用於查詢傳送者的 IP 地址並將其儲存在 remote 變數中。

recv = newsock.ReceiveFrom(data, ref Remote);

Console.WriteLine("Message received from {0}:", Remote.ToString());
Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

我們在程式開始時建立的 recv 變數現在用於 recv = newsock.ReceiveFrom(data, ref Remote); 線。我們使用之前建立的套接字 newsockReceive() 函式,並且當前接收我們當時在套接字中的所有資料。

傳遞 Receive() 函式的引數告訴在哪裡儲存資料以及應該儲存或預期誰的資料。data 引數是被傳遞的位元組大小陣列,資料將被寫入資料陣列。

ref Remote 引數告訴 IP 地址傳送了資料,該函式將返回從該套接字獲取的位元組數並將其儲存到 recv 變數中。

Console.WriteLine("Message received from {0}:", Remote.ToString());

此行在螢幕上寫入通過套接字傳送資料的客戶端的 IP 地址。傳遞的引數 Remote.ToString() 將十進位制數字轉換為 ASCII 字元,而 remote 變數是引用客戶端的第二個端點。

Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

這一行將實際資料寫入螢幕。通過套接字傳送的資料為原始形式,應轉換為 ASCII 字元才能讀取。

Encoding.ASCII.GetString(data, 0, recv) 行從作為引數傳遞的 data 變數中獲取資料,recv 引數(包含客戶端傳送的位元組數)告訴從螢幕上的 data 變數中寫入這麼多位元組。

string welcome = "Welcome to my test server";
data = Encoding.ASCII.GetBytes(welcome);
newsock.SendTo(data, data.Length, SocketFlags.None, Remote);

string welcome = "歡迎來到我的測試伺服器"; 行用歡迎來到我的測試伺服器文字初始化歡迎字串變數。

data = Encoding.ASCII.GetBytes(welcome); line 將 welcome 變數作為 Encoding.ASCII.GetBytes() 函式中的引數並將其轉換為原始形式,以便準備好通過另一端的套接字傳送。

newsock.SendTo(data, data.Length, SocketFlags.None, Remote);

這一行通過我們之前建立的套接字 newsock 傳送資料並使用 Send() 函式。它需要的引數是:包含要傳送的原始資料形式的 data 變數,data.Length 引數告訴在套接字上寫入多少位元組,因為資料的長度等於位元組數, SocketFlags.None 引數告訴我們將所有標誌保持為零,標誌是指示符,每個標誌都有其含義。

Remote 變數引數告訴我們要將資料傳送到的客戶端,因為 Remote 變數儲存客戶端的 IP 地址和埠號。

while (true) {
  data = new byte[1024];
  recv = newsock.ReceiveFrom(data, ref Remote);

  Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
  newsock.SendTo(data, recv, SocketFlags.None, Remote);
}

while(true) 行表明它是一個無限迴圈,並且它的所有語句都將無限執行。資料 = 新位元組 [1024]; 行在執行時建立一個長度為 1024 的新位元組大小陣列。

recv = newsock.ReceiveFrom(data, ref Remote); line 從客戶端接收資料並將其儲存到資料陣列中。

Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); 將資料寫入螢幕。

newsock.SendTo(data, recv, SocketFlags.None, Remote); 行將伺服器從它收到的相同資料傳送回客戶端。

之後,伺服器將再次停止並等待客戶端,客戶端傳送給伺服器的任何內容都會以相同的訊息進行響應。該伺服器將永遠不會終止,除非它崩潰或使用者手動停止它。

現在,當我們學習瞭如何在 C# 中建立 UDP 伺服器及其基本功能後,我們可以新增更多功能並根據我們的要求製作伺服器。

完整的原始碼:

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

public class SimpleUdpSrvr {
  public static void Main() {
    int recv;
    byte[] data = new byte[1024];
    IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);

    Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    newsock.Bind(ipep);
    Console.WriteLine("Waiting for a client...");

    IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
    EndPoint Remote = (EndPoint)(sender);

    recv = newsock.ReceiveFrom(data, ref Remote);

    Console.WriteLine("Message received from {0}:", Remote.ToString());
    Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

    string welcome = "Welcome to my test server";
    data = Encoding.ASCII.GetBytes(welcome);
    newsock.SendTo(data, data.Length, SocketFlags.None, Remote);
    while (true) {
      data = new byte[1024];
      recv = newsock.ReceiveFrom(data, ref Remote);

      Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
      newsock.SendTo(data, recv, SocketFlags.None, Remote);
    }
  }
}
作者: Saad Aslam
Saad Aslam avatar Saad Aslam avatar

I'm a Flutter application developer with 1 year of professional experience in the field. I've created applications for both, android and iOS using AWS and Firebase, as the backend. I've written articles relating to the theoretical and problem-solving aspects of C, C++, and C#. I'm currently enrolled in an undergraduate program for Information Technology.

LinkedIn