C#实现UDP穿越NAT

英文文献: Peer-to-Peer Communication Across Network Address Translators(I)
中文翻译: UDP/TCP穿越NATP2P通信方法研究(UDP/TCP打洞 Hole Punching)

C++
实现的源代码:(c++)TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞
本论坛搜集:         关于C#TcpListener监听WAN IP实现QQ序收集资料
一个简单说明:      UDP打洞(UDP Hole Punching)原理



C#实现UDP穿越NAT程序运行效果图


(
图一)运行在公网上的服务器程序,用于转发打洞消息.


(
图二)运行在公网上的测试客户端程序A


(
图三)运行在NAT网络上的测试客户端程序B



(
图四) UDP打洞过程状态图



***
阅读下面代码前请先了解UDP穿越NAT原理***


1.
服务器主窗体源代码

public partial class frmServer : Form
{
   
private Server _server;
   
   
public frmServer()
   {
      InitializeComponent();
   }
   
   
private void button1_Click(object sender, EventArgs e)
   {
      _server =
new Server();
      _server.OnWriteLog +=
new WriteLogHandle(server_OnWriteLog);
      _server.OnUserChanged +=
new UserChangedHandle(OnUserChanged);
      
try
      {
         _server.Start();
      }
      
catch (Exception ex)
      {
         MessageBox.Show(ex.Message);
      }
   }
   
   
//刷新用户列表
   
private void OnUserChanged(UserCollection users)
   {
      listBox2.DisplayMember = "FullName";
      listBox2.DataSource =
null;
      listBox2.DataSource = users;
   }
   
   
//显示跟踪消息
   
public void server_OnWriteLog(string msg)
   {
      listBox1.Items.Add(msg);
      listBox1.SelectedIndex = listBox1.Items.Count - 1;
   }
   
   
private void button2_Click(object sender, EventArgs e)
   {
      Application.Exit();
   }
   
   
private void frmServer_FormClosing(object sender, FormClosingEventArgs e)
   {
      
if (_server != null)
      _server.Stop();
   }
   
   
private void button3_Click(object sender, EventArgs e)
   {
      
//发送消息给所有在线用户
      P2P_TalkMessage msg =
new P2P_TalkMessage(textBox1.Text);
      
foreach (object o in listBox2.Items)
      {
         User user = o
as User;
         _server.SendMessage(msg, user.NetPoint);
      }
   }
   
   
private void button6_Click(object sender, EventArgs e)
   {
      listBox1.Items.Clear();
   }
}
如转载请注明本文来自易学网http://www.vjsdn.com/


2.
服务器业务类

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using vjsdn.net.library;
using System.Windows.Forms;

namespace vjsdn.net.library
{
   
///


   
/// 服务器端业务类
   
///

   
public class Server
   {
      
private UdpClient _server; //服务器端消息监听器
      
private UserCollection _userList; //在线用户列表
      
private Thread _serverThread;
      
private IPEndPoint _remotePoint; //远程用户请求的IP地址及端口
      
      
private WriteLogHandle _WriteLogHandle = null;
      
private UserChangedHandle _UserChangedHandle = null;
      
      
///
      
/// 显示跟踪消息
      
///

      
public WriteLogHandle OnWriteLog
      {
         
get { return _WriteLogHandle; }
         
set { _WriteLogHandle = value; }
      }
      
      
///
      
/// 当用户登入/登出时触发此事件
      
///

      
public UserChangedHandle OnUserChanged
      {
         
get { return _UserChangedHandle; }
         
set { _UserChangedHandle = value; }
      }
      
      
///
      
/// 构造器
      
///

      
public Server()
      {
         _userList =
new UserCollection();
         _remotePoint =
new IPEndPoint(IPAddress.Any, 0);
         _serverThread =
new Thread(new ThreadStart(Run));
      }
      
      
///
      
///显示跟踪记录
      
///

      
///
      
private void DoWriteLog(string log)
      {
         
if (_WriteLogHandle != null)
         (_WriteLogHandle.Target
as System.Windows.Forms.Control).Invoke(_WriteLogHandle, log);
      }
      
      
///
      
/// 刷新用户列表
      
///

      
/// 用户列表
      
private void DoUserChanged(UserCollection list)
      {
         
if (_UserChangedHandle != null)
         (_UserChangedHandle.Target
as Control).Invoke(_UserChangedHandle, list);
      }
      
      
///
      
/// 开始启动线程
      
///

      
public void Start()
      {
         
try
         {
            _server =
new UdpClient(Globals.SERVER_PORT);
            _serverThread.Start();
            DoWriteLog("
服务器已经启动,监听端口:" + Globals.SERVER_PORT.ToString() + ",等待客户连接...");
         }
         
catch (Exception ex)
         {
            DoWriteLog("
启动服务器发生错误: " + ex.Message);
            
throw ex;
         }
      }
      
      
///
      
/// 停止线程
      
///

      
public void Stop()
      {
         DoWriteLog("
停止服务器...");
         
try
         {
            _serverThread.Abort();
            _server.Close();
            DoWriteLog("
服务器已停止.");
         }
         
catch (Exception ex)
         {
            DoWriteLog("
停止服务器发生错误: " + ex.Message);
            
throw ex;
         }
      }
      
      
//线程主方法
      
private void Run()
      {
         
byte[] msgBuffer = null;
         
         
while (true)
         {
            msgBuffer = _server.Receive(
ref _remotePoint); //接受消息
            
try
            {
               
//将消息转换为对象
               
object msgObject = ObjectSerializer.Deserialize(msgBuffer);
               
if (msgObject == null) continue;
               
               Type msgType = msgObject.GetType();
               DoWriteLog("
接收到消息:" + msgType.ToString());
               DoWriteLog("From:" + _remotePoint.ToString());
               
               
//新用户登录
               
if (msgType == typeof(C2S_LoginMessage))
               {
                  C2S_LoginMessage lginMsg = (C2S_LoginMessage)msgObject;
                  DoWriteLog(
string.Format("用户’{0}’已登录!", lginMsg.FromUserName));
                  
                  
// 添加用户到列表
                  IPEndPoint userEndPoint =
new IPEndPoint(_remotePoint.Address, _remotePoint.Port);
                  User user =
new User(lginMsg.FromUserName, userEndPoint);
                  _userList.Add(user);
                  
                  
this.DoUserChanged(_userList);
                  
                  
//通知所有人,有新用户登录
                  S2C_UserAction msgNewUser =
new S2C_UserAction(user, UserAction.Login);
                  
foreach (User u in _userList)
                  {
                     
if (u.UserName == user.UserName) //如果是自己,发送所有在线用户列表
                     
this.SendMessage(new S2C_UserListMessage(_userList), u.NetPoint);
                     
else
                     
this.SendMessage(msgNewUser, u.NetPoint);
                  }
               }
               
else if (msgType == typeof(C2S_LogoutMessage))
               {
                  C2S_LogoutMessage lgoutMsg = (C2S_LogoutMessage)msgObject;
                  DoWriteLog(
string.Format("用户’{0}’已登出!", lgoutMsg.FromUserName));
                  
                  
// 从列表中删除用户
                  User logoutUser = _userList.Find(lgoutMsg.FromUserName);
                  
if (logoutUser != null) _userList.Remove(logoutUser);
                  
                  
this.DoUserChanged(_userList);
                  
                  
//通知所有人,有用户登出
                  S2C_UserAction msgNewUser =
new S2C_UserAction(logoutUser, UserAction.Logout);
                  
foreach (User u in _userList)
                  
this.SendMessage(msgNewUser, u.NetPoint);
               }
               
               
else if (msgType == typeof(C2S_HolePunchingRequestMessage))
               {
                  
//接收到AB打洞的消息,打洞请求,由客户端发送给服务器端
                  C2S_HolePunchingRequestMessage msgHoleReq = (C2S_HolePunchingRequestMessage)msgObject;
                  
                  User userA = _userList.Find(msgHoleReq.FromUserName);
                  User userB = _userList.Find(msgHoleReq.ToUserName);
                  
                  
// 发送打洞(Punching Hole)消息
                  DoWriteLog(
string.Format("用户:[{0} IP:{1}]想与[{2} IP:{3}]建立对话通道.",
                  userA.UserName, userA.NetPoint.ToString(),
                  userB.UserName, userB.NetPoint.ToString()));
                  
                  
//Server发送消息给B,AIPIP地址信息告诉B,然后由B发送一个测试消息给A.
                  S2C_HolePunchingMessage msgHolePunching =
new S2C_HolePunchingMessage(_remotePoint);
                  
this.SendMessage(msgHolePunching, userB.NetPoint); //Server->B
               }
               
else if (msgType == typeof(C2S_GetUsersMessage))
               {
                  
// 发送当前用户信息
                  S2C_UserListMessage srvResMsg =
new S2C_UserListMessage(_userList);
                  
this.SendMessage(srvResMsg, _remotePoint);
               }
            }
            
catch (Exception ex) { DoWriteLog(ex.Message); }
         }
      }
      
///
      
/// 发送消息
      
///

      
public void SendMessage(MessageBase msg, IPEndPoint remoteIP)
      {
         DoWriteLog("
正在发送消息:" + msg.ToString());
         
if (msg == null) return;
         
byte[] buffer = ObjectSerializer.Serialize(msg);
         _server.Send(buffer, buffer.Length, remoteIP);
         DoWriteLog("
消息已发送.");
      }
   }
}
如转载请注明本文来自易学网http://www.vjsdn.com/


3.
客户端主窗体源代码

public partial class frmClient : Form
{
   
private Client _client;
   
   
public frmClient()
   {
      InitializeComponent();
   }
   
   
private void frmClient_Load(object sender, EventArgs e)
   {
      _client =
new Client();
      _client.OnWriteMessage =
this.WriteLog;
      _client.OnUserChanged =
this.OnUserChanged;
   }
   
   
private void button1_Click(object sender, EventArgs e)
   {
      _client.Login(textBox2.Text, "");
      _client.Start();
   }
   
   
private void WriteLog(string msg)
   {
      listBox2.Items.Add(msg);
      listBox2.SelectedIndex = listBox2.Items.Count - 1;
   }
   
   
private void button4_Click(object sender, EventArgs e)
   {
      
this.Close();
   }
   
   
private void button3_Click(object sender, EventArgs e)
   {
      
if (_client != null)
      {
         User user = listBox1.SelectedItem
as User;
         _client.HolePunching(user);
      }
   }
   
   
private void button2_Click(object sender, EventArgs e)
   {
      
if (_client != null) _client.DownloadUserList();
   }
   
   
private void frmClient_FormClosing(object sender, FormClosingEventArgs e)
   {
      
if (_client != null) _client.Logout();
   }
   
   
private void OnUserChanged(UserCollection users)
   {
      listBox1.DisplayMember = "FullName";
      listBox1.DataSource =
null;
      listBox1.DataSource = users;
   }
   
   
private void button5_Click(object sender, EventArgs e)
   {
      P2P_TalkMessage msg =
new P2P_TalkMessage(textBox1.Text);
      User user = listBox1.SelectedItem
as User;
      _client.SendMessage(msg, user);
   }
   
   
private void button6_Click(object sender, EventArgs e)
   {
      listBox2.Items.Clear();
   }
}
如转载请注明本文来自易学网http://www.vjsdn.com/


4.
客户端业务逻辑代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using vjsdn.net.library;
using System.Windows.Forms;
using System.IO;

namespace vjsdn.net.library
{
   
///


   
/// 客户端业务类
   
///

   
public class Client : IDisposable
   {
      
//private const int MAX_RETRY_SEND_MSG = 1; //打洞时连接次数,正常情况下一次就能成功
      
      
private UdpClient _client;//客户端监听器
      
private IPEndPoint _hostPoint; //主机IP
      
private IPEndPoint _remotePoint; //接收任何远程机器的数据
      
private UserCollection _userList;//在线用户列表
      
private Thread _listenThread; //监听线程
      
private string _LocalUserName; //本地用户名
      
//private bool _HoleAccepted = false; //A->B,接收到B用户的确认消息
      
      
private WriteLogHandle _OnWriteMessage;
      
public WriteLogHandle OnWriteMessage
      {
         
get { return _OnWriteMessage; }
         
set { _OnWriteMessage = value; }
      }
      
      
private UserChangedHandle _UserChangedHandle = null;
      
public UserChangedHandle OnUserChanged
      {
         
get { return _UserChangedHandle; }
         
set { _UserChangedHandle = value; }
      }
      
      
///
      
///显示跟踪记录
      
///

      
///
      
private void DoWriteLog(string log)
      {
         
if (_OnWriteMessage != null)
         (_OnWriteMessage.Target
as Control).Invoke(_OnWriteMessage, log);
      }
      
      
///
      
/// 构造器
      
///

      
///
      
public Client()
      {
         
string serverIP = this.GetServerIP();
         _remotePoint =
new IPEndPoint(IPAddress.Any, 0); //任何与本地连接的用户IP地址。
         _hostPoint =
new IPEndPoint(IPAddress.Parse(serverIP), Globals.SERVER_PORT); //服务器地址
         _client =
new UdpClient();//不指定端口,系统自动分配
         _userList =
new UserCollection();
         _listenThread =
new Thread(new ThreadStart(Run));
      }
      
      
///
      
/// 获取服务器IPINI文件内设置
      
///

      
///
      
private string GetServerIP()
      {
         
string file = Application.StartupPath + "//ip.ini";
         
string ip = File.ReadAllText(file);
         
return ip.Trim();
      }
      
      
///
      
/// 启动客户端
      
///

      
public void Start()
      {
         
if (this._listenThread.ThreadState == ThreadState.Unstarted)
         {
            
this._listenThread.Start();
         }
      }
      
      
///
      
/// 客户登录
      
///

      
public void Login(string userName, string password)
      {
         _LocalUserName = userName;
         
         
// 发送登录消息到服务器
         C2S_LoginMessage loginMsg =
new C2S_LoginMessage(userName, password);
         
this.SendMessage(loginMsg, _hostPoint);
      }
      
      
///
      
/// 登出
      
///

      
public void Logout()
      {
         C2S_LogoutMessage lgoutMsg =
new C2S_LogoutMessage(_LocalUserName);
         
this.SendMessage(lgoutMsg, _hostPoint);
         
         
this.Dispose();
         System.Environment.Exit(0);
      }
      
      
///
      
/// 发送请求获取用户列表
      
///

      
public void DownloadUserList()
      {
         C2S_GetUsersMessage getUserMsg =
new C2S_GetUsersMessage(_LocalUserName);
         
this.SendMessage(getUserMsg, _hostPoint);
      }
      
      
///
      
/// 显示在线用户
      
///

      
///
      
private void DisplayUsers(UserCollection users)
      {
         
if (_UserChangedHandle != null)
         (_UserChangedHandle.Target
as Control).Invoke(_UserChangedHandle, users);
      }
      
      
//运行线程
      
private void Run()
      {
         
try
         {
            
byte[] buffer;//接受数据用
            
while (true)
            {
               buffer = _client.Receive(
ref _remotePoint);//_remotePoint变量返回当前连接的用户IP地址
               
               
object msgObj = ObjectSerializer.Deserialize(buffer);
               Type msgType = msgObj.GetType();
               DoWriteLog("
接收到消息:" + msgType.ToString() + " From:" + _remotePoint.ToString());
               
               
if (msgType == typeof(S2C_UserListMessage))
               {
                  
// 更新用户列表
                  S2C_UserListMessage usersMsg = (S2C_UserListMessage)msgObj;
                  _userList.Clear();
                  
                  
foreach (User user in usersMsg.UserList)
                  _userList.Add(user);
                  
                  
this.DisplayUsers(_userList);
               }
               
else if (msgType == typeof(S2C_UserAction))
               {
                  
//用户动作,新用户登录/用户登出
                  S2C_UserAction msgAction = (S2C_UserAction)msgObj;
                  
if (msgAction.Action == UserAction.Login)
                  {
                     _userList.Add(msgAction.User);
                     
this.DisplayUsers(_userList);
                  }
                  
else if (msgAction.Action == UserAction.Logout)
                  {
                     User user = _userList.Find(msgAction.User.UserName);
                     
if (user != null) _userList.Remove(user);
                     
this.DisplayUsers(_userList);
                  }
               }
               
else if (msgType == typeof(S2C_HolePunchingMessage))
               {
                  
//接受到服务器的打洞命令
                  S2C_HolePunchingMessage msgHolePunching = (S2C_HolePunchingMessage)msgObj;
                  
                  
//NAT-B的用户给NAT-A的用户发送消息,此时UDP包肯定会被NAT-A丢弃,
                  
//因为NAT-A上并没有A->NAT-B的合法Session, 但是现在NAT-B上就建立了有B->NAT-A的合法session!
                  P2P_HolePunchingTestMessage msgTest =
new P2P_HolePunchingTestMessage(_LocalUserName);
                  
this.SendMessage(msgTest, msgHolePunching.RemotePoint);
               }
               
else if (msgType == typeof(P2P_HolePunchingTestMessage))
               {
                  
//UDP打洞测试消息
                  
//_HoleAccepted = true;
                  P2P_HolePunchingTestMessage msgTest = (P2P_HolePunchingTestMessage)msgObj;
                  UpdateConnection(msgTest.UserName, _remotePoint);
                  
                  
//发送确认消息
                  P2P_HolePunchingResponse response =
new P2P_HolePunchingResponse(_LocalUserName);
                  
this.SendMessage(response, _remotePoint);
               }
               
else if (msgType == typeof(P2P_HolePunchingResponse))
               {
                  
//_HoleAccepted = true;//打洞成功
                  P2P_HolePunchingResponse msg = msgObj
as P2P_HolePunchingResponse;
                  UpdateConnection(msg.UserName, _remotePoint);
                  
               }
               
else if (msgType == typeof(P2P_TalkMessage))
               {
                  
//用户间对话消息
                  P2P_TalkMessage workMsg = (P2P_TalkMessage)msgObj;
                  DoWriteLog(workMsg.Message);
               }
               
else
               {
                  DoWriteLog("
收到未知消息!");
               }
            }
         }
         
catch (Exception ex) { DoWriteLog(ex.Message); }
      }
      
      
private void UpdateConnection(string user, IPEndPoint ep)
      {
         User remoteUser = _userList.Find(user);
         
if (remoteUser != null)
         {
            remoteUser.NetPoint = ep;
//保存此次连接的IP及端口
            remoteUser.IsConnected =
true;
            DoWriteLog(
string.Format("您已经与{0}建立通信通道,IP:{1}!",
            remoteUser.UserName, remoteUser.NetPoint.ToString()));
            
this.DisplayUsers(_userList);
         }
      }
      
      
#region IDisposable 成员
      
      
public void Dispose()
      {
         
try
         {
            
this._listenThread.Abort();
            
this._client.Close();
         }
         
catch
         {
            
         }
      }
      
      
#endregion
      
      
public void SendMessage(MessageBase msg, User user)
      {
         
this.SendMessage(msg, user.NetPoint);
      }
      
      
public void SendMessage(MessageBase msg, IPEndPoint remoteIP)
      {
         
if (msg == null) return;
         DoWriteLog("
正在发送消息给->" + remoteIP.ToString() + ",内容:" + msg.ToString());
         
byte[] buffer = ObjectSerializer.Serialize(msg);
         _client.Send(buffer, buffer.Length, remoteIP);
         DoWriteLog("
消息已发送.");
      }
      
      
///
      
/// UDP打洞过程
      
/// 假设A想连接B.首先A发送打洞消息给Server,Server告诉B有人想与你建立通话通道,ServerAIP信息转发给B
      
/// B收到命令后向A发一个UDP,此时BNAT会建立一个与A通讯的Session. 然后A再次向B发送UDPB就能收到了
      
///

      
public void HolePunching(User user)
      {
         
//A:自己; B:参数user
         
//A发送打洞消息给服务器,请求与B打洞
         C2S_HolePunchingRequestMessage msg =
new C2S_HolePunchingRequestMessage(_LocalUserName, user.UserName);
         
this.SendMessage(msg, _hostPoint);
         
         Thread.Sleep(2000);
//等待对方发送UDP包并建立Session
         
         
//再向对方发送确认消息,如果对方收到会发送一个P2P_HolePunchingResponse确认消息,此时打洞成功
         P2P_HolePunchingTestMessage confirmMessage =
new P2P_HolePunchingTestMessage(_LocalUserName);
         
this.SendMessage(confirmMessage, user);
      }
   }
   
}
如转载请注明本文来自易学网http://www.vjsdn.com/


[原创]C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(2)

***如转载请注明本文来自易学网www.vjsdn.com***

你可能感兴趣的:(.Net平台)