CLR事件机制建立在委托机制上
class MailManager
{
public class MailEventArgs : EventArgs
{
public MailEventArgs(
String from,
String to,
String subject,
String body)
{
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
}
public readonly String from, to, subject, body;
}
public delegate void MailEventHandler(Object sender, MailEventArgs e);
public event MailEventHandler MailMsg;
protected virtual void OnMailMsg(MailEventArgs e)
{
if (MailMsg != null)
{
MailMsg(this, e);
}
}
public virtual void SimulateArrivingMsg(String from, String to, String subject, String body)
{
MailEventArgs e = new MailEventArgs(from, to, subject, body);
OnMailMsg(e);
}
}
以上是一个事件类型定义,简单步骤:
1.定义一个类型,保存所有传递参数。
.net框架规定所有的信息类型继承于EventArgs,类型名称以EventArgs结束,EventArgs原型如下:
[Serializable]
public class EventArgs
{
public static readonly EventArgs Empty = new EventArgs();
public EventArgs(){};
}
只有一个静态的Empty成员,因为事件中有些不需要额外的参数信息,所以只要提供EventArgs.Empty,不需要在构造新的对象
2.定义一个委托类型,指定事件触发时调用的函数原型
public delegate void MailEventHandler(Object sender, MailEventArgs e);
如果事件中并没有额外的传递信息,则可以使用System.EventHandler,原型为
public delegate void EventHandler(Object sender,EventArgs e);
3.定义一个事件成员
public event MailEventHandler MailMsg;
4.定义受保护的虚方法,通知事件登记对象
protected virtual void OnMailMsg(MailEventArgs e);
5.定义驱动事件方法
public virtual void SimulateArrivingMsg(String from, String to, String subject, String body);
深入理解事件
当编译器遇到public event MailEventHandler MailMsg;
会产生3个构造,一个私有委托类型,及add和remove方法
private MailEventHandler MailMsg;
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public virtual void add_MailMsg(MailEventHandler handler)
{
MailMsg = (MailEventHandler)Delegate.Combine(MailMsg, handler);
}
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public virtual void remove_MailMsg(MailEventHandler handler)
{
MailMsg = (MailEventHandler)Delegate.Remove(MailMsg, handler);
}
通过MailMsg内_prev字段可以将添加的注册对象保存
侦听事件
class Fax
{
public Fax(MailManager mm)
{
mm.MailMsg += new MailManager.MailEventHandler(FaxMsg);
}
private void FaxMsg(Object sender, MailManager.MailEventArgs e)
{
}
public void UnRegister(MailManager mm)
{
MailManager.MailEventHandler callback = new MailManager.MailEventHandler(FaxMsg);
mm.MailMsg -= callback;
}
}
注意,当一个对象仍然登记有另外一个对象的事件,该对象就不可能对垃圾回收,如果我们类实现了IDisposable接口,那我们应该在
Dispose理注销所有登记事件
显示控制事件注册
private MailEventHandler mailMsgEventHandlerDelegate;
public event MailEventHandler MailMsg
{
add
{
mailMsgEventHandlerDelegate =(MailEventHandler)Delegate.Combine(mailMsgEventHandlerDelegate, value);
}
remove
{
mailMsgEventHandlerDelegate = (MailEventHandler)Delegate.Remove(mailMsgEventHandlerDelegate, value);
}
}
protected virtual void OnMailMsg(MailEventArgs e)
{
if (MailMsg != null)
{
mailMsgEventHandlerDelegate(this, e);
}
}
来代替public event MailEventHandler MailMsg;定义
当一个类型中含有多个事件时,由于每个事件事例声明都将产生委托字段,所以推荐 不要提供公共的事件成员变量,使用事件访问器替换这些变量
class EventHandlerSet:IDisposable
{
private Hashtable events = new Hashtable();
public virtual Delegate this[Object eventKey]
{
get
{
return (Delegate)events[eventKey];
}
set
{
events[eventKey] = value;
}
}
public virtual void AddHandler(Object eventKey,Delegate handler)
{
events[eventKey] = Delegate.Combine((Delegate)events[eventKey], handler);
}
public virtual void RevHandler(Object eventKey, Delegate handler)
{
events[eventKey] = Delegate.Remove((Delegate)events[eventKey], handler);
}
public virtual void Fire(Object eventKey, Object sender, EventArgs e)
{
Delegate d = (Delegate)events[eventKey];
if (d != null)
d.DynamicInvoke(new Object[]{sender,e});
}
public void Dispose()
{
events = null;
}
public static EventHandlerSet Synchronized(EventHandlerSet eventHandlerSet)
{
if (eventHandlerSet == null)
throw new ArgumentNullException("eventHandlerSet");
return new SynchronizedEventHandlerSet(eventHandlerSet);
}
public class SynchronizedEventHandlerSet : EventHandlerSet
{
private EventHandlerSet eventHandlerSet;
public SynchronizedEventHandlerSet(EventHandlerSet eventHandlerSet)
{
this.eventHandlerSet = eventHandlerSet;
Dispose();
}
public override Delegate this[object eventKey]
{
[MethodImpl(MethodImplOptions.Synchronized)]
get
{
return eventHandlerSet[eventKey];
}
set
{
eventHandlerSet[eventKey] = value;
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void AddHandler(object eventKey, Delegate handler)
{
eventHandlerSet.AddHandler(eventKey, handler);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void RevHandler(object eventKey, Delegate handler)
{
eventHandlerSet.RevHandler(eventKey, handler);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void Fire(object eventKey, object sender, EventArgs e)
{
eventHandlerSet.Fire(eventKey, sender, e);
}
}
}
class TypeWithLotsEvents
{
protected EventHandlerSet eventSet = EventHandlerSet.Synchronized(new EventHandlerSet());
protected static readonly Object fooEventKey = new Object();
public class FooEventArgs : EventArgs { };
public delegate void FooEventHandler(Object sender, FooEventArgs e);
public event FooEventHandler Foo
{
add
{
eventSet.AddHandler(fooEventKey, value);
}
remove
{
eventSet.AddHandler(fooEventKey, value);
}
}
protected virtual void OnFoo(FooEventArgs e)
{
eventSet.Fire(fooEventKey, this, e);
}
public void SimulateFoo()
{
OnFoo(new FooEventArgs());
}
}
以上是microsoft .net 框架设计一个example,如果设计该种事件类型,其中给出了EventHandlerSet的实现,
.net框架中也有类似的事件 EventHandlerList,但是访问效率和线程同步有缺陷,所以有需要我们自己提供
EventHandlerSet实现.详情请参考microsoft .net 框架设计
posted @
2010-09-30 14:17 小果子 阅读(418) |
评论 (0) |
编辑 收藏
.net框架中,c#为回调函数提供了委托的类型安全机制,下面是声明,创建和使用
namespace CSharpDotNet
{
class Set
{
public Set(Int32 numberItems)
{
items = new Object[numberItems];
for (Int32 i = 0; i < numberItems; i++)
items[i] = i;
}
private Object[] items;
public delegate void FeedBack(Object value, Int32 item, Int32 numberItems);
/*public FeedBack:System.MulticastDelegate{
public FeedBack(Object target,Int32 methodPtr);
public virtual void Invoke(Object value, Int32 item, Int32 numberItems);
public virtual IAsyncResult BeginInvoke(Object value, Int32 item, Int32 numberItems,
AsyncCallback callback,Object object);
public virtual IAsyncResult EndInvoke(IAsyncResult result);
}*/
public void ProcessItems(FeedBack feedback)
{
for (Int32 i = 0; i < items.Length; i++)
{
if (feedback != null)
{
feedback(items[i], i + 1, items.Length);
}
}
}
}
class Program
{
static void StaticCallbacks()
{
Set setOfItems = new Set(5);
setOfItems.ProcessItems(new Set.FeedBack(Program.FeedBackToConsole));
}
static void FeedBackToConsole(Object value, Int32 item, Int32 numberItems)
{
Console.WriteLine("{0},{1},{2}", value, item, numberItems);
}
static void InstanceCallbacks()
{
Set setOfItems = new Set(5);
Program p = new Program();
setOfItems.ProcessItems(new Set.FeedBack(p.FeedBackToMsg));
}
private void FeedBackToMsg(Object value, Int32 item, Int32 numberItems)
{
Console.WriteLine("msg");
}
static void Main(string[] args)
{
StaticCallbacks();
InstanceCallbacks();
}
}
}
上例显示了使用委托如何静态回调和非静态回调方法,当声明
public delegate void FeedBack(Object value, Int32 item, Int32 numberItems);
微软编译器为其产生如下定义:
public FeedBack:System.MulticastDelegate{
public FeedBack(Object target,Int32 methodPtr);
public virtual void Invoke(Object value, Int32 item, Int32 numberItems);
public virtual IAsyncResult BeginInvoke(Object value, Int32 item, Int32 numberItems,
AsyncCallback callback,Object object);
public virtual IAsyncResult EndInvoke(IAsyncResult result);
}
因为委托声明为public,所以会产生public类,可以在任何类定义的地方声明委托,委托本质是一个类,因为委托继承System.MulticastDelegate,
所以会继承其相应字段:
_target,_methodPtr,_prev.
public FeedBack(Object target,Int32 methodPtr);构造函数包含两个参数,target和methodPtr,一个对象引用和一个指向回调函数的整数,
但是我们构造的时候只是给了Program.FeedBackToConsole这样的值,其实是编译器为我们做了工作,它知道我们在构造委托,它会分析源代码知道我们
引用的是哪个对象和哪个方法
当委托调用的时候feedback(items[i], i + 1, items.Length);实质是feedback.Invoke(items[i], i + 1, items.Length);不过c#不允许
显示调用该方法,当invoke调用的时候,它使用_target,_methodPtr来指定对象调用的方法,invoke方法的签名和声明的委托签名一致
System.MulticastDelegate System.Delegate,前者继承与后者,微软编译器产生的委托都是继承与System.MulticastDelegate,但是我们有些时候
会遇到System.Delegate,System.Delegate提供了两个静态方法,Combine和Remove,其参数都是Delegate类型,所以我们可以传递MulticastDelegate
给它
关于委托的判等
Delegate重写了Object的Equals方法,如果_target和_methodPtr是否指向同样的对象和方法,返回true
MulticastDelegate重写了Delegate的Equals方法,在delegate之上,还要比较_prev
委托链
MulticastDelegate的_prev保存了下一个委托的应用,使得多个委托对象可以组成一个链表
Delegate定义了三个静态方法:
public static Delegate Combine(Delegate tail,Delegate head);
public static Delegate Combine(Delegate[] delegateArray);
public static Delegate Remove(Delegate source,Delegate value);
class FeedBack:MulticastDelegate{
public void virtual Invoke(Object value, Int32 item, Int32 numberItems){
if(_prev!=null)_prev.Invoke(value,item,numberItems);
_target.methodPtr(value,item,numberItems);
}
}
可以看出,当委托链调用的时候,如果回调函数有返回值,将只保留最后一个委托调用的返回值,而且链表尾部的
委托先调用,递归调用
c#中重载了-=,+=,可以方便实现委托链的操作,其实质是调用了以上三个静态函数实现,同时为了增加对委托的控制,
MulticastDelegate提供了
public virtual Delegate[] GetInvocationList();
返回委托链的数组,可以操控里面的每个委托对象。
(具体请参考Microsoft.Net 框架设计)
posted @
2010-09-29 17:24 小果子 阅读(317) |
评论 (0) |
编辑 收藏
安装Visual Studio
2005,2008都三年了,从来都没遇到过什么问题,安装过程都很顺利。今天给公司的新电脑装系统,自带了一个正版的Win
XP家庭版,安装VS的时候出错,1330错误:某个文件数字签名不可用。于是换了张光盘,结果还是同样的结果。。。于是换系统,先装Win2003,最
后到Win2008,都逃不过这个问题,而且数字签名不可用的文件每次都是随机的,有时是第2个文件,有时是第60个文件。找来师哥们帮忙,他们也没遇到
过这么怪的问题。后来在Google上搜索"Visual Studio
1330",找了很久,微软MSDN版主的解释是文件损坏,重新下载安装。(PS:公司里的Visual
Studio光盘是MSDN寄来的光盘,有20多张DVD,包括所有语言的版本)。还有的是让把光盘里的文件复制到硬盘安装。。。。测试无效。
Finally,通过Google在一个老外的博客上找到了解决方法:在注册表中,把原HKCU\Software\Microsoft\Windows
\CurrentVersion\WinTrust\Trust
Providers\Software Publishing\State 的值由 0x23c00 改为
0x22800。关闭文件数字签名验证。VS顺利安装!
下面附原博客内容:
FIX: Error 1330 - Installing Visual Studio 2008 on Windows Server 2008
VPC
OK, I have run into SO many bloody 1330 errors while installing Visual
Studio 2008 on a Windows Server 2008 VPC. Here is the fix I ran across
on Heath Stewart's blog.
On my host, I installed the Windows SDK for Windows Server 2008 and .NET
Framework 3.5.
I then copied SetReg to my VPC image from my host's C:\Program
Files\Microsoft SDKs\Windows\v6.1\Bin.
I then updated my SetReg settings on the VPC as is mentioned in Heath's
article.
Software Publishing State Key Values (0x22800):
1) Trust the Test Root........................... FALSE
2) Use expiration date on certificates........... TRUE
3) Check the revocation list..................... TRUE
4) Offline revocation server OK (Individual)..... FALSE
5) Offline revocation server OK (Commercial)..... TRUE
6) Java offline revocation server OK (Individual) FALSE
7) Java offline revocation server OK (Commercial) TRUE
8) Invalidate version 1 signed objects........... FALSE
9) Check the revocation list on Time Stamp Signer FALSE
10) Only trust items found in the Trust DB........ FALSE
Reran the VS2008 install and PRESTO! it worked w/ no Error 1330s.
FWIW, I am writing this down here on my blog so I have it
semi-permanently for future such mishaps. :-)
posted @
2010-09-26 09:00 小果子 阅读(784) |
评论 (0) |
编辑 收藏
template<class T>
class Singleton{
public:
static T* getInstance(){
if(ptr==NULL){
ptr=new T();//(T*)(::operator new(sizeof(T)));
}
return ptr;
}
private:
Singleton(){};
static T* ptr;
};
template<typename T>
T* Singleton<T>::ptr=0;
class C{
public:
int x;
C(){
x=0;
}
~C(){
cout<<"C delete"<<endl;
}
};
int main(){
C* c=Singleton<C>::getInstance();
C* d=Singleton<C>::getInstance();
cout<<c<<" "<<d<<endl;
}
当时一时没反应过来,用的较多的还是实例化的单体类.这里笔记一下。不过模板类有其自己方便的地方。所以还是有必要的.
posted @
2010-09-10 10:30 小果子 阅读(1000) |
评论 (0) |
编辑 收藏
UDP用打洞技术穿透NAT的原理与实现
收藏
首先先介绍一些基本概念:
NAT(Network Address
Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。NAT分为两大类,基本的NAT和NAPT(Network
Address/Port Translator)。
最开始NAT是运行在路由器上的一个功能模块。
最先提出的是基本的NAT,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪90年代中期提出
的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。
因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的
IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP
包中的原IP地址,但是不会改变IP包中的端口)
关于基本的NAT可以参看RFC 1631
另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的
TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵,我没有见到过),NAPT才是我们真正讨论的主角。看下图:
Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网络10.*.*.*,Client
A是其中的一台计算机,这个网络的网关(一个NAT设备)的外网IP是155.99.25.11(应该还有一个内网的IP地址,比如10.0.0.10)。如果Client
A中的某个进程(这个进程创建了一个UDP
Socket,这个Socket绑定1234端口)想访问外网主机18.181.0.31的1235端口,那么当数据包通过NAT时会发生什么事情呢?
首先NAT会改变这个数据包的原IP地址,改为155.99.25.11。接着NAT会为这个传输创建一个Session(Session是一个抽象的概
念,如果是TCP,也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的第一个UDP开始,结束呢,呵呵,
也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个Session分配一个端口,比如62000,然后改变这个数据包的源端口为62000。所
以本来是(10.0.0.1:1234->18.181.0.31:1235)的数据包到了互联网上变为了
(155.99.25.11:62000->18.181.0.31:1235)。
一旦NAT创建了一个Session后,NAT会记住62000端口对应的是10.0.0.1的1234端口,以后从18.181.0.31发送到
62000端口的数据会被NAT自动的转发到10.0.0.1上。(注意:这里是说18.181.0.31发送到62000端口的数据会被转发,其他的
IP发送到这个端口的数据将被NAT抛弃)这样Client
A就与Server S1建立以了一个连接。
呵呵,上面的基础知识可能很多人都知道了,那么下面是关键的部分了。
看看下面的情况:
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个Server
S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?
这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一种是
NAT再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric
NAT,后一种叫做Cone
NAT。我们期望我们的NAT是第二种,呵呵,如果你的NAT刚好是第一种,那么很可能会有很多P2P软件失灵。(可以庆幸的是,现在绝大多数的NAT属于后者,即Cone
NAT)
好了,我们看到,通过NAT,子网内的计算机向外连结是很容易的(NAT相当于透明的,子网内的和外网的计算机不用知道NAT的情况)。
但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的)。
那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?首先,我们必须在内网的NAT上打上一个“洞”(也就是前面我们说的在NAT上建立一个
Session),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比如:192.168.0.10)向外部
的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个方向为219.237.60.1的“洞”,(这
就是称为UDP
Hole
Punching的技术)以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。(但是其他的IP不能利用这个洞)。
呵呵,现在该轮到我们的正题P2P了。有了上面的理论,实现两个内网的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接
请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一个中间人来联系这两个内网主机。
现在我们来看看一个P2P软件的流程,以下图为例:
Server S (219.237.60.1)
|
|
+----------------------+----------------------+
| |
NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56)
| (内网IP:192.168.0.1) | (内网IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)
首先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S收到的Client
A的地址是202.187.45.3:60000,这就是Client A的外网地址了。同样,Client B登录Server S,NAT
B给此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
此时,Client A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client
B,那么他可以从Server S那儿获得B的公网地址187.34.1.56:40000,是不是Client
A向这个地址发送信息Client B就能收到了呢?答案是不行,因为如果这样发送信息,NAT
B会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数NAT都会执行丢弃动作)。现在我们需要的是在NAT
B上打一个方向为202.187.45.3(即Client A的外网地址)的洞,那么Client
A发送到187.34.1.56:40000的信息,Client B就能收到了。这个打洞命令由谁来发呢,呵呵,当然是Server S。
总结一下这个过程:如果Client A想向Client B发送信息,那么Client A发送命令给Server S,请求Server
S命令Client B向Client
A方向打洞。呵呵,是不是很绕口,不过没关系,想一想就很清楚了,何况还有源代码呢(侯老师说过:在源代码面前没有秘密
8)),然后Client A就可以通过Client B的外网地址与Client B通信了。
注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B向Client
A打洞的端口已经重新分配了,Client B将无法知道这个端口(如果Symmetric
NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,我们不推荐这种猜测端口的方法)。
另一篇文章接上:
下面解释一下上面的文章中没有提及或者说我觉得比较欠缺的地方.
私有地址/端口和公有地址/端口:我们知道,现在大部分网络采用的都是NAPT(Network Address/Port Translator)了,
这个东东的作用是一个对外的对话在经过NAT之后IP地址和端口号都会被改写,在这里把一次会话中客户自己认为在使用的IP地址和端口号成为私有地址/端
口,而把经过NAPT之后被改写的IP地址和端口号称为公有地址/端口.或者可以这么理解,私有地址/端口是你家里人对你的昵称而公有地址/端口则是你真
正对外公开的名字.如何获得用户的私用地址/端口号,这个很简单了,而要得到公有地址/端口号就要在连接上另一台机器之后由那台机器看到的IP地址和端口
号来表示.
如果明白了上面的东西,下面进入我们的代码,在这里解释一下关键部分的实现:
客户端首先得到自己的私有地址/终端,然后向server端发送登陆请求,server端在得到这个请求之后就可以知道这个client端的公有地址/终
端,server会为每一个登陆的client保存它们的私有地址/端口和公有地址/端口.
OK,下面开始关键的打洞流程.假设client A要向client B对话,但是A不知道B的地址,即使知道根据NAT的原理这个对话在第一次会被拒
绝,因为client B的NAT认为这是一个从没有过的外部发来的请求.这个时候,A如果发现自己没有保存B的地址,或者说发送给B的会话请求失败了,
它会要求server端让B向A打一个洞,这个B->A的会话意义在于它使NAT B认为A的地址/端口是可以通过的地址/端口,这样A再向B发送
对话的时候就不会再被NAT B拒绝了.打一个比方来说明打洞的过程,A想来B家做客,但是遭到了B的管家NAT B的拒绝,理由是:我从来没有听我家B
提过你的名字,这时A找到了A,B都认识的朋友server,要求server给B报一个信,让B去跟管家说A是我的朋友,于是,B跟管家NAT B
说,A是我认识的朋友,这样A的访问请求就不会再被管家NAT B所拒绝了.简而言之,UDP打洞就是一个通过server保存下来的地址使得彼此之间能
够直接通信的过程,server只管帮助建立连接,在建立间接之后就不再介入了.
下面是一个模拟P2P聊天的过程的源代码,过程很简单,P2PServer运行在一个拥有公网IP的计算机上,P2PClient运行在两个不同的NAT
后(注意,如果两个客户端运行在一个NAT后,本程序很可能不能运行正常,这取决于你的NAT是否支持loopback
translation,详见http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,当然,此问题可以通过双方先尝试连接对方的内网IP来解决,但是这个代码只是为了验证原理,并没有处理这些问题),后登录的计算机可以获得先登录计算机的用户名,后登录的计算机通过send
username message的格式来发送消息。如果发送成功,说明你已取得了直接与对方连接的成功。
程序现在支持三个命令:send , getu , exit
send格式:send username message
功能:发送信息给username
getu格式:getu
功能:获得当前服务器用户列表
exit格式:exit
功能:注销与服务器的连接(服务器不会自动监测客户是否吊线)
代码很短,相信很容易懂,如果有什么问题,可以给我发邮件zhouhuis22@sina.com
或者在CSDN上发送短消息。同时,欢迎转发此文,但希望保留作者版权8-)。
_05/04052509317298.rar"
http://www.ppcn.net/upload/2004_05/04052509317298.rar
另一篇介绍打洞技术的(补充)
UDP打洞技术依赖于由公共防火墙和cone
NAT,允许适当的有计划的端对端应用程序通过NAT"打洞",即使当双方的主机都处于NAT之后。这种技术在 RFC3027的5.1节[NAT
PROT]
中进行了重点介绍,并且在Internet[KEGEL]中进行了非正式的描叙,还应用到了最新的一些协议,例如[TEREDO,ICE]协议中。不过,
我们要注意的是,"术"如其名,UDP打洞技术的可靠性全都要依赖于UDP。
这里将考虑两种典型场景,来介绍连接的双方应用程序如何按照计划的进行通信的,第一种场景,我们假设两个客户端都处于不同的NAT之后;第二种场景,我们假设两个客户端都处于同一个NAT之后,但是它们彼此都不知道(他们在同一个NAT中)。
处于不同NAT之后的客户端通信
我们假设 Client A 和 Client B 都拥有自己的私有IP地址,并且都处在不同的NAT之后,端对端的程序运行于 CLIENT
A,CLIENT B,S之间,并且它们都开放了UDP端口1234。 CLIENT A和CLIENT B首先分别与S建立通信会话,这时NAT
A把它自己的UDP端口62000分配给CLIENT A与S的会话,NAT B也把自己的UDP端口31000分配给CLIENT B与S的会话。
假
如这个时候 CLIENT A 想与 CLIENT B建立一条UDP通信直连,如果 CLIENT A只是简单的发送一个UDP信息到CLIENT
B的公网地址138.76.29.7:31000的话,NAT B会不加考虑的将这个信息丢弃(除非NAT B是一个 full cone
NAT),因为 这个UDP信息中所包含的地址信息,与CLIENT B和服务器S建立连接时存储在NAT
B中的服务器S的地址信息不符。同样的,CLIENT B如果做同样的事情,发送的UDP信息也会被 NAT A 丢弃。
假如
CLIENT A 开始发送一个 UDP 信息到 CLIENT B 的公网地址上,与此同时,他又通过S中转发送了一个邀请信息给CLIENT
B,请求CLIENT B也给CLIENT A发送一个UDP信息到 CLIENT A的公网地址上。这时CLIENT A向CLIENT
B的公网IP(138.76.29.7:31000)发送的信息导致 NAT A 打开一个处于 CLIENT A的私有地址和CLIENT
B的公网地址之间的新的通信会话,与此同时,NAT B 也打开了一个处于CLIENT B的私有地址和CLIENT
A的公网地址(155.99.25.11:62000)之间的新的通信会话。一旦这个新的UDP会话各自向对方打开了,CLIENT A和CLIENT
B之间就可以直接通信,而无需S来牵线搭桥了。(这就是所谓的打洞技术)!
posted @
2010-09-01 19:08 小果子 阅读(1756) |
评论 (0) |
编辑 收藏