C# UDP穿越NAT,UDP打洞,UDP Hole Punching源代码(1)
C#实现UDP穿越NAT程序运行效果图
(图一)运行在公网上的服务器程序,用于转发打洞消息.
(图二)运行在公网上的测试客户端程序A
(图三)运行在NAT网络上的测试客户端程序B
(图四) UDP打洞过程状态图
***阅读下面代码前请先了解UDP穿越NAT原理***
1.服务器主窗体源代码
3.客户端主窗体源代码
4.客户端业务逻辑代码
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.服务器业务类
{
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 = 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 == null) return;
byte[] buffer = ObjectSerializer.Serialize(msg);
_server.Send(buffer, buffer.Length, remoteIP);
DoWriteLog("消息已发送.");
}
}
}
来源:C/S框架网( www.csframework.com) QQ:1980854898
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 = 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 == 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
{
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 == 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 = 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
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 == 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 = 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