是否可以有多个具有相同 localEndPoint 的 UDP c# 套接字?

is it possible to have multiple UDP c# sockets with same localEndPoint?


我写了一个工作正常的 TCP client/server 代码。一个 TCP 套接字侦听器接受连接并创建许多具有相同 localEndPoint 和各种 RemoteEndPoint 的套接字。现在我想扩展到 UDP,但服务器代码有问题。我必须将 UDP 套接字绑定到每个客户端对象的 localEndPoint。但我遇到了一个错误:我无法将多个 UDP 套接字绑定到相同的 localEndPoint。根据 Wiki:

,我发现这是 UDP 套接字的性质

A UDP server does not create new child processes for every concurrently served client, but the same process handles incoming data packets from all remote clients sequentially through the same socket. It implies that UDP sockets are not identified by the remote address, but only by the local address, although each message has an associated remote address.

所以我可以像为 TCP 套接字一样为每个客户端创建一个不同的 UDP 套接字吗?
注意:如果可能,请用 socket class 解释,而不是 UDPClient class.

    private void SocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Accept:
                case SocketAsyncOperation.Connect:
                    UDP.Bind(TCP.LocalEndPoint);

代码解释:我以为我可以在建立TCP连接(connect/accept)后在两端(client/server)绑定一个UDP套接字。服务器上只有一个客户端没问题。对于新客户端,服务器会抛出异常,因为它无法将多个套接字与一个 localEndPoint 绑定。并且客户端可以超过所有可用端口。所以我无法为每个客户端将 UDP 套接字绑定到不同的 localEndPoint。
我知道我可以创建一个独特的 UDP 套接字并将接收消息传递给服务器上的相应客户端对象,但它有点讨厌。谢谢!

您可以 运行 每个唯一来源 IP:Port 组合的任务。这将允许您轻松地为每个唯一的传入源 IP:PORT 组合维护一个状态机。 System.IO.Pipelines 很容易将它们结合在一起。

首先设置一个 UDPListener 任务,模仿 TCPListener,对于每个新的 IP:PORT 启动一个任务来处理该端口,创建一个管道,每时每刻都有数据从那个 IPEndpoint,将数据放入 PipeWriter

    static async Task StartUdpListener()
    {
        // Use a Dictionary to match packets from given connections to give Pipes
        ConcurrentDictionary<string, Pipe> connections = new ConcurrentDictionary<string, Pipe>();

        var udpServer = new UdpClient(new IPEndPoint(IPAddress.Any, 33000));
        while (true)
        {
            // Wait for some data to arrive
            var result = await udpServer.ReceiveAsync();

            if(connections.ContainsKey(result.RemoteEndPoint.ToString()))
            {
                // If we have seen this IPEndpoint before send the traffic to the pipe
                // the task associated with that Pipe willpick the traffic up
                connections.TryGetValue(result.RemoteEndPoint.ToString(), out var p);
                await p.Writer.WriteAsync(result.Buffer);
            }
            else
            {
                // If we have not seen it, make the pipe, stick the data in the pipe
                // and spin up a task to Read/Process the data
                var p = new Pipe();
                connections.TryAdd(result.RemoteEndPoint.ToString(), p);
                await p.Writer.WriteAsync(result.Buffer);
                _ = Task.Run(() => UdpServerClient(result.RemoteEndPoint.ToString(), p));
            }
        }
    }

这是内核在接收到 TCPPacket 时执行的操作的简单视图,它将其粘贴在套接字缓冲区中供您通过流读取。

UDP Server Client 任务看起来像这样:

    static async Task UdpServerClient(string serverName,Pipe p)
    {
        while (true)
        {
            var readResult = await p.Reader.ReadAsync();
            var message = Encoding.ASCII.GetString(readResult.Buffer.FirstSpan.ToArray());
            Console.WriteLine($"Server: {serverName} Received: {message}");
            p.Reader.AdvanceTo(readResult.Buffer.End);
        }
    }

为了完整起见,一些客户端通常位于不同的机器上,但为了简单起见,我们将 运行 它们作为任务。

    static async Task UdpClientClient(string messageToSend)
    {
        var client = new UdpClient();
        client.Connect("127.0.0.1", 33000);
        for(var i=0;i<5;i++)
        {
            var message = ASCIIEncoding.ASCII.GetBytes(messageToSend + " #"+ i.ToString());
            await client.SendAsync(message, message.Length);
            await Task.Delay(1000);
        }
    }
}

Link 他们一起:

    static async Task Main(string[] args)
    {
        _ = Task.Run(() => StartUdpListener());

        _ = UdpClientClient("hi Server!");
        _ = UdpClientClient("I am here server...");
        await UdpClientClient("Me too server!");
    }

你得到这个:

Server: 127.0.0.1:53183 Received: Me too server! #0
Server: 127.0.0.1:53182 Received: I am here server... #0
Server: 127.0.0.1:53181 Received: hi Server! #0
Server: 127.0.0.1:53182 Received: I am here server... #1
Server: 127.0.0.1:53183 Received: Me too server! #1
Server: 127.0.0.1:53181 Received: hi Server! #1
Server: 127.0.0.1:53182 Received: I am here server... #2
Server: 127.0.0.1:53183 Received: Me too server! #2
Server: 127.0.0.1:53181 Received: hi Server! #2
Server: 127.0.0.1:53182 Received: I am here server... #3
Server: 127.0.0.1:53181 Received: hi Server! #3
Server: 127.0.0.1:53183 Received: Me too server! #3
Server: 127.0.0.1:53183 Received: Me too server! #4
Server: 127.0.0.1:53181 Received: hi Server! #4
Server: 127.0.0.1:53182 Received: I am here server... #4

当然你需要错误检查,看看客户端是否还在等等