(图一)运行在公网上的服务器程序,用于转发打洞消息.
(图二)运行在公网上的测试客户端程序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();
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
{
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();
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
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("消息已发送.");
}
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
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("消息已发送.");
}
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
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();
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
{
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();
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
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.公共代码
6.消息类(转发命令)
6.1 消息基类.所有命令的基类
6.2 Client to Server命令
6.3 Server to Client命令
6.4 Client to Client(Peer to Peer P2P)命令
6.5 定义用户的动作
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
{
/// <summary>
/// 显示跟踪消息的事件委托
/// </summary>
public delegate void WriteLogHandle(string msg);
/// <summary>
/// 刷新在线用户的事件委托
/// </summary>
public delegate void UserChangedHandle(UserCollection users);
public class Globals
{
/// <summary>
/// 服务器侦听端口
/// </summary>
public const int SERVER_PORT = 21134;
/// <summary>
/// 本地侦听端口
/// </summary>
public const int LOCAL_PORT = 19786;
}
/// <summary>
/// User 的摘要说明。
/// </summary>
[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");
}
}
/// <summary>
/// 在线用户列表
/// </summary>
[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;
}
}
/// <summary>
/// 序列化反序列化对象
/// </summary>
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;
}
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.Collections;
namespace vjsdn.net.library
{
/// <summary>
/// 显示跟踪消息的事件委托
/// </summary>
public delegate void WriteLogHandle(string msg);
/// <summary>
/// 刷新在线用户的事件委托
/// </summary>
public delegate void UserChangedHandle(UserCollection users);
public class Globals
{
/// <summary>
/// 服务器侦听端口
/// </summary>
public const int SERVER_PORT = 21134;
/// <summary>
/// 本地侦听端口
/// </summary>
public const int LOCAL_PORT = 19786;
}
/// <summary>
/// User 的摘要说明。
/// </summary>
[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");
}
}
/// <summary>
/// 在线用户列表
/// </summary>
[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;
}
}
/// <summary>
/// 序列化反序列化对象
/// </summary>
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;
}
}
}
如转载请注明本文来自易学网http://www.vjsdn.com/
6.消息类(转发命令)
6.1 消息基类.所有命令的基类
/// <summary>
/// 消息基类,抽象类
/// </summary>
[Serializable]
public abstract class MessageBase
{
//消息基类
}
如转载请注明本文来自易学网http://www.vjsdn.com/
/// 消息基类,抽象类
/// </summary>
[Serializable]
public abstract class MessageBase
{
//消息基类
}
如转载请注明本文来自易学网http://www.vjsdn.com/
6.2 Client to Server命令
#region 客户端发送到服务器的消息
/// <summary>
/// 客户端发送到服务器的消息基类
/// </summary>
[Serializable]
public abstract class C2S_MessageBase : MessageBase
{
private string _fromUserName;
protected C2S_MessageBase(string fromUserName)
{
_fromUserName = fromUserName;
}
public string FromUserName
{
get { return _fromUserName; }
}
}
/// <summary>
/// 用户登录消息
/// </summary>
[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; }
}
}
/// <summary>
/// 用户登出消息
/// </summary>
[Serializable]
public class C2S_LogoutMessage : C2S_MessageBase
{
public C2S_LogoutMessage(string userName)
: base(userName)
{ }
}
/// <summary>
/// 请求用户列表消息
/// </summary>
[Serializable]
public class C2S_GetUsersMessage : C2S_MessageBase
{
public C2S_GetUsersMessage(string userName)
: base(userName)
{ }
}
/// <summary>
/// 请求Purch Hole消息
/// </summary>
[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
如转载请注明本文来自易学网http://www.vjsdn.com/
/// <summary>
/// 客户端发送到服务器的消息基类
/// </summary>
[Serializable]
public abstract class C2S_MessageBase : MessageBase
{
private string _fromUserName;
protected C2S_MessageBase(string fromUserName)
{
_fromUserName = fromUserName;
}
public string FromUserName
{
get { return _fromUserName; }
}
}
/// <summary>
/// 用户登录消息
/// </summary>
[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; }
}
}
/// <summary>
/// 用户登出消息
/// </summary>
[Serializable]
public class C2S_LogoutMessage : C2S_MessageBase
{
public C2S_LogoutMessage(string userName)
: base(userName)
{ }
}
/// <summary>
/// 请求用户列表消息
/// </summary>
[Serializable]
public class C2S_GetUsersMessage : C2S_MessageBase
{
public C2S_GetUsersMessage(string userName)
: base(userName)
{ }
}
/// <summary>
/// 请求Purch Hole消息
/// </summary>
[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
如转载请注明本文来自易学网http://www.vjsdn.com/
6.3 Server to Client命令
#region 服务器发送到客户端消息
/// <summary>
/// 服务器发送到客户端消息基类
/// </summary>
[Serializable]
public abstract class S2C_MessageBase : MessageBase
{
}
/// <summary>
/// 请求用户列表应答消息
/// </summary>
[Serializable]
public class S2C_UserListMessage : S2C_MessageBase
{
private UserCollection userList;
public S2C_UserListMessage(UserCollection users)
{
this.userList = users;
}
public UserCollection UserList
{
get { return userList; }
}
}
/// <summary>
/// 转发请求Purch Hole消息
/// </summary>
[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; }
}
}
/// <summary>
/// 服务器通知所有在线用户,
/// </summary>
[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
如转载请注明本文来自易学网http://www.vjsdn.com/
/// <summary>
/// 服务器发送到客户端消息基类
/// </summary>
[Serializable]
public abstract class S2C_MessageBase : MessageBase
{
}
/// <summary>
/// 请求用户列表应答消息
/// </summary>
[Serializable]
public class S2C_UserListMessage : S2C_MessageBase
{
private UserCollection userList;
public S2C_UserListMessage(UserCollection users)
{
this.userList = users;
}
public UserCollection UserList
{
get { return userList; }
}
}
/// <summary>
/// 转发请求Purch Hole消息
/// </summary>
[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; }
}
}
/// <summary>
/// 服务器通知所有在线用户,
/// </summary>
[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
如转载请注明本文来自易学网http://www.vjsdn.com/
6.4 Client to Client(Peer to Peer P2P)命令
#region 点对点消息
/// <summary>
/// 点对点消息基类
/// </summary>
[Serializable]
public abstract class P2P_MessageBase : MessageBase
{
//
}
/// <summary>
/// 聊天消息
/// </summary>
[Serializable]
public class P2P_TalkMessage : P2P_MessageBase
{
private string message;
public P2P_TalkMessage(string msg)
{
message = msg;
}
public string Message
{
get { return message; }
}
}
/// <summary>
/// UDP打洞测试消息
/// </summary>
[Serializable]
public class P2P_HolePunchingTestMessage : P2P_MessageBase
{
private string _UserName;
public P2P_HolePunchingTestMessage(string userName)
{
_UserName = userName;
}
public string UserName
{
get { return _UserName; }
}
}
/// <summary>
/// 收到消息的回复确认
/// 如A与B想建立通话通道,些命令由B发出确认打洞成功
/// </summary>
[Serializable]
public class P2P_HolePunchingResponse : P2P_MessageBase
{
private string _UserName;
public P2P_HolePunchingResponse(string userName)
{
_UserName = userName;
}
public string UserName
{
get { return _UserName; }
}
}
#endregion
如转载请注明本文来自易学网http://www.vjsdn.com/
/// <summary>
/// 点对点消息基类
/// </summary>
[Serializable]
public abstract class P2P_MessageBase : MessageBase
{
//
}
/// <summary>
/// 聊天消息
/// </summary>
[Serializable]
public class P2P_TalkMessage : P2P_MessageBase
{
private string message;
public P2P_TalkMessage(string msg)
{
message = msg;
}
public string Message
{
get { return message; }
}
}
/// <summary>
/// UDP打洞测试消息
/// </summary>
[Serializable]
public class P2P_HolePunchingTestMessage : P2P_MessageBase
{
private string _UserName;
public P2P_HolePunchingTestMessage(string userName)
{
_UserName = userName;
}
public string UserName
{
get { return _UserName; }
}
}
/// <summary>
/// 收到消息的回复确认
/// 如A与B想建立通话通道,些命令由B发出确认打洞成功
/// </summary>
[Serializable]
public class P2P_HolePunchingResponse : P2P_MessageBase
{
private string _UserName;
public P2P_HolePunchingResponse(string userName)
{
_UserName = userName;
}
public string UserName
{
get { return _UserName; }
}
}
#endregion
如转载请注明本文来自易学网http://www.vjsdn.com/
6.5 定义用户的动作
/// <summary>
/// 用户动作
/// </summary>
public enum UserAction
{
Login,
Logout
.... more action
}
如转载请注明本文来自易学网http://www.vjsdn.com/
/// 用户动作
/// </summary>
public enum UserAction
{
Login,
Logout
.... more action
}
如转载请注明本文来自易学网http://www.vjsdn.com/