在 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);
        }
    }
}