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

  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. webug4.0安装

    官网:https://www.webug.org/ 官方版本里安装视频教程 4.26 官网打不开,分享当初存在网盘的 链接:https://pan.baidu.com/s/13rG0TLwuA3Ro0 ...

  2. 《Linux内核分析》第二周笔记 操作系统是如何工作的

    操作系统是如何工作的 一.函数调用堆栈 1.三个法宝 计算机是如何工作的?(总结)——三个法宝(存储程序计算机.函数调用堆栈.中断机制) 1)存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: ...

  3. BaseServlet 继承 httpServlet

    BaseServlet   核心 package cn.core; import java.io.IOException; import java.lang.reflect.Method; impor ...

  4. 带状态论文粗读(三)[引用openstate的相关论文阅读]

    一 文章名称:FLOWGUARD: Building Robust Firewalls for Software-Defined Networks 发表时间:2014 期刊来源:--- 解决问题: 一 ...

  5. [Cyan之旅]使用NPOI实现Excel的导入导出,踩坑若干.

    Cyan是博主[Soar360]自2014年以来开始编写整理的工具组件,用于解决现实工作中常用且与业务逻辑无关的问题. 什么是NPOI? NPOI 是 POI 项目的 .NET 版本.POI是一个开源 ...

  6. MarkDown to PDF

    前面随便说说 之前在 windows 上一直习惯用 cmdmarkdown 把要写的东西写下来,再通过 typora 转成 pdf:现在很多时候在用 Ubuntu,需要把写完的作业转成 pdf 交上去 ...

  7. wifi 标准

    简介 https://smb.pconline.com.cn/1149/11491365.html

  8. PSP(3.23——3.29)以及周记录

    3.23 9:30 10:30 15 45 Android Studio 界面设计学习 A Y min 13:00 13:15 0 15 站立会议 A Y min 23:20 23:45 0 25 英 ...

  9. DataGridView列标题居中,内容居中

    //列标题居中 dataGridView1.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleC ...

  10. sql server 小技巧 集锦

    sql server 小技巧(1) 导入csv数据到sql server sql server 小技巧(2) 删除sql server中重复的数据 sql server 小技巧(3) SQL Serv ...