[原创]C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(1)
[原创]C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(1)
UDP穿越NAT,UDP打洞 C#实现UDP穿越NAT程序运行效果图 (图一)运行在公网上的服务器程序,用于转发打洞消息. (图二)运行在公网上的测试客户端程序A (图三)运行在NAT网络上的测试客户端程序B (图四) UDP打洞过程状态图 ***阅读下面代码前请先了解UDP穿越NAT原理*** 1.服务器主窗体源代码 public partial class frmServer : Form 2.服务器业务类{ 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 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 == 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)) { //接收到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 = 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); } } } /// <summary> /// 发送消息 /// </summary> 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("消息已发送."); } } } 来源: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 = 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("消息已发送."); } /// <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 = 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); } } }来源:C/S框架网(www.csframework.com) QQ:1980854898 扫一扫加作者微信
参考文档:
C#源代码高亮着色类(C/S框架网开源) C#网络版中国象棋游戏源代码(VS2005) C++实现的NAT打洞技术(C++ NAT Hole Puching) [原创]C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(2) UDP打洞(UDP Hole Punching)原理 C#贪吃蛇小游戏的源代码 C# ImageListView控件下载(源代码) [原创]C#键盘勾子(Hook),屏蔽键盘活动.(源代码下载) C#实现UDP穿透NAT(UDP打洞)完整版(原) 原创:CodeHighlighter源代码格式化,代码缩进,关键词高亮着色(C#源码) CSFramework C#代码生成器生成窗体界面UI,BLL,DAL,Model,WCF接口层源代码 C#源代码安全缺陷与提高源代码质量解决方案 C#源代码安全缺陷与提高源代码质量解决方案-WCF服务配置安全 C#源码-微信营销系统(第三方微信平台)C#完整源代码-源码下载 C#源码-[博客空间]X3BLOG(ASP.NET开源多用户博客系统) 1.1.0 beta1 源代码_x3blog-源码下载
其它资料:
什么是C/S结构? | C/S框架核心组成部分 | C/S框架-WebService部署图 | C/S框架-权限管理 | C/S结构系统框架 - 5.1旗舰版介绍 | C/S结构系统框架 - 功能介绍 | C/S结构系统框架 - 产品列表 | C/S结构系统框架 - 应用展示(图) | 三层体系架构详解 | C/S架构轻量级快速开发框架 | C/S框架网客户案例 | WebApi快速开发框架 | C/S框架代码生成器 | 用户授权注册软件系统 | 版本自动升级软件 | 数据库底层应用框架 | CSFramework.CMS内容管理系统 | |