在 C# 中创建一个 UDP 服务器

Saad Aslam 2022年7月18日
在 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);
        }
    }
}
Author: 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