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

41
通过共享相同的端口号,可以让两个程序在同一台计算机上(单向)通过本地主机/127... 的UDP通信进行交流吗?
我们正在进行一个学生项目,在其中需要在两台计算机之间发送包含某些遥测数据的UDP数据包。生成这些数据包的程序是专有的,但我正在使用C#和 System.Net.Sockets.UdpClient以及System.Net.IPEndPoint自己编写接收程序。
在我们小组会议期间,当我们有多个连接的计算机可以分别运行这两个程序时,这很好用。但是当我在家里尝试扩展遥测处理程序时,由于只有一台计算机(我需要测试处理程序的反馈),这并不是很有用。我也不能在学校的任何计算机上安装该程序。
当我尝试同时在我的计算机上运行两个程序(最后启动我的程序时),我会收到一个SocketException,指出每个端口只允许单个使用。这让我相信必须有一种方法可以共享端口(虽然每个计算机上只能有一个程序使用端口是有道理的,但我在同一时间内运行多个互联网浏览器没有问题(我想它们使用80端口用于http))。
重新编辑:
sipwiz是正确的,并且由于Kalmi给了我UdpClient.Client.Bind()的指针而感谢他。但是现在,我们正在考虑使用另一个生成类似数据包的程序,并且使用我的第一种(虽然天真)方法与其共享端口在同一台计算机上。抱歉,sysrqb,必须取消选中您的答案。

如果没有其他解决方案可用,您可以采取的一种方法是创建一个虚拟机,并让虚拟机与您的主桌面通信。 - TheTXI
互联网浏览器不共享端口。它们使用随机端口(由操作系统分配)连接到服务器。客户端和服务器的端口不必相同。使用netstat查看您的浏览器正在使用哪些端口。 - Tarnay Kálmán
8个回答

84

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

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

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


ReuseAddress属性用于使服务器应用程序能够使用指定的地址和端口号侦听连接,即使它们最近已被使用。这用于使服务器关闭侦听套接字并立即重新打开它,而不会出现错误。 - Tarnay Kálmán
他想要两次绑定到同一个端口... 因此,ReuseAddress不相关。 - Tarnay Kálmán
那是不正确的。ReuseAddress 的意思就是它所说的,没有关于套接字是服务器还是客户端的条件限制。如果你想在一个套接字上重用一个端口来发送数据,在另一个套接字上重用一个端口来接收数据,你是可以这样做的。 - sipsorcery
投票太旧了,除非帖子被编辑,否则无法更改...哎呀...请编辑问题以便我可以给你点赞。 - Tarnay Kálmán
如果你加上下面的语句,sipwiz的原始建议就能实现: udpClient.Client.Bind(localpt); - Cecil Has a Name
太棒了。我曾经尝试将ExclusiveAddressUse设置为false,但没有成功,几乎放弃了。再一次感谢Stack Overflow的帮助。 - Walden Leverich

39

我本来认为这是不可能的,但是...嗯...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! - Cecil Has a Name

5
以下是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();
        }
    }
}

你好,如果两个应用程序都是发送方和接收方,那该怎么办?从这个例子中我理解,如果一个应用程序只是发送方,另一个应用程序只是接收方,那么它只能在同一台机器上运行,对吗? - Scavs
1
你的示例标签错误(发送方应为“客户端”,接收方应为“服务器”)。 - Mud

2
您可以在您的网络卡或回环上放置多个IP地址,并将服务器和客户端绑定到不同的IP地址上。否则,虚拟机方法肯定可行。

多IP地址的方式更加轻量级(并且不会有任何性能损失),可以很好地工作。 - Tarnay Kálmán

1

即使我更改了代码以便我可以传递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() 方法上产生异常.. 抱歉。


1

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

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


0
我的建议是:不要将端口号传递到UdpClient构造函数中。从文档(有点稀疏,我知道...)看来,如果这样做,UdpClient将尝试绑定到该端口(正如sysrqb所提到的,这是不允许的)。 (如果不这样做,我相信UdpClient将在随机端口上侦听任何回复。您还可以选择一个未使用的端口。)

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


生成这些数据包的程序是专有的,问题在于它绑定到了发送端口。 - Tarnay Kálmán

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接