在同一台计算机上的两个程序之间发送和接收 UDP 数据包

Posted

技术标签:

【中文标题】在同一台计算机上的两个程序之间发送和接收 UDP 数据包【英文标题】:Sending and receiving UDP packets between two programs on the same computer 【发布时间】:2010-10-15 19:55:35 【问题描述】:

是否可以通过 localhost/127...通过 UDP 在同一台计算机上通过 UDP 进行通信(仅单向)...通过共享相同的端口号?

我们正在进行一个学生项目,在该项目中,我们需要在两台计算机之间发送包含一些遥测数据的 UDP 数据包。生成这些数据包的程序是专有的,但我自己正在使用 C# 使用 System.Net.Sockets.UdpClientSystem.Net.IPEndPoint 编写接收器程序。

当我们连接了多台计算机时,这在我们的小组会议期间运行良好,我们可以在这些计算机上分别运行这两个程序。但是当我在家并尝试扩展遥测处理程序时,它并不是很有用,因为我只有一台计算机(我需要一个供稿来测试处理程序)。我也无法在学校的任何计算机上安装该程序。

当我尝试在我的计算机上同时运行这两个程序时(最后启动我的程序),我收到一个 SocketException,表示每个端口通常只允许使用一次。这使我相信必须有某种方式来共享端口(尽管在任何时候只有一个程序可以使用计算机上的端口是有道理的,但我同时运行多个互联网浏览器没有问题(而且我假设他们使用端口 80 进行 http))。

重新编辑编辑:

sipwiz 是对的,感谢 Kalmi 提供指向 UdpClient.Client.Bind() 的指针。 不过,当时,我们正在考虑使用另一个程序来生成类似的数据包,并且我们可以使用我的第一个(虽然幼稚)方法与 ctor 中的 UDP 客户端绑定在同一台计算机上共享端口。 很抱歉不得不取消标记您的答案,sysrqb。

【问题讨论】:

如果没有其他可用的解决方案,您可能会解决此问题的一种方法是创建一个虚拟机并让该虚拟机与您的主桌面进行通信。 互联网浏览器不共享端口。他们使用随机端口(由操作系统分配)连接到服务器。客户端和服务器的端口不必相同。使用 netstat 查看您的浏览器正在使用哪些端口。 【参考方案1】:

您可以使用 ReuseAddress 套接字选项多次绑定到一个端口。

UdpClient udpClient = new UdpClient();
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

您还需要在 UDP 服务器套接字上设置相同的选项。

【讨论】:

ReuseAddress 属性用于使服务器应用程序能够侦听使用指定地址和端口号的连接,即使它们最近正在使用。这用于使服务器关闭侦听套接字并立即重新打开它而不会出错。 而且他想绑定同一个端口两次...所以ReuseAddress 不相关。 那是不正确的。 ReuseAddress 的意思就是它所说的,没有关于套接字是服务器还是客户端的条件。如果你想在一个套接字上重用一个端口用于发送和一个用于接收,你可以。 “投票太旧,无法更改,除非帖子已被编辑”... doh... 请编辑问题,以便我投票给您。 sipwiz 的原始提案如果你只添加语句 udpClient.Client.Bind(localpt); 就可以工作【参考方案2】:

我没想到这是可能的,但是.. 好吧.. sipwiz 是对的。

这可以很容易地完成。 (请投票给 sipwiz 的答案!)

IPEndPoint localpt = new IPEndPoint(IPAddress.Any, 6000);

//Failed try
    try
    
        var u = new UdpClient(5000);
        u.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

        UdpClient u2 = new UdpClient(5000);//KABOOM
        u2.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    
    catch (Exception)
    
        Console.WriteLine("ERROR! You must call Bind only after setting SocketOptionName.ReuseAddress. \n And you must not pass any parameter to UdpClient's constructor or it will call Bind.");
    

//This is how you do it (kudos to sipwiz)
    UdpClient udpServer = new UdpClient(localpt); //This is what the proprietary(see question) sender would do (nothing special) 

    //!!! The following 3 lines is what the poster needs...(and the definition of localpt (of course))
    UdpClient udpServer2 = new UdpClient();
    udpServer2.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    udpServer2.Client.Bind(localpt);

【讨论】:

我会调查的。目前我们已经切换到另一个程序,它显然允许我们“开箱即用”在同一台计算机上发送和接收,同时我们也可以更轻松地控制该程序。非常感谢 sipwiz!【参考方案3】:

以下是 Tarnay Kálmán 和 sipwiz 回答的完整代码:

