思路如下(参照源代码):

  1、 frmServer启动两个网络侦听,主连接侦听,协助打洞的侦听。

  2、 frmClientA和frmClientB分别与frmServer的主连接保持联系。

  3、 当frmClientA需要和frmClientB建立直接的udp连接时,首先连接frmServer的协助打洞端口,并发送协助连接申请,同时在该端口号上启动侦听。

4、  frmServer的协助打洞连接收到frmClientA的申请后通过主连接通知frmClientB,并将frmClientA经过NAT-A转换后的公网IP地址和端口等信息告诉frmClientB。

  5、 frmClientB收到frmServer的连接通知后首先与frmServer的协助打洞端口连接,发送一些数据后立即断开,目的是让frmServer能知道frmClientB经过NAT-B转换后的公网IP和端口号。

  6、 frmClientB尝试与frmClientA的经过NAT-A转换后的公网IP地址和端口进行connect,不同的路由器会有不同的结果,多数路由器对未知不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即frmClientB向frmClientA打了一个洞,下次frmClientA就能直接连接到frmClientB刚才使用的端口号了。

  7、 客户端frmClientB打洞的同时在相同的端口上启动侦听。frmClientB在一切准备就绪以后通过与frmServer的主连接回复消息“可以了,已经准备”,frmServer在收到以后将frmClientB经过NAT-B转换后的公网IP和端口号告诉给frmClientA。

  8、 frmClientA收到frmServer回复的frmClientB的公网IP和端口号等信息以后,开始连接到frmClientB公网IP和端口号,由于在步骤6中frmClientB曾经尝试连接过frmClientA的公网IP地址和端口,NAT-A纪录了此次连接的信息,所以当frmClientA主动连接frmClientB时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的udp连接建立起来了。

  • frmClientB

客户端核心代码:

 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); }
}
  • frmClientA

服务端核心代码:

 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); }
}
}
  • frmServer

如转载请注明本文来自易学网http://www.vjsdn.com/

C# p2p UDP穿越NAT,UDP打洞源码的更多相关文章

  1. UDP穿越NAT原理(p2p)

    转载自:http://blog.csdn.net/ldd909/article/details/5979967 论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码).在这里我 ...

  2. P2P网络穿越 NAT穿越

    http://blog.csdn.net/mazidao2008/article/details/4933730 ——————————————————————————————————————————— ...

  3. 以太坊系列之五: p2p的nat模块--以太坊源码学习

    p2p的nat模块 该模块相对比较简单,因为nat的真正实现并不在此模块,主要是使用了第三方的nat-upnp和nat-pmp来实现真正的穿透(端口映射). 对外公布的接口 ```go // An i ...

  4. 【转】P2P之UDP穿透NAT的原理与实现(附源代码)

    作者:shootingstars (有容乃大,无欲则刚)  日期:2004-5-25 出处:P2P中国(PPcn.net) P2P 之 UDP穿透NAT的原理与实现(附源代码)原创:shootings ...

  5. [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)

    NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...

  6. [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现

    转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...

  7. 转:【专题七】UDP编程补充——UDP广播程序的实现

    上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享下,本专题主要介绍下如何实现UDP广播的程序,下面就直接介绍实现过程和代码以及运行的结果. 一.程序实现 UDP广播程序的实现代 ...

  8. 专题七:UDP编程补充——UDP广播程序的实现

    一.程序实现 UDP广播程序的实现代码: using System; using System.Net; using System.Net.Sockets; using System.Text; us ...

  9. 死磕以太坊源码分析之p2p节点发现

    死磕以太坊源码分析之p2p节点发现 在阅读节点发现源码之前必须要理解kadmilia算法,可以参考:KAD算法详解. 节点发现概述 节点发现,使本地节点得知其他节点的信息,进而加入到p2p网络中. 以 ...

随机推荐

  1. 广州区块链系统or积分联盟

    区块链技术开发至今已有十年,从概念的现世到如今初步应用,区块链开发公司在各个领域开始发光发热,很多人都想参与其中,通过区块链开发实现企业转型来适应未来市场,也有一些初创者希望借此实现创业意图,但在诸多 ...

  2. PAT甲题题解-1037. Magic Coupon (25)-贪心,水

    题目说了那么多,就是给你两个序列,分别选取元素进行一对一相乘,求得到的最大乘积. 将两个序列的正和负数分开,排个序,然后分别将正1和正2前面的相乘,负1和负2前面的相乘,累加和即可. #include ...

  3. 《linux内核设计与实现》第一章

    第一章Linux内核简介 一.unix 1.Unix的历史 Unix是现存操作系统中最强大和最优秀的系统. ——1969年由Ken Thompson和Dernis Ritchie的灵感点亮的产物. — ...

  4. maybe i have no answer

    怎么说呢,我从小学开始到高中,大学.我觉得老师对大家都是一样的,虽然我因为父母的原因可能和老师接触比较多,但是学业上其实没什么帮助的. 我更希望老师能给我人生道路上的指点,虽然自己的道路确实是自己走出 ...

  5. 把Excel转换成DataTable,Excel2003+

    在数据处理的时候,我们会Excel(包含2003.2007.2010等)转换成DataTable,以便进一步操作 1.怎么访问Excel文件呢?我们可以通过OLEDB接口访问,如下: private ...

  6. SQLSERVER 查看操作系统内存

    1. 通过系统试图查看内存信息 SELECT total_physical_memory_kb / AS [物理内存(MB)] , available_physical_memory_kb / AS ...

  7. Django-基本指令

    目录 Django基本指令 下载Django 创建Django项目 创建APP应用 启动Django项目 更新数据库表或字段 清空数据库数据 创建超级管理员 查看更多命令 Django基本指令 下载D ...

  8. JIRA部署破解和confluence整合

    JIRA是一个项目跟踪管理工具,帮助团队创建计划任务.构建并发布优秀的产品.全球成千上万的团队选择JIRA,用JIRA来捕获.组织管理缺陷.分配任务,跟踪团队的活动.不论在桌面PC还是移动终端设备上, ...

  9. 常用的Hql语句

    // HQL: Hibernate Query Language.// 特点:// >> 1,与SQL相似,SQL中的语法基本上都可以直接使用.// >> 2,SQL查询的是表 ...

  10. iOS 给UIView添加xib

    2017-08-25编辑:这文章有点过时了 推荐新的文章:http://www.cnblogs.com/hero11223/p/6881848.html 一段时间没敲代码,以前一些简单的都不会做了,翻 ...