C# 實現UDP打洞通信(一)


最近研究了一下網絡打洞的相關技術,TCP的方式據說可行性不高,各種困難,因此決定采用UDP(UDP是什么就不解釋了)的方式。

原理:

  我們都知道局域網內的主機想要訪問外網的服務器是比較容易的,比如瀏覽器輸入www.baidu.com就可以訪問到百度的服務器,但是如果在局域網的主機部署一個服務,讓外網的機器進行訪問一般是無法訪問的,因為外部訪問的請求會被路由器給阻礙掉了,這是為什么呢?

  比如我內網的主機IP是192.168.1.128,我訪問外網的服務器的時候系統會自動給我的訪問分配端口(也可以自定義端口),我對外的訪問請求會經過路由器,路由器又會分配一個對外的端口,如果還有外部網絡路由器,那么每一層都會分配一個獨立的對外端口,一直到最終處於公網的路由器通過公網的IP及分配的端口對外部的服務器發起訪問請求,服務器收到請求的同時會得到我處於公網的路由器的IP及分配的端口,然后將請求的反饋結果發送給我,反饋的信息會發到我的公網IP及端口,然后路由器內部再逐層向內發送給對應的IP和端口最終到達發起請求的應用程序。

  如內網主機(192.168.1.128:12345)訪問外網服務器(111.110.213.99:15000),那么實際的請求過程是這樣的:內網主機(192.168.1.128:12345)發起請求,請求通過路由器A(假設只有一個路由器),路由器為內網的(192.168.1.128:12345)綁定一個動態的端口(18876)並通過路由器的外網IP(120.145.15.87:18876)訪問外網服務器(111.110.213.99:15000),服務器收到請求后發送反饋數據給路由器(120.145.15.87:18876),路由器再根據記錄的列表中18876端口綁定的內網地址,將信息轉發給內網主機(192.168.1.128:12345),於是主機就收到外部的信息了。

  注意,如果內網沒有向外部訪問,那么路由器就沒有分配(18876)這個端口,那么外部發來的數據會被路由器丟棄掉,我們通過先連接服務器,服務器收到的(18876)並使用該端口或將該端口發送給其它客戶端使用,那么這個端口其實就是我們打的一個(洞)。

  由於一些原因沒能用多台內網機器進行試驗,只是簡單的通過內網主機和外網的服務器進行的試驗,下面貼上代碼:

  1 using System;
  2 using System.Net;
  3 using System.Net.Sockets;
  4 using System.Text;
  5 using System.Threading;
  6 
  7 namespace P2MP
  8 {
  9     class MainClass
 10     {
 11         /// <summary>
 12         /// 用於UDP發送的網絡服務類
 13         /// </summary>
 14         private static UdpClient udpcSend = null;
 15 
 16         static IPEndPoint localIpep = null; 
 17 
 18         public static void Main(string[] args)
 19         {
 20             Console.Write("IP:");
 21             string ip = Console.ReadLine();
 22             Console.Write("Port:");
 23             int port = int.Parse(Console.ReadLine());
 24             localIpep = new IPEndPoint(IPAddress.Parse(ip), port); // 本機IP,指定的端口號
 25 
 26             udpcSend = new UdpClient(localIpep);
 27 
 28             StartReceive();
 29 
 30             // 實名發送
 31             string msg = null;
 32             while ((msg = Console.ReadLine()) != null)
 33             {
 34                 if ("stop" == msg)
 35                 {
 36                     StopReceive();
 37                     udpcSend.Close();
 38                 }
 39                 else
 40                 {
 41                     //string[] arr = Console.ReadLine().Split(' ');
 42                     Thread thrSend = new Thread(SendMessage);
 43                     thrSend.Start(msg);
 44                 }
 45             }
 46             Console.ReadKey();
 47         }
 48 
 49         /// <summary>
 50         /// 發送信息
 51         /// </summary>
 52         /// <param name="obj"></param>
 53         private static void SendMessage(object obj)
 54         {
 55             try
 56             {
 57                 string message = obj.ToString();
 58                 string[] array = message.Split(' ');
 59                 IPAddress iPAddress = IPAddress.Parse(array[0]);
 60                 int port = int.Parse(array[1]);
 61                 byte[] sendbytes = Encoding.Unicode.GetBytes(array[2]);
 62                 IPEndPoint remoteIpep = new IPEndPoint(iPAddress, port); // 發送到的IP地址和端口號
 63                 udpcSend.Send(sendbytes, sendbytes.Length, remoteIpep);
 64             }
 65             catch{}
 66         }
 67 
 68         /// <summary>
 69         /// 開關:在監聽UDP報文階段為true,否則為false
 70         /// </summary>
 71         static bool IsUdpcRecvStart = false;
 72         /// <summary>
 73         /// 線程:不斷監聽UDP報文
 74         /// </summary>
 75         static Thread thrRecv;
 76 
 77         private static void StartReceive()
 78         {
 79             if (!IsUdpcRecvStart) // 未監聽的情況,開始監聽
 80             {
 83                 thrRecv = new Thread(ReceiveMessage);
 84                 thrRecv.Start();
 85                 IsUdpcRecvStart = true;
 86                 Console.WriteLine("UDP監聽器已成功啟動");
 87             }
 88         }
 89 
 90         private static void StopReceive()
 91         {
 92             if (IsUdpcRecvStart)
 93             {
 94                 thrRecv.Abort(); // 必須先關閉這個線程,否則會異常
 96                 IsUdpcRecvStart = false;
 97                 Console.WriteLine("UDP監聽器已成功關閉");
 98             }
 99         }
100 
101         /// <summary>
102         /// 接收數據
103         /// </summary>
104         /// <param name="obj"></param>
105         private static void ReceiveMessage(object obj)
106         {
108             while (IsUdpcRecvStart)
109             {
110                 try
111                 {
112                     byte[] bytRecv = udpcSend.Receive(ref localIpep);
113                     string message = Encoding.Unicode.GetString(bytRecv, 0, bytRecv.Length);
114                     Console.WriteLine(string.Format("{0}[{1}]", localIpep, message));
115                 }
116                 catch (Exception ex)
117                 {
118                     Console.WriteLine(ex.Message);
119                     break;
120                 }
121             }
122         }
123     }
124 }

  可以同時在多個內網主機運行,並且保證其中有一個實在外網的服務器上運行,啟動后輸入本機的IP和使用的端口,當所有機器都顯示“UDP監聽器已成功啟動”后,分別使用內網程序向 服務器IP地址[空格]端口號[空格]消息內容如:"188.90.9.145 12345 hello你好",發送消息給服務器,服務器收到的消息上附帶客戶端發來的對外端口,這時候就知道各個客戶端的對外IP和端口了,各個主機想要給另一台主機發消息只要從服務器上看其它客戶端的IP和端口,並通過“IP 端口 消息”的格式發送消息,網絡良好不丟包的情況下就能發送進去了。

  由於路由器會定時銷毀記錄的列表,因此還需要保持客戶端跟服務器之間的心跳,比如每10秒發送一個消息,服務器端將各個客戶端最新的列表保存下來。

  暫時先貼出簡單的代碼,后續打算開發一個P2P文件服務,代碼逐步完善中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM