我正在尝试完成UDP打孔 . 我的理论基于this article和WIKI page,但我面临着C#编码的一些问题 . 这是我的问题:
使用已发布的代码here我现在能够连接到远程计算机并在同一端口上侦听传入连接(将2个UDP客户端绑定到同一端口) .
由于某种原因,对同一端口的两个绑定相互阻止接收任何数据 . 我有一个响应我的连接的UDP服务器,所以如果我在将任何其他客户端绑定到端口之前先连接到它,我会得到它的响应 .
如果我将另一个客户端绑定到该端口,则不会在任一客户端上接收数据 .
以下是显示我的问题的2个代码片段 . 第一个连接到远程服务器以在NAT设备上创建规则,然后在另一个线程上启动侦听器以捕获传入的数据包 . 然后代码将数据包发送到本地IP,以便侦听器获取它 . 第二个只发送数据包到本地IP,以确保这是有效的 . 我知道这不是真正的打孔,因为我在不使用NAT设备的情况下将数据包发送给自己 . 我现在面临一个问题,如果我在NAT设备旁边使用计算机进行连接,我认为这不会有任何不同 .
[编辑] 2/4/2012我尝试在我的网络上使用另一台计算机和WireShark(数据包嗅探器)来测试监听器 . 我看到从其他计算机传入的数据包但是没有被侦听器UDP客户端(udpServer)或发送方UDP客户端(客户端)接收 .
[编辑] 2/5/2010我现在添加了一个函数调用,在初始发送和接收数据包之后关闭第一个UDP客户端,只有第二个UDP客户端才能侦听端口 . 这有效,我可以从该端口上的网络内部接收数据包 . 我现在将尝试从网络外部发送和接收数据包 . 我一发现就会发布我的发现 .
使用此代码,我在侦听客户端上获取数据:
static void Main(string[] args)
{
IPEndPoint localpt = new IPEndPoint(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
ThreadPool.QueueUserWorkItem(delegate
{
UdpClient udpServer = new UdpClient();
udpServer.ExclusiveAddressUse = false;
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpServer.Client.Bind(localpt);
IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine("Listening on " + localpt + ".");
byte[] buffer = udpServer.Receive(ref inEndPoint); //this line will block forever
Console.WriteLine("Receive from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
});
Thread.Sleep(1000);
UdpClient udpServer2 = new UdpClient(6000);
// the following lines work and the data is received
udpServer2.Connect(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
udpServer2.Send(new byte[] { 0x41 }, 1);
Console.Read();
}
如果我使用以下代码,在客户端和服务器之间的连接和数据传输之后,侦听UDP客户端将不会收到任何内容:
static void Main(string[] args)
{
IPEndPoint localpt = new IPEndPoint(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
//if the following lines up until serverConnect(); are removed all packets are received correctly
client = new UdpClient();
client.ExclusiveAddressUse = false;
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
client.Client.Bind(localpt);
remoteServerConnect(); //connection to remote server is done here
//response is received correctly and printed to the console
ThreadPool.QueueUserWorkItem(delegate
{
UdpClient udpServer = new UdpClient();
udpServer.ExclusiveAddressUse = false;
udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpServer.Client.Bind(localpt);
IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine("Listening on " + localpt + ".");
byte[] buffer = udpServer.Receive(ref inEndPoint); //this line will block forever
Console.WriteLine("Receive from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
});
Thread.Sleep(1000);
UdpClient udpServer2 = new UdpClient(6000);
// I expected the following line to work and to receive this as well
udpServer2.Connect(Dns.Resolve(Dns.GetHostName()).AddressList[0], 4545);
udpServer2.Send(new byte[] { 0x41 }, 1);
Console.Read();
}
5 回答
如果我理解正确,你试图在两个客户端之间进行点对点通信,每个客户端位于不同的NAT后面,使用中介服务器进行打孔?
几年前我在c#中做了完全相同的事情,我还没有找到代码,但是如果你喜欢的话会给你一些指示:
首先,我不会在udpclient上使用Connect()函数,因为UDP是一个无连接协议,所有这个功能实际上都隐藏了UDP套接字的功能 .
您应该执行以下步骤:
在服务器上打开UDP套接字,其端口未被防火墙阻止,位于特定端口(例如 Bind 此套接字到所选端口,例如23000)
在第一个客户端上创建UDP套接字,并以23000向服务器发送内容. Do not bind this socket . 当使用udp发送数据包时,windows会自动为插槽分配一个空闲端口
从其他客户端执行相同操作
服务器现在从2个不同地址的2个客户端收到2个数据包,有2个不同的端口 . 测试服务器是否可以在同一地址和端口上发回数据包 . (如果这不起作用 . 如果你可以在不打开端口的情况下玩游戏,你知道它的工作原理:D)
服务器现在应该将其他客户端的地址和端口发送到每个连接的客户端 .
客户端现在应该能够使用UDP将数据包发送到从服务器接收的地址 .
您应该注意nat上使用的端口可能与客户端PC上的端口不同!!服务器应将此外部端口分发给客户端 . You must use the external adresses and the external ports to send to!
另请注意,您的NAT可能不支持此类端口转发 . 一些NAT将指定端口上的所有传入流量转发给您的客户端,这就是您想要的 . 但是有些nat会对传入的数据包进行过滤,因此它可能会阻止其他客户端数据包 . 这在使用标准个人用户路由器时不太可能 .
您是否尝试过使用Async函数,以下是如何使其工作的示例,可能需要一些工作才能使其100%正常运行:
我希望这有帮助 .
编辑:经过更多测试后,除非我启用UPnP,否则这对我来说似乎根本不起作用 . 所以我在这里写的很多东西你可能觉得很有用但是许多人没有启用UPnP(因为它存在安全风险)所以它不适用于他们 .
这是一些使用PubNub作为中继服务器的代码:) . 我不建议在没有测试的情况下使用此代码,因为它不完美(我不确定它是否是安全的或正确的做事方式?idk我不是网络专家)但它应该给你一个想法该怎么做到目前为止,它至少对我来说是一个爱好项目 . 它缺少的是:
测试客户端是否在您的LAN上 . 我只发送两个适用于您的LAN和另一个网络上的设备,但这是非常低效的 .
例如,当客户端关闭程序时,测试客户端何时停止监听 . 因为这是UDP,所以它是无状态的,所以如果没有人得到它,它就不会这样做
我使用Open.NAT以编程方式进行端口转发,但这可能不适用于某些设备 . 具体来说,它使用UPnP,它有点不安全,需要手动端口转发UDP端口1900 . 一旦他们这样做,它在大多数路由器上都受支持,但许多人还没有这样做 .
首先,您需要一种方法来获取外部和本地IP . 以下是获取本地IP的代码:
以下是一些代码,用于通过尝试一些旨在返回外部IP的网站来获取外部IP
现在我们需要找到一个开放端口并将其转发到外部端口 . 如上所述,我使用Open.NAT . 首先,在查看registered UDP ports之后,将您认为合适的端口列表放在一起 . 以下是一些例子:
现在我们可以循环遍历它们,并希望找到一个未使用的端口转发:
现在为PubNub中继服务器代码(P2PPeer将在下面定义) . 这里有很多,所以我不会真的解释它,但希望代码足够清楚,以帮助您了解正在发生的事情
这是一个P2PPeer
最后,这是我的所有用途:
我愿意接受评论和提问,如果这里的内容不好或不起作用,请随时提供反馈 . 我的代码在翻译中引入了一些错误,我最终将在这里修复,但这至少应该让你知道该怎么做 .
Update:
无论哪个UdpClients首先绑定是由Windows发送传入数据包 . 在您的示例中,尝试将设置侦听线程的代码块移动到顶部 .
您确定问题不仅仅是接收线程只是为了处理单个接收而编写的吗?尝试使用如下替换接收线程 .
很抱歉上传了如此巨大的代码,但我想这很清楚地解释了事情是如何运作的,并且可能非常有用 . 如果您对此代码有疑问,请告诉我们 .
Note:
这只是一个草案
(重要)您必须通过本地 endpoints 通知服务器 . 如果你不这样做,即使服务器不在NAT,你也无法在一个NAT后面的两个对等体之间进行通信(例如,在一台本地机器上)
你必须关闭"puncher"客户端(至少,我没有设法收到任何数据包,直到我这样做) . 稍后您将能够使用其他
UdpClient
与服务器通信的cource它不适用于对称NAT
如果您发现此代码中的内容是"terrible practice",请告诉我,我不是网络专家:)
Server.cs
Client.cs
Demo.cs
Protocol.cs
我不确定这种方法有多好,也许protobuf可以做得更好