C# p2p UDP穿越NAT,UDP打洞源碼


思路如下(參照源代碼):

  1、 frmServer啟動兩個網絡偵聽,主連接偵聽,協助打洞的偵聽。

  2、 frmClientA和frmClientB分別與frmServer的主連接保持聯系。

  3、 當frmClientA需要和frmClientB建立直接的udp連接時,首先連接frmServer的協助打洞端口,並發送協助連接申請,同時在該端口號上啟動偵聽。

     4、  frmServer的協助打洞連接收到frmClientA的申請后通過主連接通知frmClientB,並將frmClientA經過NAT-A轉換后的公網IP地址和端口等信息告訴frmClientB。

  5、 frmClientB收到frmServer的連接通知后首先與frmServer的協助打洞端口連接,發送一些數據后立即斷開,目的是讓frmServer能知道frmClientB經過NAT-B轉換后的公網IP和端口號。

  6、 frmClientB嘗試與frmClientA的經過NAT-A轉換后的公網IP地址和端口進行connect,不同的路由器會有不同的結果,多數路由器對未知不請自到的SYN請求包直接丟棄而導致connect失敗,但NAT-A會紀錄此次連接的源地址和端口號,為接下來真正的連接做好了准備,這就是所謂的打洞,即frmClientB向frmClientA打了一個洞,下次frmClientA就能直接連接到frmClientB剛才使用的端口號了。

  7、 客戶端frmClientB打洞的同時在相同的端口上啟動偵聽。frmClientB在一切准備就緒以后通過與frmServer的主連接回復消息“可以了,已經准備”,frmServer在收到以后將frmClientB經過NAT-B轉換后的公網IP和端口號告訴給frmClientA。

  8、 frmClientA收到frmServer回復的frmClientB的公網IP和端口號等信息以后,開始連接到frmClientB公網IP和端口號,由於在步驟6中frmClientB曾經嘗試連接過frmClientA的公網IP地址和端口,NAT-A紀錄了此次連接的信息,所以當frmClientA主動連接frmClientB時,NAT-B會認為是合法的SYN數據,並允許通過,從而直接的udp連接建立起來了。

  • frmClientB

客戶端核心代碼:

 1 private void Run()
 2         {
 3             try
 4             {
 5                 byte[] buffer;//接受數據用 
 6                 while (true)
 7                 {
 8                     buffer = _client.Receive(ref _remotePoint);//_remotePoint變量返回當前連接的用戶IP地址 
 9 
10                     object msgObj = ObjectSerializer.Deserialize(buffer);
11                     Type msgType = msgObj.GetType();
12                     DoWriteLog("接收到消息:" + msgType.ToString() + " From:" + _remotePoint.ToString());
13 
14                     if (msgType == typeof(S2C_UserListMessage))
15                     {
16                         // 更新用戶列表 
17                         S2C_UserListMessage usersMsg = (S2C_UserListMessage)msgObj;
18                         _userList.Clear();
19 
20                         foreach (User user in usersMsg.UserList)
21                             _userList.Add(user);
22 
23                         this.DisplayUsers(_userList);
24                     }
25                     else if (msgType == typeof(S2C_UserAction))
26                     {
27                         //用戶動作,新用戶登錄/用戶登出 
28                         S2C_UserAction msgAction = (S2C_UserAction)msgObj;
29                         if (msgAction.Action == UserAction.Login)
30                         {
31                             _userList.Add(msgAction.User);
32                             this.DisplayUsers(_userList);
33                         }
34                         else if (msgAction.Action == UserAction.Logout)
35                         {
36                             User user = _userList.Find(msgAction.User.UserName);
37                             if (user != null) _userList.Remove(user);
38                             this.DisplayUsers(_userList);
39                         }
40                     }
41                     else if (msgType == typeof(S2C_HolePunchingMessage))
42                     {
43                         //接受到服務器的打洞命令 
44                         S2C_HolePunchingMessage msgHolePunching = (S2C_HolePunchingMessage)msgObj;
45 
46                         //NAT-B的用戶給NAT-A的用戶發送消息,此時UDP包肯定會被NAT-A丟棄, 
47                         //因為NAT-A上並沒有A->NAT-B的合法Session, 但是現在NAT-B上就建立了有B->NAT-A的合法session了! 
48                         P2P_HolePunchingTestMessage msgTest = new P2P_HolePunchingTestMessage(_LocalUserName);
49                         this.SendMessage(msgTest, msgHolePunching.RemotePoint);
50                     }
51                     else if (msgType == typeof(P2P_HolePunchingTestMessage))
52                     {
53                         //UDP打洞測試消息 
54                         //_HoleAccepted = true; 
55                         P2P_HolePunchingTestMessage msgTest = (P2P_HolePunchingTestMessage)msgObj;
56                         UpdateConnection(msgTest.UserName, _remotePoint);
57 
58                         //發送確認消息 
59                         P2P_HolePunchingResponse response = new P2P_HolePunchingResponse(_LocalUserName);
60                         this.SendMessage(response, _remotePoint);
61                     }
62                     else if (msgType == typeof(P2P_HolePunchingResponse))
63                     {
64                         //_HoleAccepted = true;//打洞成功 
65                         P2P_HolePunchingResponse msg = msgObj as P2P_HolePunchingResponse;
66                         UpdateConnection(msg.UserName, _remotePoint);
67 
68                     }
69                     else if (msgType == typeof(P2P_TalkMessage))
70                     {
71                         //用戶間對話消息 
72                         P2P_TalkMessage workMsg = (P2P_TalkMessage)msgObj;
73                         DoWriteLog(workMsg.Message);
74                     }
75                     else
76                     {
77                         DoWriteLog("收到未知消息!");
78                     }
79                 }
80             }
81             catch (Exception ex) { DoWriteLog(ex.Message); }
82         }
View Code
  • frmClientA

服務端核心代碼:

 1 private void Run()
 2         {
 3             byte[] msgBuffer = null;
 4 
 5             while (true)
 6             {
 7                 msgBuffer = _server.Receive(ref _remotePoint); //接受消息 
 8                 try
 9                 {
10                     //將消息轉換為對象 
11                     object msgObject = ObjectSerializer.Deserialize(msgBuffer);
12                     if (msgObject == null) continue;
13 
14                     Type msgType = msgObject.GetType();
15                     DoWriteLog("接收到消息:" + msgType.ToString());
16                     DoWriteLog("From:" + _remotePoint.ToString());
17 
18                     //新用戶登錄 
19                     if (msgType == typeof(C2S_LoginMessage))
20                     {
21                         C2S_LoginMessage lginMsg = (C2S_LoginMessage)msgObject;
22                         DoWriteLog(string.Format("用戶’{0}’已登錄!", lginMsg.FromUserName));
23 
24                         // 添加用戶到列表 
25                         IPEndPoint userEndPoint = new IPEndPoint(_remotePoint.Address, _remotePoint.Port);
26                         User user = new User(lginMsg.FromUserName, userEndPoint);
27                         _userList.Add(user);
28 
29                         this.DoUserChanged(_userList);
30 
31                         //通知所有人,有新用戶登錄 
32                         S2C_UserAction msgNewUser = new S2C_UserAction(user, UserAction.Login);
33                         foreach (User u in _userList)
34                         {
35                             if (u.UserName == user.UserName) //如果是自己,發送所有在線用戶列表
36                                 this.SendMessage(new S2C_UserListMessage(_userList), u.NetPoint);
37                             else
38                                 this.SendMessage(msgNewUser, u.NetPoint);
39                         }
40                     }
41                     else if (msgType == typeof(C2S_LogoutMessage))
42                     {
43                         C2S_LogoutMessage lgoutMsg = (C2S_LogoutMessage)msgObject;
44                         DoWriteLog(string.Format("用戶’{0}’已登出!", lgoutMsg.FromUserName));
45 
46                         // 從列表中刪除用戶 
47                         User logoutUser = _userList.Find(lgoutMsg.FromUserName);
48                         if (logoutUser != null) _userList.Remove(logoutUser);
49 
50                         this.DoUserChanged(_userList);
51 
52                         //通知所有人,有用戶登出 
53                         S2C_UserAction msgNewUser = new S2C_UserAction(logoutUser, UserAction.Logout);
54                         foreach (User u in _userList)
55                             this.SendMessage(msgNewUser, u.NetPoint);
56                     }
57                     else if (msgType == typeof(C2S_HolePunchingRequestMessage))
58                     {
59                         //接收到A給B打洞的消息,打洞請求,由客戶端發送給服務器端 
60                         C2S_HolePunchingRequestMessage msgHoleReq = (C2S_HolePunchingRequestMessage)msgObject;
61 
62                         User userA = _userList.Find(msgHoleReq.FromUserName);
63                         User userB = _userList.Find(msgHoleReq.ToUserName);
64 
65                         // 發送打洞(Punching Hole)消息 
66                         DoWriteLog(string.Format("用戶:[{0} IP:{1}]想與[{2} IP:{3}]建立對話通道.",
67                         userA.UserName, userA.NetPoint.ToString(),
68                         userB.UserName, userB.NetPoint.ToString()));
69 
70                         //由Server發送消息給B,將A的IP的IP地址信息告訴B,然后由B發送一個測試消息給A. 
71                         S2C_HolePunchingMessage msgHolePunching = new S2C_HolePunchingMessage(_remotePoint);
72                         this.SendMessage(msgHolePunching, userB.NetPoint); //Server->B 
73                     }
74                     else if (msgType == typeof(C2S_GetUsersMessage))
75                     {
76                         // 發送當前用戶信息 
77                         S2C_UserListMessage srvResMsg = new S2C_UserListMessage(_userList);
78                         this.SendMessage(srvResMsg, _remotePoint);
79                     }
80                 }
81                 catch (Exception ex) { DoWriteLog(ex.Message); }
82             }
83         }
View Code
  • frmServer 

如轉載請注明本文來自易學網http://www.vjsdn.com/

 


免責聲明!

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



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