C#:使用UPnP来穿透NAT使内网接口对外网可见

在写完Object 672后,软件的一个致命问题暴露出来,若是服务器和客户端都在内网环境下,即双方都经过NAT来接触外网,那么此时客户端是没法直接和服务器交流的。html

 

解决方案能够是:web

1:把服务器部署在不存在NAT的公网环境下。浏览器

2:使用常见的NAT穿透方法好比UDP打洞,或者STUN协议,可是这些方法都须要另外一个已知的部署在公网环境下的服务器。服务器

3:就是这篇文章主要讨论的方案,即不须要部署任何公网环境下的服务器,经过路由器支持的UPnP协议来把内网的接口绑定到公网接口上。app

 

UPnP的一大优点就是不会像UDP打洞那样,内网接口不须要先向外部接口发送UDP包来把绑定的公网接口告诉NAT,并且对于对称NAT,UDP打洞是无效的。而UPnP一旦设置成功后,内网接口彻底以绑定的公网接口暴露在公网中。socket

 

演示程序的运行是这样的:工具

image


具体过程:post

1. 输出用户Host Name和内网IP地址。

2. 经过UPnP把内网IP地址,内部端口号绑定到一个外部端口号上。测试

3. 经过HTTP从外部网站获取公网IP地址。网站

4. 在内网中建立TCP Socket服务器。

5. 创建另外一个TCP Socket客户端,而后尝试链接上面获取的公网IP和UPnP绑定的外部端口。

6. 若是一切没有问题的话,此时会成功链接到服务器,并收到回应!

 

在.NET环境下使用Windows的UPnP组件须要如今工程中引用:NATUPnP 1.0 Type Library,这是一个COM类库。

下面开始逐句分析源代码,源代码均拟用户已加入下列命名空间:

using System.Net;                     

using System.Net.Sockets;            

using System.Text.RegularExpressions;  //提取IP时的正则

using System.Threading.Tasks;          //Task

using System.IO;                       //读取服务器信息用到StreamReader

using NATUPNPLib;                      //Windows UPnP COM组件

  

首先输出本机(也就是内网接口信息),这个很简单了:

//获取Host Name

var name =Dns.GetHostName();

Console.WriteLine("用户:"+ name);

//从当前Host Name解析IP地址,筛选IPv4地址是本机的内网IP地址。

var ipv4 =Dns.GetHostEntry(name).AddressList.Where(i => i.AddressFamily ==AddressFamily.InterNetwork).FirstOrDefault();

Console.WriteLine("内网IP:"+ ipv4);

  

接 下来就是设置UPnP了,首先须要初始化UPnPNAT类型(他是一个接口,只不过经过CoClass特性把执行导向UPnPNATClass类型),接 着经过UPnPNAT的StaticPortMappingCollection来添加或者删除UPnP绑定。注意在没有路由器或者路由器的UPnP不开 启的状况下,StaticPortMappingCollection属性可能会返回null。


代码以下:

Console.WriteLine("设置UPnP");

//UPnP绑定信息

var eport =8733;

var iport =8733;

var description ="Mgen测试";


//建立COM类型

var upnpnat =newUPnPNAT();

var mappings = upnpnat.StaticPortMappingCollection;

 

//错误判断

if (mappings ==null)

{

    Console.WriteLine("没有检测到路由器,或者路由器不支持UPnP功能。");

    return;

}

 

//添加以前的ipv4变量(内网IP),内部端口,和外部端口

mappings.Add(eport, "TCP", iport, ipv4.ToString(), true, description);

Console.WriteLine("外部端口:{0}", eport);

Console.WriteLine("内部端口:{0}", iport);

  

若是成功后,你应该能够在路由器的UPnP选项中看到这些数据:

image

   

设置好UPnP后,开始获取外网IP地址,能够经过这个网址(http://checkip.dyndns.org/)。

此时只须要发送一个HTTP GET请求,而后把返回的HTML中的IP地址提取出来就能够了,咱们用正则来提取IP地址。

代码以下:

//外网IP变量

string eip;

//正则

var regex =@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b";

using (var webclient =newWebClient())

{

    var rawRes = webclient.DownloadString("http://checkip.dyndns.org/");

    eip =Regex.Match(rawRes, regex).Value;

}

Console.WriteLine("外网IP:"+ eip);

 

OK,这个时候(若是一切顺利的话),一切准备工做都作好了。咱们有了:内网IP,内部端口,外网IP,外部端口。那么就能够作一个TCP链接作测试了。

直接创建一个TCP服务端,表明在NAT下的服务器,注意端口号要绑定到UPnP设置时的内部端口。

代码:

//在NAT下的服务器

var socket =newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//绑定内网IP和内部端口

socket.Bind(newIPEndPoint(ipv4, iport));

socket.Listen(1);

 

//在另外一个线程中运行客户端Socket

Task.Run(() =>

    {

        Task.Delay(1000);

        ClientSocket(eip, eport);

    });

    

//成功链接

var client = socket.Accept();

//服务器向客户端发送信息

client.Send(Encoding.Unicode.GetBytes("=== 欢迎来到Mgen的服务器!==="+Environment.NewLine));

Console.ReadKey(false);

  

上 面的ClientSocket方法就是客户端的Socket链接执行,注意TCP协议是不保留数据边界的,所以服务器在发送消息时,后面加了个换行符 (Environment.NewLine),而后在客户端接受数据时,使用Socket –> NetworkStream –> StreamReader的嵌套组合,最后由StreamReader的ReadLine读取数据,这样确保会读到最后的换行符。

 

ClientSocket方法的执行代码:

//ip参数和port参数是公网的IP地址,和UPnP中的外部端口

staticvoid ClientSocket(string ip, int port)

{

    try

    {

        Console.WriteLine("创建客户端TCP链接");

        var socket =newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        socket.Connect(newIPEndPoint(IPAddress.Parse(ip), port));

        using (var ns =newNetworkStream(socket))

        using (var sr =newStreamReader(ns, Encoding.Unicode))

        {

            Console.WriteLine("收到来自服务器的回应:");

            Console.WriteLine(sr.ReadLine());

        }

    }

    catch (Exception ex)

    {

        Console.WriteLine(ex.Message);

    }

}

 

OK。

 
  

源代码下载     下载页面      注意:连接是微软SkyDrive页面,下载时请用浏览器直接下载,用某些下载工具可能没法下载      源代码环境:Microsoft Visual Studio Express 2012 for Windows Desktop      注意:源代码不包含引用的外部类库文件

 

出处:http://www.cnblogs.com/cuihongyu3503319/archive/2013/02/05/2892764.html

相关文章
相关标签/搜索