首页 > 编程开发 > Objective-C编程 >
-
UDP打洞简单描述
第一次写文章,难免会有很多地方不尽人意,希望大家多多包涵。今天我想和大家谈谈关于Udp打洞的一些基本知识。
C#提供了Sockets来进行套接字的编程,里面包含了TcpClient和UdpClient。用过的大家都知道Tcp发送消息更安全
而Udp传送数据容易丢包,但速度快,能穿越防火墙。目前比较流行的QQ聊天工具底层传输协议用户的就是Udp协议(不知道现在该没)。
那么大家或许会发现,为什么你的好友隐身的时候你像他说话或者发送图片的时候会提示对方隐身不能发送或者要等很久
才能收到。其实这里就是打洞了,可能有些人不明白为什么会叫打洞呢?大家想想,暴露在公网上的电脑IP为什么会比经
过路由分下来的电脑IP更容易收到攻击了,因为它的地址更容易被查找出来,而经过路由分下来的IP无数多个而且很可能
是多个路由连接在分配,那自然找到IP就不容易了。所以我们需要一个大家都知道的一个地址(服务器)然后对齐访问,
此时客户端就会发送一条消息到服务器,服务器就会接收到你的地址也就连通了,这里我用图在说明这个流程吧。
现在客户端开始发送信息到服务器
目前一条通信道路就打通了,服务器于电脑A就可以相互访问了,但其他电脑还是不能访问,所以也要先登陆服务器,
然服务器知道你的IP和Port是从哪来然后保存下来。当其中一台电脑要访问电脑A的时候就要先从服务器获取电脑A的IP
和Port,这样2个客户端才算是真正的连通了,这也解释了你的QQ好友隐身问什么你发送消息要等一段时间才能发送成
功的原因所在。当然这是一种比较简单的处理方式,还有一些更复杂点的,技术有限还没去研究过,但这是最基本的原理。
下面是我写的一些示例代码:(聊天没做,不过用户信息都获取到了,做起来很简单了)
客户端,界面自己拖拖就是了,就几个按钮、文本框加个显示所有登陆用户信息的listbox。
Client
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using lib;
using System.Threading;
namespace Client
{
public partial class FrmClient : Form
{
private IPEndPoint hostPoint;//服务器主机地址
private IPEndPoint remotePoint;//目标主机地址
private List<UserInfo> userInfoList;//保存所有登陆用户信息
private Thread listenThread;//监听线程
private UdpClient udpClient;
public FrmClient()
{
InitializeComponent();
userInfoList = new List<UserInfo>();
listenThread = new Thread(new ThreadStart(Run));
}
/**//// <summary>
/// 登陆按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLogin_Click(object sender, EventArgs e)
{
string serverIp = this.txtIP.Text;
int Port = int.Parse(this.txtPort.Text);
//不需要指定端口,路由会自动跟你的电脑映射一个(除非你要监听一个端口),这里不要错误理解为服务器端口,应该是客户端监听消息的端口
udpClient = new UdpClient();
remotePoint = new IPEndPoint(IPAddress.Any, 0);//提供任何端口和IP,这里不需要指定
hostPoint = new IPEndPoint(IPAddress.Parse(serverIp), Port);//实例化主机地址
LoginServer(this.txtUserName.Text);//由于udpClient还没有连接到服务器,所以要先登陆然后在监听
this.btnLogin.Enabled = false;
listenThread.Start();
}
/**//// <summary>
/// 发送消息按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
}
/**//// <summary>
/// 登陆服务器
/// </summary>
/// <param name="userName">用户名</param>
private void LoginServer(string userName)
{
UserInfo userInfo = new UserInfo();
userInfo.UserName = userName;
byte[] buffer = SerializeHelper.Serialize(userInfo);
udpClient.Send(buffer, buffer.Length, hostPoint);
buffer = udpClient.Receive(ref remotePoint);
UserList userList = (UserList)SerializeHelper.Deserialize(buffer);
this.userInfoList = userList.userInfoList;
foreach (UserInfo user in userInfoList)
{
this.lstUserInfo.Items.Add(user.UserName + ":" + user.Address.ToString());
}
}
/**//// <summary>
/// 监听线程
/// </summary>
private void Run()
{
byte[] buffer;
while (true)
{
buffer = udpClient.Receive(ref remotePoint);
try
{
object msgObj = SerializeHelper.Deserialize(buffer);
Type msgType = msgObj.GetType();
if (msgType == typeof(UserList))
{
UserList userList = (UserList)msgObj;
this.userInfoList = userList.userInfoList;
this.lstUserInfo.BeginInvoke(new System.EventHandler(ClearUI), this.userInfoList);
}
Thread.Sleep(50);
}
catch
{ }
}
}
/**//// <summary>
/// 获取在线用户列表
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGetUserInfo_Click(object sender, EventArgs e)
{
GetUserList getUserList = new GetUserList();
byte[] buffer = SerializeHelper.Serialize(getUserList);
udpClient.Send(buffer, buffer.Length, hostPoint);
}
/**//// <summary>
/// 跨线程访问控件
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
private void ClearUI(object o, System.EventArgs e)
{
this.lstUserInfo.Items.Clear();
this.lstUserInfo.Items.Add(((UserInfo)o).UserName + ":" + ((UserInfo)o).Address.ToString());
}
/**//// <summary>
/// 窗体关闭事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmClient_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
if (this.listenThread.ThreadState == ThreadState.Running)
{
this.listenThread.Abort();
}
udpClient.Close();
}
catch
{ }
}
}
}
类库,传递序列化的消息类。
lib
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace lib
{
[Serializable]
public class GetUserList
{
}
public class SerializeHelper
{
/**////*****************************************
/// <summary>
/// 序列化一个对象
/// </summary>
/// <param name="o">将要序列化的对象</param>
/// <returns>返回byte[]</returns>
///*****************************************
public static byte[] Serialize(object o)
{
if (o == null) return null;
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
formatter.Serialize(ms, o);
ms.Position = 0;
byte[] b = new byte[ms.Length];
ms.Read(b, 0, b.Length);
ms.Close();
return b;
}
/**////*****************************************
/// <summary>
/// 反序列化
/// </summary>
/// <param name="b">返回一个对象</param>
///*****************************************
public static object Deserialize(byte[] b)
{
if (b.Length == 0) return null;
try
{
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
ms.Write(b, 0, b.Length);
ms.Position = 0;
object n = (object)bf.Deserialize(ms);
ms.Close();
return n;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
return null;
}
}
}
[Serializable]
public class UserInfo
{
/**//// <summary>
/// 用户名
/// </summary>
public string UserName
{
set;
get;
}
/**//// <summary>
/// 地址(IP+Port)
/// </summary>
public IPEndPoint Address
{
set;
get;
}
}
[Serializable]
public class UserList
{
/**//// <summary>
/// 用户列表
/// </summary>
public List<UserInfo> userInfoList
{
set;
get;
}
}
}
服务器端,
Server
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using lib;
using System.Threading;
namespace Server
{
public partial class FrmServer : Form
{
private UdpClient udpServer;
private List<UserInfo> userInfoList;
private IPEndPoint remotePoint;//目标IP地址
private Thread listenThread;//监听线程
public FrmServer()
{
InitializeComponent();
userInfoList = new List<UserInfo>();
listenThread = new Thread(new ThreadStart(Run));
}
/**//// <summary>
/// 连接按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnConnect_Click(object sender, EventArgs e)
{
int port = int.Parse(this.txtPort.Text);
udpServer = new UdpClient(port);
this.btnConnect.Enabled = false;
listenThread.Start();
}
/**//// <summary>
/// 监听线程事件
/// </summary>
private void Run()
{
byte[] buffer;
while (true)
{
buffer = udpServer.Receive(ref remotePoint);
try
{
object msgObj = SerializeHelper.Deserialize(buffer);
Type msgType = msgObj.GetType();
if (msgType == typeof(UserInfo))//用户登陆,返回当前所有用户登陆后的信息
{
UserInfo userInfo = (UserInfo)msgObj;
userInfo.Address = remotePoint;
this.userInfoList.Add(userInfo);
this.lstUserInfo.BeginInvoke(new System.EventHandler(UpdateUI), userInfo);
UserList userList = new UserList();
userList.userInfoList = this.userInfoList;
byte[] buff = SerializeHelper.Serialize(userList);
this.udpServer.Send(buff, buff.Length, remotePoint);
}
if (msgType == typeof(GetUserList))//获取所有登陆后的用户信息
{
UserList userList = new UserList();
userList.userInfoList = this.userInfoList;
byte[] buff = SerializeHelper.Serialize(userList);
udpServer.Send(buff, buff.Length, remotePoint);
}
Thread.Sleep(50);
}
catch
{
}
}
}
/**//// <summary>
/// 跨线程访问控件
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
private void UpdateUI(object o, System.EventArgs e)
{
this.lstUserInfo.Items.Add(((UserInfo)o).UserName + ":" + ((UserInfo)o).Address.ToString());
}
/**//// <summary>
/// 关闭按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmServer_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
listenThread.Abort();
udpServer.Close();
}
catch
{ }
}
}
}
和Port,这样2个客户端才算是真正的连通了,这也解释了你的QQ好友隐身问什么你发送消息要等一段时间才能发送成
功的原因所在。当然这是一种比较简单的处理方式,还有一些更复杂点的,技术有限还没去研究过,但这是最基本的原理。
下面是我写的一些示例代码:(聊天没做,不过用户信息都获取到了,做起来很简单了)
客户端,界面自己拖拖就是了,就几个按钮、文本框加个显示所有登陆用户信息的listbox。
Client
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using lib;
using System.Threading;
namespace Client
{
public partial class FrmClient : Form
{
private IPEndPoint hostPoint;//服务器主机地址
private IPEndPoint remotePoint;//目标主机地址
private List<UserInfo> userInfoList;//保存所有登陆用户信息
private Thread listenThread;//监听线程
private UdpClient udpClient;
public FrmClient()
{
InitializeComponent();
userInfoList = new List<UserInfo>();
listenThread = new Thread(new ThreadStart(Run));
}
/**//// <summary>
/// 登陆按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLogin_Click(object sender, EventArgs e)
{
string serverIp = this.txtIP.Text;
int Port = int.Parse(this.txtPort.Text);
//不需要指定端口,路由会自动跟你的电脑映射一个(除非你要监听一个端口),这里不要错误理解为服务器端口,应该是客户端监听消息的端口
udpClient = new UdpClient();
remotePoint = new IPEndPoint(IPAddress.Any, 0);//提供任何端口和IP,这里不需要指定
hostPoint = new IPEndPoint(IPAddress.Parse(serverIp), Port);//实例化主机地址
LoginServer(this.txtUserName.Text);//由于udpClient还没有连接到服务器,所以要先登陆然后在监听
this.btnLogin.Enabled = false;
listenThread.Start();
}
/**//// <summary>
/// 发送消息按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
}
/**//// <summary>
/// 登陆服务器
/// </summary>
/// <param name="userName">用户名</param>
private void LoginServer(string userName)
{
UserInfo userInfo = new UserInfo();
userInfo.UserName = userName;
byte[] buffer = SerializeHelper.Serialize(userInfo);
udpClient.Send(buffer, buffer.Length, hostPoint);
buffer = udpClient.Receive(ref remotePoint);
UserList userList = (UserList)SerializeHelper.Deserialize(buffer);
this.userInfoList = userList.userInfoList;
foreach (UserInfo user in userInfoList)
{
this.lstUserInfo.Items.Add(user.UserName + ":" + user.Address.ToString());
}
}
/**//// <summary>
/// 监听线程
/// </summary>
private void Run()
{
byte[] buffer;
while (true)
{
buffer = udpClient.Receive(ref remotePoint);
try
{
object msgObj = SerializeHelper.Deserialize(buffer);
Type msgType = msgObj.GetType();
if (msgType == typeof(UserList))
{
UserList userList = (UserList)msgObj;
this.userInfoList = userList.userInfoList;
this.lstUserInfo.BeginInvoke(new System.EventHandler(ClearUI), this.userInfoList);
}
Thread.Sleep(50);
}
catch
{ }
}
}
/**//// <summary>
/// 获取在线用户列表
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGetUserInfo_Click(object sender, EventArgs e)
{
GetUserList getUserList = new GetUserList();
byte[] buffer = SerializeHelper.Serialize(getUserList);
udpClient.Send(buffer, buffer.Length, hostPoint);
}
/**//// <summary>
/// 跨线程访问控件
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
private void ClearUI(object o, System.EventArgs e)
{
this.lstUserInfo.Items.Clear();
this.lstUserInfo.Items.Add(((UserInfo)o).UserName + ":" + ((UserInfo)o).Address.ToString());
}
/**//// <summary>
/// 窗体关闭事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmClient_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
if (this.listenThread.ThreadState == ThreadState.Running)
{
this.listenThread.Abort();
}
udpClient.Close();
}
catch
{ }
}
}
}