C# UDP穿越NAT,UDP打洞,UDP Hole Punching

    xiaoxiao2021-03-25  63

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

    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("消息已发送.");       }    } } 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();    } } 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);       }    }     }
    转载请注明原文地址: https://ju.6miu.com/read-37686.html

    最新回复(0)