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

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

C#的內網穿透學習(附源碼)非常詳細,零基礎入門到精通

admin
2024年6月2日 16:44 本文熱度 1074

 如何讓兩臺處在不同內網的主機直接互連?你需要內網穿透

上圖是一個非完整版內外網通訊圖由內網端先發起,內網設備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:

/// <summary>  

/// 打洞服務端,非常的簡單,接收兩個連接并且轉發給對方;  

/// </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;  

        //是否同步就完成了,同步完成需要自己觸發  

        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, $"已經通知雙方!\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}發來信息:{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實現異步;

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

附件:zsg_-peer-to-peer-master.zip

二、面向過程方式

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

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

附件:zsg_-peer-to-peer_-lite-master.zip

三、說明

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

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

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

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

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

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

- EOF -


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