狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C#的內(nèi)網(wǎng)穿透學習(附源碼)

admin
2024年4月9日 23:33 本文熱度 770

如何讓兩臺處在不同內(nèi)網(wǎng)的主機直接互連?你需要內(nèi)網(wǎng)穿透!



上圖是一個非完整版內(nèi)外網(wǎng)通訊圖由內(nèi)網(wǎng)端先發(fā)起,內(nèi)網(wǎng)設備192.168.1.2:6677發(fā)送數(shù)據(jù)到外網(wǎng)時候必須經(jīng)過nat會轉(zhuǎn)換成對應的外網(wǎng)ip+端口,然后在發(fā)送給外網(wǎng)設備,外網(wǎng)設備回復數(shù)據(jù)也是發(fā)給你的外網(wǎng)ip+端口。


這只是單向的內(nèi)去外,那反過來,如果外網(wǎng)的設備需要主動訪問我局域網(wǎng)里的某一個設備是無法訪問的,因為這個時候還沒做nat轉(zhuǎn)換所以外網(wǎng)不知道你內(nèi)網(wǎng)設備的應用具體對應的是哪個端口,這個時候我們就需要內(nèi)網(wǎng)穿透了,內(nèi)網(wǎng)穿透也叫NAT穿透;


穿透原理


如上圖所示經(jīng)NAT轉(zhuǎn)換后的內(nèi)外網(wǎng)地址+端口,會緩存一段時間,在這段時間內(nèi)192.168.1.2:6677和112.48.69.2020:8899的映射關系會一直存在,這樣你的內(nèi)網(wǎng)主機就得到一個外網(wǎng)地址,這個對應關系又根據(jù)NAT轉(zhuǎn)換方法類型的不同,得用對應的方式實現(xiàn)打洞,NAT轉(zhuǎn)換方法類型有下列幾種(來源百度百科NAT):


(1)Full cone NAT:即著名的一對一(one-to-one)NAT。


一旦一個內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。任意外部主機都能通過給eAddr:port2發(fā)包到iAddr:port1(純天然不用打洞?。?/span>


(2)Address-Restricted cone NAT :限制地址,即只接收曾經(jīng)發(fā)送到對端的IP地址來的數(shù)據(jù)包。


一旦一個內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。


任意外部主機(hostAddr:any)都能通過給eAddr:port2發(fā)包到達iAddr:port1的前提是:iAddr:port1之前發(fā)送過包到hostAddr:any. "any"也就是說端口不受限制(只需知道某個轉(zhuǎn)換后的外網(wǎng)ip+端口即可。)


3)Port-Restricted cone NAT:類似受限制錐形NAT(Restricted cone NAT),但是還有端口限制。


一旦一個內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。一個外部主機(hostAddr:port3)能夠發(fā)包到達iAddr:port1的前提是:iAddr:port1之前發(fā)送過包到hostAddr:port3.(雙方需要各自知道對方轉(zhuǎn)換后的外網(wǎng)ip+端口,然后一方先發(fā)一次嘗試連接,另一方在次連接過來的時候就能直接連通了。)


(4)Symmetric NAT(對稱NAT)


每一個來自相同內(nèi)部IP與port的請求到一個特定目的地的IP地址和端口,映射到一個獨特的外部來源的IP地址和端口。


同一個內(nèi)部主機發(fā)出一個信息包到不同的目的端,不同的映射使用外部主機收到了一封包從一個內(nèi)部主機可以送一封包回來(只能和Full cone NAT連,沒法打洞,手機流量開熱點就是,同一個本地端口連接不同的服務器得到的外網(wǎng)第地址和IP不同?。?/span>


例子:

 

下面用一個例子演示下“受限制錐形NAT”的打洞,實現(xiàn)了這個它前面兩個類型也能通用。對稱型的話不考慮,打不了洞。


我們知道要實現(xiàn)兩臺“受限制錐形NAT”互連重點就是要知道對方轉(zhuǎn)換后的外網(wǎng)IP+端口,這樣我們可以:

 

1、準備一臺Full cone NAT 類型的外網(wǎng)服務端,接受來自兩個客戶端的連接,并對應告知對方ip+端口;

 

2、知道了對方ip+端口 需要設置socke:Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);這樣才能端口復用;目的就是讓連接對外的端口一致;

 

3、最后,我們可以讓兩臺客戶端互相連接,或者一臺先發(fā)一個請求,打個洞;另一個在去連接;

 

代碼:


1、TCP+IOCP方式,相對 “面向?qū)ο蟆钡貙崿F(xiàn)穿透!


服務端 ServerListener類,用SocketAsyncEventArgs:


/// <summary>
/// 打洞服務端,非常的簡單,接收兩個連接并且轉(zhuǎn)發(fā)給對方;
/// </summary>
public class ServerListener : IServerListener
{
   IPEndPoint EndPoint { get; set; }
   //消息委托
   public delegate void EventMsg(object sender, string e);
   public static object obj = new object();
   //通知消息
   public event EventMsg NoticeMsg;
   //接收事件
   public event EventMsg ReceivedMsg;
   /// <summary>
   /// 上次鏈接的
   /// </summary>
   private Socket Previous;
   public ServerListener(IPEndPoint endpoint)
   
{
       this.EndPoint = endpoint;
   }
   private Socket listener;
   public void Start()
   
{
       this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
       var connectArgs = new SocketAsyncEventArgs();            
       listener.Bind(EndPoint);
       listener.Listen(2);
       EndPoint = (IPEndPoint)listener.LocalEndPoint;
       connectArgs.Completed += OnAccept;
       //是否同步就完成了,同步完成需要自己觸發(fā)
       if (!listener.AcceptAsync(connectArgs))
           OnAccept(listener, connectArgs);
   }
   byte[] bytes = new byte[400];
   private void OnAccept(object sender, SocketAsyncEventArgs e)
   
{
       Socket socket = null;
       try
       {
           var remoteEndPoint1 = e.AcceptSocket.RemoteEndPoint.ToString();
           NoticeMsg?.Invoke(sender, $"客戶端:{remoteEndPoint1}連接上我了!\r\n");
           SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs();
           readEventArgs.Completed += OnSocketReceived;
           readEventArgs.UserToken = e.AcceptSocket;
           readEventArgs.SetBuffer(bytes, 0, 400);
           if (!e.AcceptSocket.ReceiveAsync(readEventArgs))
               OnSocketReceived(e.AcceptSocket, readEventArgs);
           lock (obj)
           {
               socket = e.AcceptSocket;
               //上次有鏈接并且鏈接還”健在“
               if (Previous == null||! Previous.Connected)
               {
                   Previous = e.AcceptSocket;
               }
               else
               {
                   //Previous.SendAsync()..?
                   Previous.Send(Encoding.UTF8.GetBytes(remoteEndPoint1 + "_1"));
                   socket.Send(Encoding.UTF8.GetBytes(Previous.RemoteEndPoint.ToString() + "_2"));
                   NoticeMsg?.Invoke(sender, $"已經(jīng)通知雙方!\r\n");
                   Previous = null;
               }
           }
           e.AcceptSocket = null;
           if (e.SocketError != SocketError.Success)
               throw new SocketException((int)e.SocketError);
         
           if(!listener.AcceptAsync(e))
               OnAccept(listener, e);
       }
       catch
       {
           socket?.Close();
      }
   }
   public void Close()
   
{
       using (listener)
       {
          // listener.Shutdown(SocketShutdown.Both);
           listener.Close();
       }
       //throw new NotImplementedException();
   }
   /// <summary>
   /// 此處留有一個小BUG,接收的字符串大于400的時候會有問題;可以參考客戶端修改
   /// </summary>
   public void OnSocketReceived(object sender, SocketAsyncEventArgs e)
   
{          
       Socket socket = e.UserToken as Socket;            
       var remoteEndPoint = socket.RemoteEndPoint.ToString();
       try
       {
           if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)

           {
               ReceivedMsg?.Invoke(sender, $"收到:{remoteEndPoint}發(fā)來信息:{Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred)}\r\n");                

           }
           else
           {
               socket?.Close();
               NoticeMsg?.Invoke(sender, $"鏈接:{remoteEndPoint}釋放啦!\r\n");
               return;
           }
           if (!socket.ReceiveAsync(e))
               OnSocketReceived(socket, e);
       }
       catch
       {
           socket?.Close();
       }
       //{
       //   if (!((Socket)sender).AcceptAsync(e))
       //        OnSocketReceived(sender, e);
       //}
       //catch
       //{
       //    return;
       //}
   }
}


2、客戶端類 PeerClient用BeginReceive和EndReceive實現(xiàn)異步;


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>
/// 打洞節(jié)點客戶端 實現(xiàn)的功能:
/// 連接服務器獲取對方節(jié)點ip
/// 請求對方ip(打洞)
/// 根據(jù)條件判斷是監(jiān)聽連接還是監(jiān)聽等待連接
/// </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
   //本地綁定的節(jié)點
   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, $"連接服務器失?。?span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(224, 108, 117);">{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;
           //因為到這邊的經(jīng)常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, $"鏈接已經(jīng)釋放!\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, $"鏈接已經(jīng)釋放!{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, $"已經(jīng)連接上:{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>
   /// 如果連接不成功,因為事先有打洞過了,根據(jù)條件監(jiān)聽 等待對方連接來
   /// </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();//關系現(xiàn)有偵聽
           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, $"監(jiān)聽出錯了{ex}涼了!\r\n");
       }
       //scoket.send
   }
   /// <summary>
   /// 本例子只存在一個成功的鏈接,對成功的連接發(fā)送消息!
   /// </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, $"發(fā)送消息出錯了{e}!\r\n");
       }
   }
}


完整代碼:https://gitee.com/qqljcn/zsg_-peer-to-peer


二、面向過程方式


Task+(TcpClient+TcpListener )|(UdpClient)實現(xiàn) tcp|udp的打洞!這個就不貼代碼了直接放碼云連接:


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


三、說明


1、代碼僅供參考,都是挺久以前寫的也沒有經(jīng)過嚴格的測試僅能演示這個例子,有不成熟的地方,煩請各位大神海涵指教;


2、不要都用本機試這個例子,本機不走nat;


3、然后udp因為是無連接的所以打孔成功后不要等太久再發(fā)消息,nat緩存一過就失效了!


4、確定自己不是對稱型nat的話,如果打洞不成功,那就多試幾次!


5 、我這個例子代碼名字叫 PeerToPeer 但不是真的p2p, 微軟提供了p2p的實現(xiàn) 在using System.Net.PeerToPeer命名空間下。


以上是通過nat的方式,另外還有一種方式是,通過一個有外網(wǎng)ip的第三方服務器轉(zhuǎn)發(fā),像花生殼、nat123這類軟件,也有做個小程序,并且自己在用以后演示。


- EOF -


該文章在 2024/4/9 23:34:52 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務費用、相關報表等業(yè)務管理,結(jié)合碼頭的業(yè)務特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved