思路如下(参照源代码):

  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

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

客户端核心代码:

 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

相关文章: