C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(1)

317 篇文章 0 订阅
43 篇文章 0 订阅
C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(1)

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();
   }
}

来源:C/S框架网( www.csframework.com) QQ:1980854898
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
{
    /// <summary> 
    /// 服务器端业务类 
    /// </summary> 
    public  class Server
   {
       private UdpClient _server;  //服务器端消息监听 
       private UserCollection _userList;  //在线用户列表 
       private Thread _serverThread;
       private IPEndPoint _remotePoint;  //远程用户请求的IP地址及端口 
      
       private WriteLogHandle _WriteLogHandle =  null;
       private UserChangedHandle _UserChangedHandle =  null;
      
       /// <summary> 
       /// 显示跟踪消息 
       /// </summary> 
       public WriteLogHandle OnWriteLog
      {
          get {  return _WriteLogHandle; }
          set { _WriteLogHandle = value; }
      }
      
       /// <summary> 
       /// 当用户登入/登出时触发此事件 
       /// </summary> 
       public UserChangedHandle OnUserChanged
      {
          get {  return _UserChangedHandle; }
          set { _UserChangedHandle = value; }
      }
      
       /// <summary> 
       /// 构造器 
       /// </summary> 
       public Server()
      {
         _userList =  new UserCollection();
         _remotePoint =  new IPEndPoint(IPAddress.Any, 0);
         _serverThread =  new Thread( new ThreadStart(Run));
      }
      
       /// <summary> 
       ///显示跟踪记录 
       /// </summary> 
       /// <param name="log"></param> 
       private  void DoWriteLog( string log)
      {
          if (_WriteLogHandle !=  null)
         (_WriteLogHandle.Target  as System.Windows.Forms.Control).Invoke(_WriteLogHandle, log);
      }
      
       /// <summary> 
       /// 刷新用户列表 
       /// </summary> 
       /// <param name="list">用户列表</param> 
       private  void DoUserChanged(UserCollection list)
      {
          if (_UserChangedHandle !=  null)
         (_UserChangedHandle.Target  as Control).Invoke(_UserChangedHandle, list);
      }
      
       /// <summary> 
       /// 开始启动线程 
       /// </summary> 
       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;
         }
      }
      
       /// <summary> 
       /// 停止线程 
       /// </summary> 
       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 ==  nullcontinue;
               
               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))
               {
                   //接收到A给B打洞的消息,打洞请求,由客户端发送给服务器端 
                  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,将A的IP的IP地址信息告诉B,然后由B发送一个测试消息给A. 
                  S2C_HolePunchingMessage msgHolePunching =  newS2C_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); }
         }
      }
       /// <summary> 
       /// 发送消息 
       /// </summary> 
       public  void SendMessage(MessageBase msg, IPEndPoint remoteIP)
      {
         DoWriteLog("正在发送消息:" + msg.ToString());
          if (msg ==  nullreturn;
          byte[] buffer = ObjectSerializer.Serialize(msg);
         _server.Send(buffer, buffer.Length, remoteIP);
         DoWriteLog("消息已发送.");
      }
   }
}
来源:C/S框架网( www.csframework.com) QQ:1980854898


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();
   }
}
来源:C/S框架网( www.csframework.com) QQ:1980854898


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
{
    /// <summary> 
    /// 客户端业务类 
    /// </summary> 
    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; }
      }
      
       /// <summary> 
       ///显示跟踪记录 
       /// </summary> 
       /// <param name="log"></param> 
       private  void DoWriteLog( string log)
      {
          if (_OnWriteMessage !=  null)
         (_OnWriteMessage.Target  as Control).Invoke(_OnWriteMessage, log);
      }
      
       /// <summary> 
       /// 构造器 
       /// </summary> 
       /// <param name="serverIP"></param> 
       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));
      }
      
       /// <summary> 
       /// 获取服务器IP,INI文件内设置 
       /// </summary> 
       /// <returns></returns> 
       private  string GetServerIP()
      {
          string file = Application.StartupPath + "\\ip.ini";
          string ip = File.ReadAllText(file);
          return ip.Trim();
      }
      
       /// <summary> 
       /// 启动客户端 
       /// </summary> 
       public  void Start()
      {
          if ( this._listenThread.ThreadState == ThreadState.Unstarted)
         {
             this._listenThread.Start();
         }
      }
      
       /// <summary> 
       /// 客户登录 
       /// </summary> 
       public  void Login( string userName,  string password)
      {
         _LocalUserName = userName;
         
          // 发送登录消息到服务器 
         C2S_LoginMessage loginMsg =  new C2S_LoginMessage(userName, password);
          this.SendMessage(loginMsg, _hostPoint);
      }
      
       /// <summary> 
       /// 登出 
       /// </summary> 
       public  void Logout()
      {
         C2S_LogoutMessage lgoutMsg =  new C2S_LogoutMessage(_LocalUserName);
          this.SendMessage(lgoutMsg, _hostPoint);
         
          this.Dispose();
         System.Environment.Exit(0);
      }
      
       /// <summary> 
       /// 发送请求获取用户列表 
       /// </summary> 
       public  void DownloadUserList()
      {
         C2S_GetUsersMessage getUserMsg =  new C2S_GetUsersMessage(_LocalUserName);
          this.SendMessage(getUserMsg, _hostPoint);
      }
      
       /// <summary> 
       /// 显示在线用户 
       /// </summary> 
       /// <param name="users"></param> 
       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 =  newP2P_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 ==  nullreturn;
         DoWriteLog("正在发送消息给->" + remoteIP.ToString() + ",内容:" + msg.ToString());
          byte[] buffer = ObjectSerializer.Serialize(msg);
         _client.Send(buffer, buffer.Length, remoteIP);
         DoWriteLog("消息已发送.");
      }
      
       /// <summary> 
       /// UDP打洞过程 
       /// 假设A想连接B.首先A发送打洞消息给Server,让Server告诉B有人想与你建立通话通道,Server将A的IP信息转发给B 
       /// B收到命令后向A发一个UDP包,此时B的NAT会建立一个与A通讯的Session. 然后A再次向B发送UDP包B就能收到了 
       /// </summary> 
       public  void HolePunching(User user)
      {
          //A:自己; B:参数user 
          //A发送打洞消息给服务器,请求与B打洞 
         C2S_HolePunchingRequestMessage msg =  newC2S_HolePunchingRequestMessage(_LocalUserName, user.UserName);
          this.SendMessage(msg, _hostPoint);
         
         Thread.Sleep(2000); //等待对方发送UDP包并建立Session 
         
          //再向对方发送确认消息,如果对方收到会发送一个P2P_HolePunchingResponse确认消息,此时打洞成功 
         P2P_HolePunchingTestMessage confirmMessage =  newP2P_HolePunchingTestMessage(_LocalUserName);
          this.SendMessage(confirmMessage, user);
      }
   }
   
}来源:C/S框架网( www.csframework.com) QQ:1980854898
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值