bro*_*ooc 21 c# udp traversal nat hole-punching
我正在尝试完成UDP打孔.我的理论基于这篇文章和WIKI页面,但我面临着C#编码的一些问题.这是我的问题:
使用此处发布的代码,我现在能够连接到远程计算机并在同一端口上侦听传入连接(将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();
}
Run Code Online (Sandbox Code Playgroud)
如果我使用以下代码,在客户端和服务器之间的连接和数据传输之后,侦听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();
}
Run Code Online (Sandbox Code Playgroud)
MHG*_*ork 13
如果我理解正确,你试图在两个客户端之间进行点对点通信,每个客户端位于不同的NAT后面,使用中介服务器进行打孔?
几年前我在c#中做了完全相同的事情,我还没有找到代码,但是如果你喜欢的话会给你一些指示:
首先,我不会在udpclient上使用Connect()函数,因为UDP是一个无连接协议,所有这个功能实际上都隐藏了UDP套接字的功能.
您应该执行以下步骤:
您应该注意nat上使用的端口可能与客户端PC上的端口不同!! 服务器应将此外部端口分发给客户端.您必须使用外部地址和外部端口发送到!
另请注意,您的NAT可能不支持此类端口转发.一些NAT将指定端口上的所有传入流量转发给您的客户端,这就是您想要的.但是有些nat会对传入的数据包进行过滤,因此它可能会阻止其他客户端数据包.这在使用标准个人用户路由器时不太可能.
编辑:经过大量测试后,除非我启用 UPnP,否则这对我来说似乎根本不起作用。所以我在这里写的很多东西你可能会觉得有用,但很多人没有启用 UPnP(因为它存在安全风险)所以它对他们不起作用。
这是一些使用 PubNub 作为中继服务器的代码:)。我不建议在没有测试的情况下使用此代码,因为它并不完美(我不确定它是否安全或正确的做事方式?idk 我不是网络专家)但它应该给你一个想法做什么。到目前为止,它至少在一个业余爱好项目中对我有用。它缺少的东西是:
因此,首先,您需要一种获取外部和本地 IP 的方法。这是获取本地IP的代码:
// From http://stackoverflow.com/questions/6803073/get-local-ip-address
public string GetLocalIp()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
throw new Exception("Failed to get local IP");
}
Run Code Online (Sandbox Code Playgroud)
这是一些通过尝试一些旨在返回您的外部 IP 的网站来获取您的外部 IP 的代码
public string GetExternalIp()
{
for (int i = 0; i < 2; i++)
{
string res = GetExternalIpWithTimeout(400);
if (res != "")
{
return res;
}
}
throw new Exception("Failed to get external IP");
}
private static string GetExternalIpWithTimeout(int timeoutMillis)
{
string[] sites = new string[] {
"http://ipinfo.io/ip",
"http://icanhazip.com/",
"http://ipof.in/txt",
"http://ifconfig.me/ip",
"http://ipecho.net/plain"
};
foreach (string site in sites)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(site);
request.Timeout = timeoutMillis;
using (var webResponse = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = webResponse.GetResponseStream())
{
using (StreamReader responseReader = new System.IO.StreamReader(responseStream, Encoding.UTF8))
{
return responseReader.ReadToEnd().Trim();
}
}
}
}
catch
{
continue;
}
}
return "";
}
Run Code Online (Sandbox Code Playgroud)
现在我们需要找到一个开放的端口并将其转发到外部端口。如上所述,我使用了Open.NAT。首先,在查看注册的 UDP 端口后,您将您认为适合您的应用程序使用的端口列表放在一起。举几个例子:
public static int[] ports = new int[]
{
5283,
5284,
5285,
5286,
5287,
5288,
5289,
5290,
5291,
5292,
5293,
5294,
5295,
5296,
5297
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以遍历它们并希望找到一个未用于使用端口转发的:
public UdpClient GetUDPClientFromPorts(out Socket portHolder, out string localIp, out int localPort, out string externalIp, out int externalPort)
{
localIp = GetLocalIp();
externalIp = GetExternalIp();
var discoverer = new Open.Nat.NatDiscoverer();
var device = discoverer.DiscoverDeviceAsync().Result;
IPAddress localAddr = IPAddress.Parse(localIp);
int workingPort = -1;
for (int i = 0; i < ports.Length; i++)
{
try
{
// You can alternatively test tcp with nc -vz externalip 5293 in linux and
// udp with nc -vz -u externalip 5293 in linux
Socket tempServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
tempServer.Bind(new IPEndPoint(localAddr, ports[i]));
tempServer.Close();
workingPort = ports[i];
break;
}
catch
{
// Binding failed, port is in use, try next one
}
}
if (workingPort == -1)
{
throw new Exception("Failed to connect to a port");
}
int localPort = workingPort;
// You could try a different external port if the below code doesn't work
externalPort = workingPort;
// Mapping ports
device.CreatePortMapAsync(new Open.Nat.Mapping(Open.Nat.Protocol.Udp, localPort, externalPort));
// Bind a socket to our port to "claim" it or cry if someone else is now using it
try
{
portHolder = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
portHolder.Bind(new IPEndPoint(localAddr, localPort));
}
catch
{
throw new Exception("Failed, someone is now using local port: " + localPort);
}
// Make a UDP Client that will use that port
UdpClient udpClient = new UdpClient(localPort);
return udpClient;
}
Run Code Online (Sandbox Code Playgroud)
现在为 PubNub 中继服务器代码(P2PPeer 将在下面定义)。这里有很多,所以我不会真正解释它,但希望代码足够清晰,以帮助您理解发生了什么
public delegate void NewPeerCallback(P2PPeer newPeer);
public event NewPeerCallback OnNewPeerConnection;
public Pubnub pubnub;
public string pubnubChannelName;
public string localIp;
public string externalIp;
public int localPort;
public int externalPort;
public UdpClient udpClient;
HashSet<string> uniqueIdsPubNubSeen;
object peerLock = new object();
Dictionary<string, P2PPeer> connectedPeers;
string myPeerDataString;
public void InitPubnub(string pubnubPublishKey, string pubnubSubscribeKey, string pubnubChannelName)
{
uniqueIdsPubNubSeen = new HashSet<string>();
connectedPeers = new Dictionary<string, P2PPeer>;
pubnub = new Pubnub(pubnubPublishKey, pubnubSubscribeKey);
myPeerDataString = localIp + " " + externalIp + " " + localPort + " " + externalPort + " " + pubnub.SessionUUID;
this.pubnubChannelName = pubnubChannelName;
pubnub.Subscribe<string>(
pubnubChannelName,
OnPubNubMessage,
OnPubNubConnect,
OnPubNubError);
return pubnub;
}
//// Subscribe callbacks
void OnPubNubConnect(string res)
{
pubnub.Publish<string>(pubnubChannelName, connectionDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
}
void OnPubNubError(PubnubClientError clientError)
{
throw new Exception("PubNub error on subscribe: " + clientError.Message);
}
void OnPubNubMessage(string message)
{
// The message will be the string ["localIp externalIp localPort externalPort","messageId","channelName"]
string[] splitMessage = message.Trim().Substring(1, message.Length - 2).Split(new char[] { ',' });
string peerDataString = splitMessage[0].Trim().Substring(1, splitMessage[0].Trim().Length - 2);
// If you want these, I don't need them
//string peerMessageId = splitMessage[1].Trim().Substring(1, splitMessage[1].Trim().Length - 2);
//string channelName = splitMessage[2].Trim().Substring(1, splitMessage[2].Trim().Length - 2);
string[] pieces = peerDataString.Split(new char[] { ' ', '\t' });
string peerLocalIp = pieces[0].Trim();
string peerExternalIp = pieces[1].Trim();
string peerLocalPort = int.Parse(pieces[2].Trim());
string peerExternalPort = int.Parse(pieces[3].Trim());
string peerPubnubUniqueId = pieces[4].Trim();
pubNubUniqueId = pieces[4].Trim();
// If you are on the same device then you have to do this for it to work idk why
if (peerLocalIp == localIp && peerExternalIp == externalIp)
{
peerLocalIp = "127.0.0.1";
}
// From me, ignore
if (peerPubnubUniqueId == pubnub.SessionUUID)
{
return;
}
// We haven't set up our connection yet, what are we doing
if (udpClient == null)
{
return;
}
// From someone else
IPEndPoint peerEndPoint = new IPEndPoint(IPAddress.Parse(peerExternalIp), peerExternalPort);
IPEndPoint peerEndPointLocal = new IPEndPoint(IPAddress.Parse(peerLocalIp), peerLocalPort);
// First time we have heard from them
if (!uniqueIdsPubNubSeen.Contains(peerPubnubUniqueId))
{
uniqueIdsPubNubSeen.Add(peerPubnubUniqueId);
// Dummy messages to do UDP hole punching, these may or may not go through and that is fine
udpClient.Send(new byte[10], 10, peerEndPoint);
udpClient.Send(new byte[10], 10, peerEndPointLocal); // This is if they are on a LAN, we will try both
pubnub.Publish<string>(pubnubChannelName, myPeerDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
}
// Second time we have heard from them, after then we don't care because we are connected
else if (!connectedPeers.ContainsKey(peerPubnubUniqueId))
{
//bool isOnLan = IsOnLan(IPAddress.Parse(peerExternalIp)); TODO, this would be nice to test for
bool isOnLan = false; // For now we will just do things for both
P2PPeer peer = new P2PPeer(peerLocalIp, peerExternalIp, peerLocalPort, peerExternalPort, this, isOnLan);
lock (peerLock)
{
connectedPeers.Add(peerPubnubUniqueId, peer);
}
// More dummy messages because why not
udpClient.Send(new byte[10], 10, peerEndPoint);
udpClient.Send(new byte[10], 10, peerEndPointLocal);
pubnub.Publish<string>(pubnubChannelName, connectionDataString, OnPubNubTheyGotMessage, OnPubNubMessageFailed);
if (OnNewPeerConnection != null)
{
OnNewPeerConnection(peer);
}
}
}
//// Publish callbacks
void OnPubNubTheyGotMessage(object result)
{
}
void OnPubNubMessageFailed(PubnubClientError clientError)
{
throw new Exception("PubNub error on publish: " + clientError.Message);
}
Run Code Online (Sandbox Code Playgroud)
这是一个 P2PPeer
public class P2PPeer
{
public string localIp;
public string externalIp;
public int localPort;
public int externalPort;
public bool isOnLan;
P2PClient client;
public delegate void ReceivedBytesFromPeerCallback(byte[] bytes);
public event ReceivedBytesFromPeerCallback OnReceivedBytesFromPeer;
public P2PPeer(string localIp, string externalIp, int localPort, int externalPort, P2PClient client, bool isOnLan)
{
this.localIp = localIp;
this.externalIp = externalIp;
this.localPort = localPort;
this.externalPort = externalPort;
this.client = client;
this.isOnLan = isOnLan;
if (isOnLan)
{
IPEndPoint endPointLocal = new IPEndPoint(IPAddress.Parse(localIp), localPort);
Thread localListener = new Thread(() => ReceiveMessage(endPointLocal));
localListener.IsBackground = true;
localListener.Start();
}
else
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(externalIp), externalPort);
Thread externalListener = new Thread(() => ReceiveMessage(endPoint));
externalListener.IsBackground = true;
externalListener.Start();
}
}
public void SendBytes(byte[] data)
{
if (client.udpClient == null)
{
throw new Exception("P2PClient doesn't have a udpSocket open anymore");
}
//if (isOnLan) // This would work but I'm not sure how to test if they are on LAN so I'll just use both for now
{
client.udpClient.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(localIp), localPort));
}
//else
{
client.udpClient.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(externalIp), externalPort));
}
}
// Encoded in UTF8
public void SendString(string str)
{
SendBytes(System.Text.Encoding.UTF8.GetBytes(str));
}
void ReceiveMessage(IPEndPoint endPoint)
{
while (client.udpClient != null)
{
byte[] message = client.udpClient.Receive(ref endPoint);
if (OnReceivedBytesFromPeer != null)
{
OnReceivedBytesFromPeer(message);
}
//string receiveString = Encoding.UTF8.GetString(message);
//Console.Log("got: " + receiveString);
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这里是我所有的用途:
using PubNubMessaging.Core; // Get from PubNub GitHub for C#, I used the Unity3D library
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
Run Code Online (Sandbox Code Playgroud)
我对评论和问题持开放态度,如果这里的某些做法不好或不起作用,请随时提供反馈。从我的代码翻译中引入了一些错误,我最终会在这里修复这些错误,但这至少应该让您知道该怎么做。
更新:
第一个绑定的 UdpClient 就是 Windows 将发送传入数据包的那个。在您的示例中,尝试将设置侦听线程的代码块移至顶部。
您确定问题不仅仅是接收线程仅为处理单个接收而编写吗?尝试将接收线程替换为如下所示。
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 + ".");
while (inEndPoint != null)
{
byte[] buffer = udpServer.Receive(ref inEndPoint);
Console.WriteLine("Bytes received from " + inEndPoint + " " + Encoding.ASCII.GetString(buffer) + ".");
}
});
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
18452 次 |
最近记录: |