C# p2p UDP穿越NAT,UDP打洞源码
思路如下(参照源代码):
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打洞源码的更多相关文章
- UDP穿越NAT原理(p2p)
转载自:http://blog.csdn.net/ldd909/article/details/5979967 论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码).在这里我 ...
- P2P网络穿越 NAT穿越
http://blog.csdn.net/mazidao2008/article/details/4933730 ——————————————————————————————————————————— ...
- 以太坊系列之五: p2p的nat模块--以太坊源码学习
p2p的nat模块 该模块相对比较简单,因为nat的真正实现并不在此模块,主要是使用了第三方的nat-upnp和nat-pmp来实现真正的穿透(端口映射). 对外公布的接口 ```go // An i ...
- 【转】P2P之UDP穿透NAT的原理与实现(附源代码)
作者:shootingstars (有容乃大,无欲则刚) 日期:2004-5-25 出处:P2P中国(PPcn.net) P2P 之 UDP穿透NAT的原理与实现(附源代码)原创:shootings ...
- [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)
NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...
- [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现
转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...
- 转:【专题七】UDP编程补充——UDP广播程序的实现
上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享下,本专题主要介绍下如何实现UDP广播的程序,下面就直接介绍实现过程和代码以及运行的结果. 一.程序实现 UDP广播程序的实现代 ...
- 专题七:UDP编程补充——UDP广播程序的实现
一.程序实现 UDP广播程序的实现代码: using System; using System.Net; using System.Net.Sockets; using System.Text; us ...
- 死磕以太坊源码分析之p2p节点发现
死磕以太坊源码分析之p2p节点发现 在阅读节点发现源码之前必须要理解kadmilia算法,可以参考:KAD算法详解. 节点发现概述 节点发现,使本地节点得知其他节点的信息,进而加入到p2p网络中. 以 ...
随机推荐
- PAT甲题题解-1035. Password (20)-水
题意:给n个用户名和密码,把密码中的1改为@,0改为%,l改为L,O改为o. 让你输出需要修改密码的用户名个数,以及对应的用户名和密码,按输入的顺序.如果没有用户需要修改,则输出对应的语句,注意单复数 ...
- thinkphp在wamp 配置去掉url中index.php方法
http://blog.csdn.net/youmypig/article/details/45008971
- LeetCode 141. Linked List Cycle环形链表 (C++)
题目: Given a linked list, determine if it has a cycle in it. To represent a cycle in the given linked ...
- 《Linux内核分析》第三周学习小结 构造一个简单的Linux系统OS
郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第三周 构造一个简单的Linux系统Me ...
- HTML5遇到的问题
一.Uncaught SyntaxError: Unexpected identifier 解决办法: Uncaught SyntaxError: Unexpected identifier这个问题, ...
- C/C+ 感触
1. C/C++语言开发的首选利器- C++Test 以前在windows平台下的开发,使用的框架主要是MFC,以及console工程(基于win32SDK),属于纯C/C++ ...
- Eat Style --proposed by Chongyang Bai
NEED 1. 有人希望妈妈是这样的: 但实际上对妈妈做的菜反应确是这样的: 处在不同的时节,根据不同的个人偏好,到底该做些什么饭菜?工作繁忙,家里的厨师可能也没时间琢磨.最后做出的只是应付差事的饭菜 ...
- beta 圆桌 6
031602111 傅海涛 1.今天进展 接口微调修正 2.存在问题 文档转化太久 3.明天安排 完成全部接口的交互 4.心得体会 文档转化需要好好优化 031602115 黄家雄 1.界面优化 2. ...
- PerfMon Metrics Collector插件的Disks I/O使用总结
做Jmeter测试的时候如果想要统计磁盘的IO读写速度,那么很容易想到用PerfMon Metrics Collector这个插件,但是具体使用过程中还有以下需要注意的. 1.如下图首先,需要选择监控 ...
- C语言入门:02.第一个C语言程序
一.开发工具的选择(1)可以用来写代码的工具:记事本.UltraEdit.Vim.Xcode等(2)选择Xcode的原因:苹果官方提供的开发利器.简化开发过程.有高亮显示功能 (3)使用Xcode新建 ...