在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
在写完Object 672后,软件的一个致命问题暴露出来,如果服务器和客户端都在内网环境下,即双方都通过NAT来接触外网,那么此时客户端是无法直接和服务器交流的。 解决方案可以是: 1:把服务器部署在不存在NAT的公网环境下。 2:使用常见的NAT穿透方法比如UDP打洞,或者STUN协议,但是这些方法都需要另一个已知的部署在公网环境下的服务器。 3:就是这篇文章主要讨论的方案,即不需要部署任何公网环境下的服务器,通过路由器支持的UPnP协议来把内网的接口绑定到公网接口上。 UPnP的一大优势就是不会像UDP打洞那样,内网接口不需要先向外部接口发送UDP包来把绑定的公网接口告诉NAT,而且对于对称NAT,UDP打洞是无效的。而UPnP一旦设置成功后,内网接口完全以绑定的公网接口暴露在公网中。
演示程序的运行是这样的:
具体过程:
在.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选项中看到这些数据:
设置好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。
作者:Mgen
本文版权归作者所有,欢迎以网址(链接)的方式转载,不欢迎复制文章内容的方式转载,其一是为了在搜索引擎中去掉重复文章内容,其二复制后的文章往往没有提供本博客的页面格式和链接,造成文章可读性很差。望有素质人自觉遵守上述建议。 如果一定要以复制文章内容的方式转载,必须在文章开头标明作者信息和原文章链接地址。否则保留追究法律责任的权利。
在写完Object 672后,软件的一个致命问题暴露出来,如果服务器和客户端都在内网环境下,即双方都通过NAT来接触外网,那么此时客户端是无法直接和服务器交流的。 解决方案可以是: 1:把服务器部署在不存在NAT的公网环境下。 2:使用常见的NAT穿透方法比如UDP打洞,或者STUN协议,但是这些方法都需要另一个已知的部署在公网环境下的服务器。 3:就是这篇文章主要讨论的方案,即不需要部署任何公网环境下的服务器,通过路由器支持的UPnP协议来把内网的接口绑定到公网接口上。 UPnP的一大优势就是不会像UDP打洞那样,内网接口不需要先向外部接口发送UDP包来把绑定的公网接口告诉NAT,而且对于对称NAT,UDP打洞是无效的。而UPnP一旦设置成功后,内网接口完全以绑定的公网接口暴露在公网中。
演示程序的运行是这样的:
具体过程:
在.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选项中看到这些数据:
设置好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。
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论