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

解决方案可以是:

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

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

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

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

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

具体过程:

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 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选项中看到这些数据:

设置好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

Task.Run(() =>

{

);

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

C#:使用UPnP来穿透NAT使内网接口对外网可见的更多相关文章

  1. frp内网 穿透映射使内网svn可外网访问

    起因 公司svn目前部署在内网服务器上,现在想在家中也可以使用,因此需要外网访问内网的工具 经过 使用过几个产品: utools,一个小巧的windows下的工具,内网映射只是它的一个小功能,支持tc ...

  2. [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)

    NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...

  3. [p2p]UDP用打洞技术穿透NAT的原理与实现

    首先先介绍一些基本概念:            NAT(Network Address             Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的, ...

  4. 【转】P2P之UDP穿透NAT的原理与实现(附源代码)

    作者:shootingstars (有容乃大,无欲则刚)  日期:2004-5-25 出处:P2P中国(PPcn.net) P2P 之 UDP穿透NAT的原理与实现(附源代码)原创:shootings ...

  5. P2P之UDP穿透NAT的原理与实现

    首先先介绍一些基本概念: NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用.NAT分为两 ...

  6. 利用iptables的NAT代理实现内网访问外网

    利用NAT代理实现内网访问外网 背景及原理 若局域网中的两台计算机只能有一台能够访问外网,而这两台计算机之间能相互通信,那么可以配置能访问外网的那台服务器实现路由器的功能,即实现其他机器的NAT转换, ...

  7. UDP穿透NAT原理解析

    转自:http://www.2cto.com/net/201201/116793.html NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益 ...

  8. float使内联支持宽高

    float使内联元素支持了宽高,可以设置宽高属性:float消除内联元素的空格:

  9. P2P之UDP穿透NAT原理

    首先先介绍一些基本概念:             NAT(Network   Address   Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就 ...

随机推荐

  1. python cookboo 文件与IO 函数

    写出文本数据 g = open('test.txt', 'rt', newline='',encoding = 'utf-8', errors='replace') t是windows平台特有的所谓t ...

  2. Linux环境下的图形系统和AMD R600显卡编程(1)——Linux环境下的图形系统简介

    转:https://www.cnblogs.com/shoemaker/p/linux_graphics01.html Linux/Unix环境下最早的图形系统是Xorg图形系统,Xorg图形系统通过 ...

  3. 每日练习level-7

    1.有序列表.无序列表.自定义列表如何使用?写个简单的例子.三者在语义上有什么区别?使用场景是什么? 能否嵌套? 有序列表是一列使用数字进行标记的项目,它使用<li>包含于<ol&g ...

  4. Class文件结构(更新中)

    Class文件是一组以8位字节为单位的二进制流,当遇见需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储. 格式,采用伪结构,只有两种数据结构:无符号数和表. 无符 ...

  5. 大话设计模式之PHP篇 - 适配器模式

    定义将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作. 角色Target适配目标,该角色定义把其他类转换为何种接口,也就是我们的期望接口.Adapte ...

  6. 0x5C 计数类DP

    cf 559C 考虑到黑色的格子很少,那么我把(1,1)变成黑色,然后按每个黑色格子接近终点的程度排序,计算黑色格子不经过另一个黑色格子到达终点的方案,对于当前的格子,要减去在它右下角的所有方案数(注 ...

  7. 解决Pycharm中matplotlib画图出错问题(AttributeError: module 'matplotlib' has no attribute 'verbose')

    最近在Linux中使用pycharm过程中使用matplotlib无法画图,总是提示错误 /usr/bin/python3. /home/leo/PycharmProjects/untitled1/E ...

  8. hdu 5663 Hillan and the girl 莫比乌斯反演

    Hillan and the girl Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/O ...

  9. Java日期时间输出格式优化

    使用printf格式化日期 printf 方法可以很轻松地格式化时间和日期.使用两个字母格式,它以 %t 开头并且以下面表格中的一个字母结尾. 转  换  符 说    明 示    例 c 包括全部 ...

  10. pandas读取Excel

    time31 = pd.read_excel('F:/save_file/3问出车表.xlsx', sheetname='Sheet1') # 读取‘3问出车表.xlsx’中的Sheet1表单, ti ...