C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
***阅读下面代码前请先了解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 == 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("消息已发送."); } } } 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 = 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); } } } 5.公共代码 using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Net; using System.Collections; namespace vjsdn.net.library { /// /// 显示跟踪消息的事件委托 /// public delegate void WriteLogHandle(string msg); /// /// 刷新在线用户的事件委托 /// public delegate void UserChangedHandle(UserCollection users); public class Globals { /// /// 服务器侦听端口 /// public const int SERVER_PORT = 21134; /// /// 本地侦听端口 /// public const int LOCAL_PORT = 19786; } /// /// User 的摘要说明。 /// [Serializable] public class User { protected string _userName; protected IPEndPoint _netPoint; protected bool _conntected; public User(string UserName, IPEndPoint NetPoint) { this._userName = UserName; this._netPoint = NetPoint; } public string UserName { get { return _userName; } } public IPEndPoint NetPoint { get { return _netPoint; } set { _netPoint = value; } } public bool IsConnected //打洞标记 { get { return _conntected; } set { _conntected = value; } } public string FullName { get { return this.ToString(); } } public override string ToString() { return _userName + "- [" + _netPoint.Address.ToString() + ":" + _netPoint.Port.ToString() + "] " + (_conntected ? "Y" : "N"); } } /// /// 在线用户列表 /// [Serializable] public class UserCollection : CollectionBase { public void Add(User user) { InnerList.Add(user); } public void Remove(User user) { InnerList.Remove(user); } public User this[int index] { get { return (User)InnerList[index]; } } public User Find(string userName) { foreach (User user in this) { if (string.Compare(userName, user.UserName, true) == 0) { return user; } } return null; } } /// /// 序列化反序列化对象 /// public class ObjectSerializer { public static byte[] Serialize(object obj) { BinaryFormatter binaryF = new BinaryFormatter(); MemoryStream ms = new MemoryStream(1024 * 10); binaryF.Serialize(ms, obj); ms.Seek(0, SeekOrigin.Begin); byte[] buffer = new byte[(int)ms.Length]; ms.Read(buffer, 0, buffer.Length); ms.Close(); return buffer; } public static object Deserialize(byte[] buffer) { BinaryFormatter binaryF = new BinaryFormatter(); MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false); object obj = binaryF.Deserialize(ms); ms.Close(); return obj; } } } 6.消息类(转发命令) 6.1 消息基类.所有命令的基类 /// 6.2 Client to Server命令/// 消息基类,抽象类 /// [Serializable] public abstract class MessageBase { //消息基类 } #region 客户端发送到服务器的消息 /// /// 客户端发送到服务器的消息基类 /// [Serializable] public abstract class C2S_MessageBase : MessageBase { private string _fromUserName; protected C2S_MessageBase(string fromUserName) { _fromUserName = fromUserName; } public string FromUserName { get { return _fromUserName; } } } /// /// 用户登录消息 /// [Serializable] public class C2S_LoginMessage : C2S_MessageBase { private string _password; public C2S_LoginMessage(string userName, string password) : base(userName) { this._password = password; } public string Password { get { return _password; } } } /// /// 用户登出消息 /// [Serializable] public class C2S_LogoutMessage : C2S_MessageBase { public C2S_LogoutMessage(string userName) : base(userName) { } } /// /// 请求用户列表消息 /// [Serializable] public class C2S_GetUsersMessage : C2S_MessageBase { public C2S_GetUsersMessage(string userName) : base(userName) { } } /// /// 请求Purch Hole消息 /// [Serializable] public class C2S_HolePunchingRequestMessage : C2S_MessageBase { protected string toUserName; public C2S_HolePunchingRequestMessage(string fromUserName, string toUserName) : base(fromUserName) { this.toUserName = toUserName; } public string ToUserName { get { return this.toUserName; } } } #endregion 6.3 Server to Client命令 #region 服务器发送到客户端消息 6.4 Client to Client(Peer to Peer P2P)命令/// /// 服务器发送到客户端消息基类 /// [Serializable] public abstract class S2C_MessageBase : MessageBase { } /// /// 请求用户列表应答消息 /// [Serializable] public class S2C_UserListMessage : S2C_MessageBase { private UserCollection userList; public S2C_UserListMessage(UserCollection users) { this.userList = users; } public UserCollection UserList { get { return userList; } } } /// /// 转发请求Purch Hole消息 /// [Serializable] public class S2C_HolePunchingMessage : S2C_MessageBase { protected System.Net.IPEndPoint _remotePoint; public S2C_HolePunchingMessage(System.Net.IPEndPoint point) { this._remotePoint = point; } public System.Net.IPEndPoint RemotePoint { get { return _remotePoint; } } } /// /// 服务器通知所有在线用户, /// [Serializable] public class S2C_UserAction : S2C_MessageBase { protected User _User; protected UserAction _Action; public S2C_UserAction(User user, UserAction action) { _User = user; _Action = action; } public User User { get { return _User; } } public UserAction Action { get { return _Action; } } } #endregion #region 点对点消息 /// /// 点对点消息基类 /// [Serializable] public abstract class P2P_MessageBase : MessageBase { // } /// /// 聊天消息 /// [Serializable] public class P2P_TalkMessage : P2P_MessageBase { private string message; public P2P_TalkMessage(string msg) { message = msg; } public string Message { get { return message; } } } /// /// UDP打洞测试消息 /// [Serializable] public class P2P_HolePunchingTestMessage : P2P_MessageBase { private string _UserName; public P2P_HolePunchingTestMessage(string userName) { _UserName = userName; } public string UserName { get { return _UserName; } } } /// /// 收到消息的回复确认 /// 如A与B想建立通话通道,些命令由B发出确认打洞成功 /// [Serializable] public class P2P_HolePunchingResponse : P2P_MessageBase { private string _UserName; public P2P_HolePunchingResponse(string userName) { _UserName = userName; } public string UserName { get { return _UserName; } } } #endregion /// /// 用户动作 /// public enum UserAction { Login, Logout .... more action } 该文章在 2021/2/2 11:54:37 编辑过 |
关键字查询
相关文章
正在查询... |