<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 14201字,需瀏覽 29分鐘

           ·

          2020-10-18 02:03


          轉(zhuǎn)自:~小菜鳥
          cnblogs.com/qqljcn/p/13738595.html

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



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


          這只是單向的內(nèi)去外,那反過來,如果外網(wǎng)的設(shè)備需要主動訪問我局域網(wǎng)里的某一個設(shè)備是無法訪問的,因為這個時候還沒做nat轉(zhuǎn)換所以外網(wǎng)不知道你內(nèi)網(wǎng)設(shè)備的應(yīng)用具體對應(yī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的映射關(guān)系會一直存在,這樣你的內(nèi)網(wǎng)主機就得到一個外網(wǎng)地址,這個對應(yīng)關(guān)系又根據(jù)NAT轉(zhuǎn)換方法類型的不同,得用對應(yīng)的方式實現(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(純天然不用打洞!)


          (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ù)器得到的外網(wǎng)第地址和IP不同?。?/span>


          例子:

          ?

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


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

          ?

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

          ?

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

          ?

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

          ?

          代碼:


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


          服務(wù)端 ServerListener類,用SocketAsyncEventArgs:


          /// 
          /// 打洞服務(wù)端,非常的簡單,接收兩個連接并且轉(zhuǎn)發(fā)給對方;
          ///

          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;
          ///
          /// 上次鏈接的
          ///

          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();
          }
          ///
          /// 此處留有一個小BUG,接收的字符串大于400的時候會有問題;可以參考客戶端修改
          ///

          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>();
          //是不是和服務(wù)器的鏈接
          public bool IsServerCon = false;
          }
          ///
          /// 打洞節(jié)點客戶端 實現(xiàn)的功能:
          /// 連接服務(wù)器獲取對方節(jié)點ip
          /// 請求對方ip(打洞)
          /// 根據(jù)條件判斷是監(jiān)聽連接還是監(jiān)聽等待連接
          ///

          public class PeerClient : IPeerClient
          {
          //ManualResetEvent xxxxDone = new ManualResetEvent(false);
          //Semaphore
          ///
          /// 當前鏈接
          ///

          public Socket Client { get;private set; }
          #region 服務(wù)端
          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;
          }

          ///
          /// 初始化客戶端(包括啟動)
          ///

          public void Init()
          {
          try
          {
          Client.Connect(ServerHostName, ServerPort);
          }
          catch (SocketException ex)
          {
          NoticeMsg?.Invoke(Client, $"連接服務(wù)器失??!{ex}!\r\n");
          throw;
          }
          catch (Exception ex)
          {
          NoticeMsg?.Invoke(Client, $"連接服務(wù)器失?。?span style="color: rgb(224, 108, 117);font-weight: 400;font-style: normal;">{ex}!\r\n");
          throw;
          }
          NoticeMsg?.Invoke(Client, $"連接上服務(wù)器了!\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");
          }
          }
          ///
          /// 打洞或者嘗試鏈接
          ///

          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"); }

          ///
          /// 如果連接不成功,因為事先有打洞過了,根據(jù)條件監(jiān)聽 等待對方連接來
          ///

          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();//關(guān)系現(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
          }
          ///
          /// 本例子只存在一個成功的鏈接,對成功的連接發(fā)送消息!
          ///

          ///
          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的第三方服務(wù)器轉(zhuǎn)發(fā)像 花生殼、nat123這類軟件,也有做個小程序,并且自己在用以后演示;

          回復(fù)?【關(guān)閉】關(guān)
          回復(fù)?【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復(fù)?【被刪】
          回復(fù)?【訪客】
          回復(fù)?【小程序】學獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
          回復(fù)?【python】學微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù)?【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復(fù)?【加群】加入dotnet微信交流群

          強烈推薦:超全C#幫助類,提升效率就靠它


          那個挖礦從入門到坐牢的程序員,后來怎么了


          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97国产超碰在线观看 | 啦啦啦www日本高清免费观看 | 日韩黄色视频在线观看 | 在线内射毛片 | 色婷婷视频在线播放 |