如何让两台处在不同内网的主机直接互连?你需要内网穿透!
 
 

     上图是一个非完整版内外网通讯图由内网端先发起,内网设备192.168.1.2:6677发送数据到外网时候必须经过nat会转换成对应的外网ip+端口,然后在发送给外网设备,外网设备回复数据也是发给你的外网ip+端口。这只是单向的内去外,那反过来,如果外网的设备需要主动访问我局域网里的某一个设备是无法访问的,因为这个时候还没做nat转换所以外网不知道你内网设备的应用具体对应的是哪个端口,这个时候我们就需要内网穿透了,内网穿透也叫NAT穿透;
 
       穿透原理
 
       如上图所示经NAT转换后的内外网地址+端口,会缓存一段时间,在这段时间内192.168.1.2:6677和112.48.69.2020:8899的映射关系会一直存在,这样你的内网主机就得到一个外网地址,这个对应关系又根据NAT转换方法类型的不同,得用对应的方式实现打洞,NAT转换方法类型有下列几种(来源百度百科NAT):
 
     (1)Full cone NAT:即著名的一对一(one-to-one)NAT。
 
       一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。任意外部主机都能通过给eAddr:port2发包到iAddr:port1(纯天然不用打洞!)
 

(2)Address-Restricted cone NAT :限制地址,即只接收曾经发送到对端的IP地址来的数据包。

一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。任意外部主机(hostAddr:any)都能通过给eAddr:port2发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:any. "any"也就是说端口不受限制(只需知道某个转换后的外网ip+端口即可。)

      (3)Port-Restricted cone NAT:类似受限制锥形NAT(Restricted cone NAT),但是还有端口限制。

        一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。一个外部主机(hostAddr:port3)能够发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:port3.(双方需要各自知道对方转换后的外网ip+端口,然后一方先发一次尝试连接,另一方在次连接过来的时候就能直接连通了。)
 

(4)Symmetric NAT(对称NAT)

       每一个来自相同内部IP与port的请求到一个特定目的地的IP地址和端口,映射到一个独特的外部来源的IP地址和端口。
 
       同一个内部主机发出一个信息包到不同的目的端,不同的映射使用外部主机收到了一封包从一个内部主机可以送一封包回来(只能和Full cone NAT连,没法打洞,手机流量开热点就是,同一个本地端口连接不同的服务器得到的外网第地址和IP不同!)
 
例子:
 
下面用一个例子演示下“受限制锥形NAT”的打洞,实现了这个它前面两个类型也能通用。对称型的话不考虑,打不了洞。我们知道要实现两台“受限制锥形NAT”互连重点就是要知道对
方转换后的外网IP+端口,这样我们可以:
 
1. 准备一台Full cone NAT 类型的外网服务端,接受来自两个客户端的连接,并对应告知对方ip+端口;
 
2.知道了对方ip+端口 需要设置socke:Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);这样才能端口复用;目的就是让连接对外的端口一致;
 
3.最后,我们可以让两台客户端互相连接,或者一台先发一个请求,打个洞;另一个在去连接;
 
代码:
1.TCP+IOCP方式,相对 “面向对象”地实现穿透!
服务端 ServerListener类,用SocketAsyncEventArgs:

  1   /// <summary>
2 /// 打洞服务端,非常的简单,接收两个连接并且转发给对方;
3 /// </summary>
4 public class ServerListener : IServerListener
5 {
6 IPEndPoint EndPoint { get; set; }
7 //消息委托
8 public delegate void EventMsg(object sender, string e);
9 public static object obj = new object();
10 //通知消息
11 public event EventMsg NoticeMsg;
12 //接收事件
13 public event EventMsg ReceivedMsg;
14 /// <summary>
15 /// 上次链接的
16 /// </summary>
17 private Socket Previous;
18 public ServerListener(IPEndPoint endpoint)
19 {
20 this.EndPoint = endpoint;
21 }
22 private Socket listener;
23 public void Start()
24 {
25 this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
26 var connectArgs = new SocketAsyncEventArgs();
27 listener.Bind(EndPoint);
28 listener.Listen(2);
29 EndPoint = (IPEndPoint)listener.LocalEndPoint;
30 connectArgs.Completed += OnAccept;
31 //是否同步就完成了,同步完成需要自己触发
32 if (!listener.AcceptAsync(connectArgs))
33 OnAccept(listener, connectArgs);
34 }
35 byte[] bytes = new byte[400];
36 private void OnAccept(object sender, SocketAsyncEventArgs e)
37 {
38 Socket socket = null;
39 try
40 {
41 var remoteEndPoint1 = e.AcceptSocket.RemoteEndPoint.ToString();
42 NoticeMsg?.Invoke(sender, $"客户端:{remoteEndPoint1}连接上我了!\r\n");
43 SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs();
44 readEventArgs.Completed += OnSocketReceived;
45 readEventArgs.UserToken = e.AcceptSocket;
46 readEventArgs.SetBuffer(bytes, 0, 400);
47 if (!e.AcceptSocket.ReceiveAsync(readEventArgs))
48 OnSocketReceived(e.AcceptSocket, readEventArgs);
49 lock (obj)
50 {
51 socket = e.AcceptSocket;
52 //上次有链接并且链接还”健在“
53 if (Previous == null||! Previous.Connected)
54 {
55 Previous = e.AcceptSocket;
56 }
57 else
58 {
59 //Previous.SendAsync()..?
60 Previous.Send(Encoding.UTF8.GetBytes(remoteEndPoint1 + "_1"));
61 socket.Send(Encoding.UTF8.GetBytes(Previous.RemoteEndPoint.ToString() + "_2"));
62 NoticeMsg?.Invoke(sender, $"已经通知双方!\r\n");
63 Previous = null;
64 }
65 }
66
67 e.AcceptSocket = null;
68 if (e.SocketError != SocketError.Success)
69 throw new SocketException((int)e.SocketError);
70
71 if(!listener.AcceptAsync(e))
72 OnAccept(listener, e);
73 }
74 catch
75 {
76 socket?.Close();
77 }
78
79 }
80 public void Close()
81 {
82 using (listener)
83 {
84 // listener.Shutdown(SocketShutdown.Both);
85 listener.Close();
86 }
87 //throw new NotImplementedException();
88 }
89 /// <summary>
90 /// 此处留有一个小BUG,接收的字符串大于400的时候会有问题;可以参考客户端修改
91 /// </summary>
92 public void OnSocketReceived(object sender, SocketAsyncEventArgs e)
93 {
94 Socket socket = e.UserToken as Socket;
95 var remoteEndPoint = socket.RemoteEndPoint.ToString();
96 try
97 {
98 if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
99 {
100
101 ReceivedMsg?.Invoke(sender, $"收到:{remoteEndPoint}发来信息:{Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred)}\r\n");
102
103 }
104 else
105 {
106 socket?.Close();
107 NoticeMsg?.Invoke(sender, $"链接:{remoteEndPoint}释放啦!\r\n");
108 return;
109 }
110 if (!socket.ReceiveAsync(e))
111 OnSocketReceived(socket, e);
112 }
113 catch
114 {
115 socket?.Close();
116 }
117
118 //{
119 // if (!((Socket)sender).AcceptAsync(e))
120 // OnSocketReceived(sender, e);
121 //}
122 //catch
123 //{
124 // return;
125 //}
126 }
127 }

2.客户端类 PeerClient用BeginReceive和EndReceive实现异步;

  public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 100;
public byte[] buffer = new byte[BufferSize];
public List<byte> buffers = new List<byte>();
//是不是和服务器的链接
public bool IsServerCon = false;
}
/// <summary>
/// 打洞节点客户端 实现的功能:
/// 连接服务器获取对方节点ip
/// 请求对方ip(打洞)
/// 根据条件判断是监听连接还是监听等待连接
/// </summary>
public class PeerClient : IPeerClient
{
//ManualResetEvent xxxxDone = new ManualResetEvent(false);
//Semaphore
/// <summary>
/// 当前链接
/// </summary>
public Socket Client { get;private set; } #region 服务端
public string ServerHostName { get;private set; }
public int ServerPort { get; private set; }
#endregion #region 接收和通知事件
public delegate void EventMsg(object sender, string e);
//接收事件
public event EventMsg ReceivedMsg;
//通知消息
public event EventMsg NoticeMsg;
#endregion //本地绑定的节点
private IPEndPoint LocalEP; public PeerClient(string hostname, int port)
{
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.ServerHostName = hostname;
this.ServerPort = port; }
/// <summary>
/// 初始化客户端(包括启动)
/// </summary>
public void Init()
{
try
{
Client.Connect(ServerHostName, ServerPort);
}
catch (SocketException ex)
{
NoticeMsg?.Invoke(Client, $"连接服务器失败!{ex}!\r\n");
throw;
}
catch (Exception ex)
{
NoticeMsg?.Invoke(Client, $"连接服务器失败!{ex}!\r\n");
throw;
}
NoticeMsg?.Invoke(Client, $"连接上服务器了!\r\n");
var _localEndPoint = Client.LocalEndPoint.ToString();
LocalEP = new IPEndPoint(IPAddress.Parse(_localEndPoint.Split(':')[0])
, int.Parse(_localEndPoint.Split(':')[1]));
Receive(Client); }
private void Receive(Socket client)
{
try
{
StateObject state = new StateObject();
state.workSocket = client;
state.IsServerCon = true;
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
NoticeMsg?.Invoke(Client, $"接收消息出错了{e}!\r\n");
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
var state = (StateObject)ar.AsyncState;
Socket _client = state.workSocket;
//因为到这边的经常Connected 还是true
//if (!_client.Connected)
//{
// _client.Close();
// return;
//}
SocketError error = SocketError.Success;
int bytesRead = _client.EndReceive(ar,out error);
if (error == SocketError.ConnectionReset)
{
NoticeMsg?.Invoke(Client, $"链接已经释放!\r\n");
_client.Close();
_client.Dispose();
return;
}
if (SocketError.Success!= error)
{
throw new SocketException((int)error);
}
var arr = state.buffer.AsQueryable().Take(bytesRead).ToArray();
state.buffers.AddRange(arr); if (bytesRead >= state.buffer.Length)
{
_client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
////state.buffers.CopyTo(state.buffers.Count, state.buffer, 0, bytesRead);
//_client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
// new AsyncCallback(ReceiveCallback), state);
}
else
{
var _msg = Encoding.UTF8.GetString(state.buffers.ToArray());
ReceivedMsg?.Invoke(_client, _msg);
if (state.IsServerCon)
{
_client.Shutdown(SocketShutdown.Both);
_client.Close();
int retryCon = _msg.Contains("_1") ? 1 : 100;
_msg = _msg.Replace("_1", "").Replace("_2", "");
TryConnection(_msg.Split(':')[0], int.Parse(_msg.Split(':')[1]), retryCon);
return;
}
state = new StateObject();
state.IsServerCon = false;
state.workSocket = _client;
_client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state); }
}
catch (SocketException ex)
{
//10054
NoticeMsg?.Invoke(Client, $"链接已经释放!{ex}!\r\n"); }
catch (Exception e)
{
NoticeMsg?.Invoke(Client, $"接收消息出错了2{e}!\r\n");
}
}
/// <summary>
/// 打洞或者尝试链接
/// </summary>
private void TryConnection(string remoteHostname, int remotePort,int retryCon)
{
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
var _iPRemotePoint = new IPEndPoint(IPAddress.Parse(remoteHostname), remotePort);
Client.Bind(LocalEP);
System.Threading.Thread.Sleep(retryCon==1?1:3*1000);
for (int i = 0; i < retryCon; i++)
{
try
{
Client.Connect(_iPRemotePoint);
NoticeMsg?.Invoke(Client, $"已经连接上:{remoteHostname}:{remotePort}!\r\n");
StateObject state = new StateObject();
state.workSocket = Client;
state.IsServerCon = false;
Client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
return;
}
catch
{
NoticeMsg?.Invoke(Client, $"尝试第{i+1}次链接:{remoteHostname}:{remotePort}!\r\n");
}
}
if (retryCon==1)
{
Listening(LocalEP.Port);
return;
}
NoticeMsg?.Invoke(Client, $"尝试了{retryCon}次都没有办法连接到:{remoteHostname}:{remotePort},凉了!\r\n"); }
/// <summary>
/// 如果连接不成功,因为事先有打洞过了,根据条件监听 等待对方连接来
/// </summary>
private void Listening(int Port)
{
try
{
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
Client.Bind(new IPEndPoint(IPAddress.Any, Port));
Client.Listen((int)SocketOptionName.MaxConnections);
NoticeMsg?.Invoke(Client, $"开始侦听断开等待链接过来!\r\n");
StateObject state = new StateObject();
state.IsServerCon = false;
var _socket = Client.Accept();//只有一个链接 不用BeginAccept
Client.Close();//关系现有侦听
Client = _socket;
state.workSocket = Client;
NoticeMsg?.Invoke(Client, $"接收到来自{Client.RemoteEndPoint}的连接!\r\n");
Client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (Exception ex)
{ NoticeMsg?.Invoke(Client, $"监听出错了{ex}凉了!\r\n");
}
//scoket.send }
/// <summary>
/// 本例子只存在一个成功的链接,对成功的连接发送消息!
/// </summary>
/// <param name="strMsg"></param>
public void Send(string strMsg)
{
byte[] bytes = Encoding.UTF8.GetBytes(strMsg);
Client.BeginSend(bytes, 0, bytes.Length, 0,
new AsyncCallback(SendCallback), Client);
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket _socket = (Socket)ar.AsyncState;
//if(ar.IsCompleted)
_socket.EndSend(ar);
}
catch (Exception e)
{
NoticeMsg?.Invoke(Client, $"发送消息出错了{e}!\r\n");
}
}
}

完整代码:

https://gitee.com/qqljcn/zsg_-peer-to-peer

二、面向过程方式:

Task+(TcpClient+TcpListener )|(UdpClient)实现 tcp|udp的打洞!这个就不贴代码了直接放码云连接

https://gitee.com/qqljcn/zsg_-peer-to-peer_-lite

三、说明:

1.本人是个老菜鸟代码仅供参考,都是挺久以前写的也没有经过严格的测试仅能演示这个例子,有不成熟的地方,烦请各位大神海涵指教;

2.不要都用本机试这个例子,本机不走nat

3.然后udp因为是无连接的所以打孔成功后不要等太久再发消息,nat缓存一过就失效了!

4.确定自己不是对称型nat的话,如果打洞不成功,那就多试几次!

5 .我这个例子代码名字叫 PeerToPeer 但不是真的p2p, 微软提供了p2p的实现 在using System.Net.PeerToPeer命名空间下。

以上是通过nat的方式,另外还有一种方式是,通过一个有外网ip的第三方服务器转发像 花生壳、nat123这类软件,也有做个小程序,并且自己在用以后演示;

 
 
 
 
 
 

基于C#的内网穿透学习笔记(附源码)的更多相关文章

  1. 基于frp的内网穿透实例4-为本地的web服务实现HTTPS访问

    原文地址:https://wuter.cn/1932.html/ 一.想要实现的功能 目前已经实现将本地的web服务暴露到公网,现想要实现https访问.(前提:已经有相应的证书文件,如果没有就去申请 ...

  2. [转]OpenTK学习笔记(1)-源码、官网地址

    OpenTK源码下载地址:https://github.com/opentk/opentk OpenTK使用Nuget安装命令:OpenTK:Install-Package OpenTK -Versi ...

  3. Hadoop学习笔记(9) ——源码初窥

    Hadoop学习笔记(9) ——源码初窥 之前我们把Hadoop算是入了门,下载的源码,写了HelloWorld,简要分析了其编程要点,然后也编了个较复杂的示例.接下来其实就有两条路可走了,一条是继续 ...

  4. frp内网穿透学习

    前言 因为自己在内网,但是目标站在外网,这时候可以通过内网穿透工具,将接收到的请求转发到内网,实现在内网的msf可以控制外网的靶机. 也看了一些Ngrok,花生壳的,发现Ngrok.cc这个看文章说有 ...

  5. 基于frp的内网穿透实例1-通过SSH访问内网机器

    原文地址:https://wuter.cn/1804.html/ 老母鸡终于到了,作为一个能运行linux系统的四核1G硬件,它还是比较小巧的. FRP 全名:Fast Reverse Proxy.F ...

  6. OpenTK学习笔记(1)-源码、官网地址

    OpenTK源码下载地址:https://github.com/opentk/opentk OpenTK使用Nuget安装命令:OpenTK:Install-Package OpenTK -Versi ...

  7. Linux简易APR内存池学习笔记(带源码和实例)

    先给个内存池的实现代码,里面带有个应用小例子和画的流程图,方便了解运行原理,代码 GCC 编译可用.可以自己上网下APR源码,参考代码下载链接: http://pan.baidu.com/s/1hq6 ...

  8. Nginx学习笔记4 源码分析

    Nginx学习笔记(四) 源码分析 源码分析 在茫茫的源码中,看到了几个好像挺熟悉的名字(socket/UDP/shmem).那就来看看这个文件吧!从简单的开始~~~ src/os/unix/Ngx_ ...

  9. MarkDown语法 学习笔记 效果源码对照

    MarkDown基本语法学习笔记 Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式. 下面将对Markdown的基本使用做一个介绍 目 ...

随机推荐

  1. Java数据结构——二叉树节点的增删改查、获取深度及最大最小值

    一.查找最大值 // 查找最大值 public static Node maxNode() { Node node = root; Node maxNode = node; while (node ! ...

  2. 【C艹】关于sort用法之重构cmp(comp)函数的笔记

    众所周知,balabalabalabala············. 所以掌握sort函数(库文件:<algorithm>)的用法还是很有必要的. 一般选手只会简单地用用sort排一排数组 ...

  3. Docker 镜像构建之 docker commit

    我们可以通过公共仓库拉取镜像使用,但是,有些时候公共仓库拉取的镜像并不符合我们的需求.尽管已经从繁琐的部署工作中解放出来,但是实际开发时,我们可能希望镜像包含整个项目的完整环境,在其他机器上拉取打包完 ...

  4. jieba分词-强大的Python 中文分词库

    1. jieba的江湖地位 NLP(自然语言)领域现在可谓是群雄纷争,各种开源组件层出不穷,其中一支不可忽视的力量便是jieba分词,号称要做最好的 Python 中文分词组件. 很多人学习pytho ...

  5. Almost All Divisors(求因子个数及思维)

    ---恢复内容开始--- We guessed some integer number xx. You are given a list of almost all its divisors. Alm ...

  6. 深入了解Redis【一】源码下载与参考资料准备

    引言 一直在使用redis,但是却没有系统的了解过它的底层实现,准备边学习边记录,深入了解redis. 打算分析以下几个方面: redis的基本类型及底层原理与java对比,每种数据类型的使用场景 r ...

  7. 剑指offer 07 & LeetCode 105 重建二叉树

    题目 题目链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/ 初步题解 先放代码: /** * Definition for ...

  8. .NET实现可交互的WINDOWS服务(转载自CSDN"烈火蜓蜻")

    Windows 服务应用程序在不同于登录用户的交互区域的窗口区域中运行.窗口区域是包含剪贴板.一组全局原子和一组桌面对象的安全对象.由于 Windows 服务的区域不是交互区域,因此 Windows ...

  9. SpringBoot简单(登录/显示/登出)工程下载 使用Thymeleaf输出页面文字

    下载地址:https://files.cnblogs.com/files/xiandedanteng/SessionShare20191226.zip 测试用,画面如下: SpringMVC入门弟子也 ...

  10. IDAPython 安装和设置(windows+linux)

    安装步骤: 我采用的是IDA 6.8 windows安装: 机器上安装了Python,到Python的官网—http://www.python.org/getit/下载2.7的安装包.注意对应操作系统 ...