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.定義一個類型,保存所有傳遞參數(shù)。
.net框架規(guī)定所有的信息類型繼承于EventArgs,類型名稱以EventArgs結(jié)束,EventArgs原型如下:
[Serializable]
public class EventArgs
{
public static readonly EventArgs Empty = new EventArgs();
public EventArgs(){};
}
只有一個靜態(tài)的Empty成員,因為事件中有些不需要額外的參數(shù)信息,所以只要提供EventArgs.Empty,不需要在構(gòu)造新的對象
2.定義一個委托類型,指定事件觸發(fā)時調(diào)用的函數(shù)原型
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.定義驅(qū)動事件方法
public virtual void SimulateArrivingMsg(String from, String to, String subject, String body);
深入理解事件
當編譯器遇到public event MailEventHandler MailMsg;
會產(chǎn)生3個構(gòu)造,一個私有委托類型,及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內(nèi)_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;
}
}
注意,當一個對象仍然登記有另外一個對象的事件,該對象就不可能對垃圾回收,如果我們類實現(xiàn)了IDisposable接口,那我們應(yīng)該在
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;定義
當一個類型中含有多個事件時,由于每個事件事例聲明都將產(chǎn)生委托字段,所以推薦 不要提供公共的事件成員變量,使用事件訪問器替換這些變量
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 框架設(shè)計一個example,如果設(shè)計該種事件類型,其中給出了EventHandlerSet的實現(xiàn),
.net框架中也有類似的事件 EventHandlerList,但是訪問效率和線程同步有缺陷,所以有需要我們自己提供
EventHandlerSet實現(xiàn).詳情請參考microsoft .net 框架設(shè)計
posted @
2010-09-30 14:17 小果子 閱讀(426) |
評論 (0) |
編輯 收藏
.net框架中,c#為回調(diào)函數(shù)提供了委托的類型安全機制,下面是聲明,創(chuàng)建和使用
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();
}
}
}
上例顯示了使用委托如何靜態(tài)回調(diào)和非靜態(tài)回調(diào)方法,當聲明
public delegate void FeedBack(Object value, Int32 item, Int32 numberItems);
微軟編譯器為其產(chǎn)生如下定義:
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,所以會產(chǎn)生public類,可以在任何類定義的地方聲明委托,委托本質(zhì)是一個類,因為委托繼承System.MulticastDelegate,
所以會繼承其相應(yīng)字段:
_target,_methodPtr,_prev.
public FeedBack(Object target,Int32 methodPtr);構(gòu)造函數(shù)包含兩個參數(shù),target和methodPtr,一個對象引用和一個指向回調(diào)函數(shù)的整數(shù),
但是我們構(gòu)造的時候只是給了Program.FeedBackToConsole這樣的值,其實是編譯器為我們做了工作,它知道我們在構(gòu)造委托,它會分析源代碼知道我們
引用的是哪個對象和哪個方法
當委托調(diào)用的時候feedback(items[i], i + 1, items.Length);實質(zhì)是feedback.Invoke(items[i], i + 1, items.Length);不過c#不允許
顯示調(diào)用該方法,當invoke調(diào)用的時候,它使用_target,_methodPtr來指定對象調(diào)用的方法,invoke方法的簽名和聲明的委托簽名一致
System.MulticastDelegate System.Delegate,前者繼承與后者,微軟編譯器產(chǎn)生的委托都是繼承與System.MulticastDelegate,但是我們有些時候
會遇到System.Delegate,System.Delegate提供了兩個靜態(tài)方法,Combine和Remove,其參數(shù)都是Delegate類型,所以我們可以傳遞MulticastDelegate
給它
關(guān)于委托的判等
Delegate重寫了Object的Equals方法,如果_target和_methodPtr是否指向同樣的對象和方法,返回true
MulticastDelegate重寫了Delegate的Equals方法,在delegate之上,還要比較_prev
委托鏈
MulticastDelegate的_prev保存了下一個委托的應(yīng)用,使得多個委托對象可以組成一個鏈表
Delegate定義了三個靜態(tài)方法:
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);
}
}
可以看出,當委托鏈調(diào)用的時候,如果回調(diào)函數(shù)有返回值,將只保留最后一個委托調(diào)用的返回值,而且鏈表尾部的
委托先調(diào)用,遞歸調(diào)用
c#中重載了-=,+=,可以方便實現(xiàn)委托鏈的操作,其實質(zhì)是調(diào)用了以上三個靜態(tài)函數(shù)實現(xiàn),同時為了增加對委托的控制,
MulticastDelegate提供了
public virtual Delegate[] GetInvocationList();
返回委托鏈的數(shù)組,可以操控里面的每個委托對象。
(具體請參考Microsoft.Net 框架設(shè)計)
posted @
2010-09-29 17:24 小果子 閱讀(326) |
評論 (0) |
編輯 收藏
安裝Visual Studio
2005,2008都三年了,從來都沒遇到過什么問題,安裝過程都很順利。今天給公司的新電腦裝系統(tǒng),自帶了一個正版的Win
XP家庭版,安裝VS的時候出錯,1330錯誤:某個文件數(shù)字簽名不可用。于是換了張光盤,結(jié)果還是同樣的結(jié)果。。。于是換系統(tǒng),先裝Win2003,最
后到Win2008,都逃不過這個問題,而且數(shù)字簽名不可用的文件每次都是隨機的,有時是第2個文件,有時是第60個文件。找來師哥們幫忙,他們也沒遇到
過這么怪的問題。后來在Google上搜索"Visual Studio
1330",找了很久,微軟MSDN版主的解釋是文件損壞,重新下載安裝。(PS:公司里的Visual
Studio光盤是MSDN寄來的光盤,有20多張DVD,包括所有語言的版本)。還有的是讓把光盤里的文件復(fù)制到硬盤安裝。。。。測試無效。
Finally,通過Google在一個老外的博客上找到了解決方法:在注冊表中,把原HKCU\Software\Microsoft\Windows
\CurrentVersion\WinTrust\Trust
Providers\Software Publishing\State 的值由 0x23c00 改為
0x22800。關(guān)閉文件數(shù)字簽名驗證。VS順利安裝!
下面附原博客內(nèi)容:
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 小果子 閱讀(801) |
評論 (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;
}
當時一時沒反應(yīng)過來,用的較多的還是實例化的單體類.這里筆記一下。不過模板類有其自己方便的地方。所以還是有必要的.
posted @
2010-09-10 10:30 小果子 閱讀(1006) |
評論 (0) |
編輯 收藏
UDP用打洞技術(shù)穿透NAT的原理與實現(xiàn)
收藏
首先先介紹一些基本概念:
NAT(Network Address
Translators),網(wǎng)絡(luò)地址轉(zhuǎn)換:網(wǎng)絡(luò)地址轉(zhuǎn)換是在IP地址日益缺乏的情況下產(chǎn)生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network
Address/Port Translator)。
最開始NAT是運行在路由器上的一個功能模塊。
最先提出的是基本的NAT,它的產(chǎn)生基于如下事實:一個私有網(wǎng)絡(luò)(域)中的節(jié)點中只有很少的節(jié)點需要與外網(wǎng)連接(呵呵,這是在上世紀90年代中期提出
的)。那么這個子網(wǎng)中其實只有少數(shù)的節(jié)點需要全球唯一的IP地址,其他的節(jié)點的IP地址應(yīng)該是可以重用的。
因此,基本的NAT實現(xiàn)的功能很簡單,在子網(wǎng)內(nèi)使用一個保留的IP子網(wǎng)段,這些IP對外是不可見的。子網(wǎng)內(nèi)只有少數(shù)一些IP地址可以對應(yīng)到真正全球唯一的
IP地址。如果這些節(jié)點需要訪問外部網(wǎng)絡(luò),那么基本NAT就負責將這個節(jié)點的子網(wǎng)內(nèi)IP轉(zhuǎn)化為一個全球唯一的IP然后發(fā)送出去。(基本的NAT會改變IP
包中的原IP地址,但是不會改變IP包中的端口)
關(guān)于基本的NAT可以參看RFC 1631
另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經(jīng)過這個NAT設(shè)備的IP數(shù)據(jù)報的IP地址,還會改變IP數(shù)據(jù)報的
TCP/UDP端口。基本NAT的設(shè)備可能我們見的不多(呵呵,我沒有見到過),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
有一個私有網(wǎng)絡(luò)10.*.*.*,Client
A是其中的一臺計算機,這個網(wǎng)絡(luò)的網(wǎng)關(guān)(一個NAT設(shè)備)的外網(wǎng)IP是155.99.25.11(應(yīng)該還有一個內(nèi)網(wǎng)的IP地址,比如10.0.0.10)。如果Client
A中的某個進程(這個進程創(chuàng)建了一個UDP
Socket,這個Socket綁定1234端口)想訪問外網(wǎng)主機18.181.0.31的1235端口,那么當數(shù)據(jù)包通過NAT時會發(fā)生什么事情呢?
首先NAT會改變這個數(shù)據(jù)包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創(chuàng)建一個Session(Session是一個抽象的概
念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結(jié)束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結(jié)束呢,呵呵,
也許是幾分鐘,也許是幾小時,這要看具體的實現(xiàn)了)并且給這個Session分配一個端口,比如62000,然后改變這個數(shù)據(jù)包的源端口為62000。所
以本來是(10.0.0.1:1234->18.181.0.31:1235)的數(shù)據(jù)包到了互聯(lián)網(wǎng)上變?yōu)榱?
(155.99.25.11:62000->18.181.0.31:1235)。
一旦NAT創(chuàng)建了一個Session后,NAT會記住62000端口對應(yīng)的是10.0.0.1的1234端口,以后從18.181.0.31發(fā)送到
62000端口的數(shù)據(jù)會被NAT自動的轉(zhuǎn)發(fā)到10.0.0.1上。(注意:這里是說18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會被轉(zhuǎn)發(fā),其他的
IP發(fā)送到這個端口的數(shù)據(jù)將被NAT拋棄)這樣Client
A就與Server S1建立以了一個連接。
呵呵,上面的基礎(chǔ)知識可能很多人都知道了,那么下面是關(guān)鍵的部分了。
看看下面的情況:
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發(fā)送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
這時可能會有兩種情況發(fā)生,一種是NAT再次創(chuàng)建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是
NAT再次創(chuàng)建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric
NAT,后一種叫做Cone
NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現(xiàn)在絕大多數(shù)的NAT屬于后者,即Cone
NAT)
好了,我們看到,通過NAT,子網(wǎng)內(nèi)的計算機向外連結(jié)是很容易的(NAT相當于透明的,子網(wǎng)內(nèi)的和外網(wǎng)的計算機不用知道NAT的情況)。
但是如果外部的計算機想訪問子網(wǎng)內(nèi)的計算機就比較困難了(而這正是P2P所需要的)。
那么我們?nèi)绻霃耐獠堪l(fā)送一個數(shù)據(jù)報給內(nèi)網(wǎng)的計算機有什么辦法呢?首先,我們必須在內(nèi)網(wǎng)的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個
Session),這個洞不能由外部來打,只能由內(nèi)網(wǎng)內(nèi)的主機來打。而且這個洞是有方向的,比如從內(nèi)部某臺主機(比如:192.168.0.10)向外部
的某個IP(比如:219.237.60.1)發(fā)送一個UDP包,那么就在這個內(nèi)網(wǎng)的NAT設(shè)備上打了一個方向為219.237.60.1的“洞”,(這
就是稱為UDP
Hole
Punching的技術(shù))以后219.237.60.1就可以通過這個洞與內(nèi)網(wǎng)的192.168.0.10聯(lián)系了。(但是其他的IP不能利用這個洞)。
呵呵,現(xiàn)在該輪到我們的正題P2P了。有了上面的理論,實現(xiàn)兩個內(nèi)網(wǎng)的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發(fā)出連接
請求,誰也不知道誰的公網(wǎng)地址,那我們?nèi)绾蝸泶蜻@個洞呢?我們需要一個中間人來聯(lián)系這兩個內(nèi)網(wǎng)主機。
現(xiàn)在我們來看看一個P2P軟件的流程,以下圖為例:
Server S (219.237.60.1)
|
|
+----------------------+----------------------+
| |
NAT A (外網(wǎng)IP:202.187.45.3) NAT B (外網(wǎng)IP:187.34.1.56)
| (內(nèi)網(wǎng)IP:192.168.0.1) | (內(nèi)網(wǎng)IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)
首先,Client A登錄服務(wù)器,NAT A為這次的Session分配了一個端口60000,那么Server S收到的Client
A的地址是202.187.45.3:60000,這就是Client A的外網(wǎng)地址了。同樣,Client B登錄Server S,NAT
B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發(fā)送信息給Client
B,那么他可以從Server S那兒獲得B的公網(wǎng)地址187.34.1.56:40000,是不是Client
A向這個地址發(fā)送信息Client B就能收到了呢?答案是不行,因為如果這樣發(fā)送信息,NAT
B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數(shù)NAT都會執(zhí)行丟棄動作)。現(xiàn)在我們需要的是在NAT
B上打一個方向為202.187.45.3(即Client A的外網(wǎng)地址)的洞,那么Client
A發(fā)送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發(fā)呢,呵呵,當然是Server S。
總結(jié)一下這個過程:如果Client A想向Client B發(fā)送信息,那么Client A發(fā)送命令給Server S,請求Server
S命令Client B向Client
A方向打洞。呵呵,是不是很繞口,不過沒關(guān)系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密
8)),然后Client A就可以通過Client B的外網(wǎng)地址與Client B通信了。
注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client
A打洞的端口已經(jīng)重新分配了,Client B將無法知道這個端口(如果Symmetric
NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導(dǎo)致失敗的因素太多,我們不推薦這種猜測端口的方法)。
另一篇文章接上:
下面解釋一下上面的文章中沒有提及或者說我覺得比較欠缺的地方.
私有地址/端口和公有地址/端口:我們知道,現(xiàn)在大部分網(wǎng)絡(luò)采用的都是NAPT(Network Address/Port Translator)了,
這個東東的作用是一個對外的對話在經(jīng)過NAT之后IP地址和端口號都會被改寫,在這里把一次會話中客戶自己認為在使用的IP地址和端口號成為私有地址/端
口,而把經(jīng)過NAPT之后被改寫的IP地址和端口號稱為公有地址/端口.或者可以這么理解,私有地址/端口是你家里人對你的昵稱而公有地址/端口則是你真
正對外公開的名字.如何獲得用戶的私用地址/端口號,這個很簡單了,而要得到公有地址/端口號就要在連接上另一臺機器之后由那臺機器看到的IP地址和端口
號來表示.
如果明白了上面的東西,下面進入我們的代碼,在這里解釋一下關(guān)鍵部分的實現(xiàn):
客戶端首先得到自己的私有地址/終端,然后向server端發(fā)送登陸請求,server端在得到這個請求之后就可以知道這個client端的公有地址/終
端,server會為每一個登陸的client保存它們的私有地址/端口和公有地址/端口.
OK,下面開始關(guān)鍵的打洞流程.假設(shè)client A要向client B對話,但是A不知道B的地址,即使知道根據(jù)NAT的原理這個對話在第一次會被拒
絕,因為client B的NAT認為這是一個從沒有過的外部發(fā)來的請求.這個時候,A如果發(fā)現(xiàn)自己沒有保存B的地址,或者說發(fā)送給B的會話請求失敗了,
它會要求server端讓B向A打一個洞,這個B->A的會話意義在于它使NAT B認為A的地址/端口是可以通過的地址/端口,這樣A再向B發(fā)送
對話的時候就不會再被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運行在一個擁有公網(wǎng)IP的計算機上,P2PClient運行在兩個不同的NAT
后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback
translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內(nèi)網(wǎng)IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send
username message的格式來發(fā)送消息。如果發(fā)送成功,說明你已取得了直接與對方連接的成功。
程序現(xiàn)在支持三個命令:send , getu , exit
send格式:send username message
功能:發(fā)送信息給username
getu格式:getu
功能:獲得當前服務(wù)器用戶列表
exit格式:exit
功能:注銷與服務(wù)器的連接(服務(wù)器不會自動監(jiān)測客戶是否吊線)
代碼很短,相信很容易懂,如果有什么問題,可以給我發(fā)郵件zhouhuis22@sina.com
或者在CSDN上發(fā)送短消息。同時,歡迎轉(zhuǎn)發(fā)此文,但希望保留作者版權(quán)8-)。
_05/04052509317298.rar"
http://www.ppcn.net/upload/2004_05/04052509317298.rar
另一篇介紹打洞技術(shù)的(補充)
UDP打洞技術(shù)依賴于由公共防火墻和cone
NAT,允許適當?shù)挠杏媱澋亩藢Χ藨?yīng)用程序通過NAT"打洞",即使當雙方的主機都處于NAT之后。這種技術(shù)在 RFC3027的5.1節(jié)[NAT
PROT]
中進行了重點介紹,并且在Internet[KEGEL]中進行了非正式的描敘,還應(yīng)用到了最新的一些協(xié)議,例如[TEREDO,ICE]協(xié)議中。不過,
我們要注意的是,"術(shù)"如其名,UDP打洞技術(shù)的可靠性全都要依賴于UDP。
這里將考慮兩種典型場景,來介紹連接的雙方應(yīng)用程序如何按照計劃的進行通信的,第一種場景,我們假設(shè)兩個客戶端都處于不同的NAT之后;第二種場景,我們假設(shè)兩個客戶端都處于同一個NAT之后,但是它們彼此都不知道(他們在同一個NAT中)。
處于不同NAT之后的客戶端通信
我們假設(shè) 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只是簡單的發(fā)送一個UDP信息到CLIENT
B的公網(wǎng)地址138.76.29.7:31000的話,NAT B會不加考慮的將這個信息丟棄(除非NAT B是一個 full cone
NAT),因為 這個UDP信息中所包含的地址信息,與CLIENT B和服務(wù)器S建立連接時存儲在NAT
B中的服務(wù)器S的地址信息不符。同樣的,CLIENT B如果做同樣的事情,發(fā)送的UDP信息也會被 NAT A 丟棄。
假如
CLIENT A 開始發(fā)送一個 UDP 信息到 CLIENT B 的公網(wǎng)地址上,與此同時,他又通過S中轉(zhuǎn)發(fā)送了一個邀請信息給CLIENT
B,請求CLIENT B也給CLIENT A發(fā)送一個UDP信息到 CLIENT A的公網(wǎng)地址上。這時CLIENT A向CLIENT
B的公網(wǎng)IP(138.76.29.7:31000)發(fā)送的信息導(dǎo)致 NAT A 打開一個處于 CLIENT A的私有地址和CLIENT
B的公網(wǎng)地址之間的新的通信會話,與此同時,NAT B 也打開了一個處于CLIENT B的私有地址和CLIENT
A的公網(wǎng)地址(155.99.25.11:62000)之間的新的通信會話。一旦這個新的UDP會話各自向?qū)Ψ酱蜷_了,CLIENT A和CLIENT
B之間就可以直接通信,而無需S來牽線搭橋了。(這就是所謂的打洞技術(shù))!
posted @
2010-09-01 19:08 小果子 閱讀(1776) |
評論 (0) |
編輯 收藏