服务器代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UdpBroadcastTest

    class Program
    
        static void Main(string[] args)
        

            Console.WriteLine("Sender");
            // This constructor arbitrarily assigns the local port number.

            UdpClient udpClient = new UdpClient();
            udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            udpClient.Connect("localhost", 11000);
            try
            
                string message = String.Empty;
                do
                
                    message = Console.ReadLine();
                    // Sends a message to the host to which you have connected.
                    Byte[] sendBytes = Encoding.ASCII.GetBytes(message);

                    udpClient.Send(sendBytes, sendBytes.Length);
                 while (message != String.Empty);

                udpClient.Close();
            
            catch (Exception e)
            
                Console.WriteLine(e.ToString());
            

            Console.WriteLine("Press Any Key to Continue");
            Console.ReadKey();
        
    

客户端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace UdpReciever

    class Program
    
        static void Main(string[] args)
        
            Console.WriteLine("Receiver");
            // This constructor arbitrarily assigns the local port number.
            UdpClient udpClient = new UdpClient();
            udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, 11000));
            try
            
                //IPEndPoint object will allow us to read datagrams sent from any source.
                IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);

                string message = String.Empty;
                do
                

                    // Blocks until a message returns on this socket from a remote host.
                    Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
                    message = Encoding.ASCII.GetString(receiveBytes);

                    // Uses the IPEndPoint object to determine which of these two hosts responded.
                    Console.WriteLine("This is the message you received: " +
                                                 message);
                    //Console.WriteLine("This message was sent from " +
                    //                            RemoteIpEndPoint.Address.ToString() +
                    //                            " on their port number " +
                    //                            RemoteIpEndPoint.Port.ToString());
                
                while (message != "exit");
                udpClient.Close();
                //udpClientB.Close();

            
            catch (Exception e)
            
                Console.WriteLine(e.ToString());
            

            Console.WriteLine("Press Any Key to Continue");
            Console.ReadKey();
        
    

【讨论】:

嗨,如果两个应用程序都是发送者和接收者,我从这个例子中了解到,如果一个只是发送者而另一个只是接收者,它只会在同一台机器上工作? 您的示例标签错误(发送者应该是“客户端”,接收者应该是“服务器”)。【参考方案4】:

您也许可以在您的网卡上放置多个 IP 地址,或者环回,并将服务器和客户端绑定到不同的 IP 地址?

否则虚拟机方法肯定会起作用。

【讨论】:

多 IP 地址方式更轻量级(并且可以很好地工作而不会造成任何性能损失)。【参考方案5】:

一次只有一个程序可以绑定到一个端口。多个程序可以连接到另一个系统上的一个端口,但是您的不同网络浏览器绑定到的本地端口是随机分配的。

除非你想做一些丑陋的进程间通信或数据包嗅探,否则没有办法将多个程序绑定到一个端口。

【讨论】:

【参考方案6】:

即使更改您的代码以便我可以传递 IP 地址,我也会收到相同的错误消息,您似乎无法绑定到同一个端口并且只能使用一个端口 这是我使用您的示例并对其进行更改以从本地计算机捕获我的 ip 的示例代码.. IPAddress ipAddress = Dns.Resolve(Dns.GetHostName()).AddressList[0]; IPEndPoint ipLocalEndPoint = new IPEndPoint(ipAddress, 11000);

        //IPEndPoint localpt = new IPEndPoint(ipLocalEndPoint);

        UdpClient udpServer = new UdpClient(ipLocalEndPoint);
        udpServer.Client.SetSocketOption(
            SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        udpServer.Connect(ipLocalEndPoint);
        UdpClient udpServer2 = new UdpClient();
        udpServer2.Client.SetSocketOption(
            SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

        udpServer2.Client.Bind(ipLocalEndPoint); // <<---------- Exception here

这将在 Bind () 方法上产生异常。抱歉。

【讨论】:

【参考方案7】:

我的建议:不要将端口号传递给 UdpClient 构造函数。从documentation,(有点稀疏,我知道......)看起来如果你这样做,UdpClient 将尝试 bind 到该端口(正如 sysrqb 提到的那样,这是不允许的) . (如果你不这样做,我相信 UdpClient 会在随机端口上监听任何回复。你也可以选择一个你知道未使用的端口。)

当您调用 Connect() 时,您需要传入服务器正在侦听的端口号。

【讨论】:

“生成这些数据包的程序是专有的”,问题是它绑定到它发送到的同一个端口。【参考方案8】:

将两个程序,即发送者和接收者绑定到本地主机上的同一个端口。简单的答案。

【讨论】:

以上是关于在同一台计算机上的两个程序之间发送和接收 UDP 数据包的主要内容,如果未能解决你的问题,请参考以下文章

发送/接收通知

Qt UDpsocket 在同一台计算机上工作,但不在同一网络上的两台计算机上工作

UDP IP 分片和 MTU

UDP协议

UDP协议详细讲解

UDP与TCP协议