日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

内网穿透实现P2P通信

發(fā)布時(shí)間:2024/3/12 编程问答 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 内网穿透实现P2P通信 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

P2P 通信最大的障礙就是 NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換),NAT 使得局域網(wǎng)內(nèi)的設(shè)備可以與公網(wǎng)進(jìn)行通訊,但是不同 NAT 下的設(shè)備之間通訊將會(huì)變得很困難。UDP 打洞就是用來使得設(shè)備間繞過 NAT 進(jìn)行通訊的一種技術(shù)。

一、背景知識(shí)介紹

1.什么是NAT?

NAT(Network Address Translation,網(wǎng)絡(luò)地址轉(zhuǎn)換)是一種網(wǎng)絡(luò)地址翻譯技術(shù),主要是將內(nèi)部的私有IP地址(private IP)轉(zhuǎn)換成可以在公網(wǎng)使用的公網(wǎng)IP(public IP)。

2.為什么會(huì)有NAT?

時(shí)光回到上個(gè)世紀(jì)80年代,當(dāng)時(shí)的人們?cè)谠O(shè)計(jì)網(wǎng)絡(luò)地址的時(shí)候,覺得再怎么樣也不會(huì)有超過32bit位長(zhǎng)即2的32次冪臺(tái)終端設(shè)備連入互聯(lián)網(wǎng),再加上增加ip的長(zhǎng)度(即使是從4字節(jié)增到6字節(jié))對(duì)當(dāng)時(shí)設(shè)備的計(jì)算、存儲(chǔ)、傳輸成本也是相當(dāng)巨大的。后來逐漸發(fā)現(xiàn)IP地址不夠用了,然后就NAT就誕生了!(雖然ipv6也是解決辦法,但始終普及不開來,而且未來到底ipv6夠不夠用仍是未知)。

因此,NAT技術(shù)能夠興起的原因還是因?yàn)樵谖覀儑?guó)家公網(wǎng)IP地址太少了,不夠用,所以才會(huì)采取這種地址轉(zhuǎn)換的策略。可見,NAT的本質(zhì)就是讓一群機(jī)器公用同一個(gè)IP,這樣就暫時(shí)解決了IP短缺的問題。

3.NAT有什么優(yōu)缺點(diǎn)?

優(yōu)勢(shì)其實(shí)上面已經(jīng)剛剛討論過了,根據(jù)定義,比較容易看出,NAT可以同時(shí)讓多個(gè)計(jì)算機(jī)同時(shí)聯(lián)網(wǎng),并隱藏其內(nèi)網(wǎng)IP,因此也增加了內(nèi)網(wǎng)的網(wǎng)絡(luò)安全性;此外,NAT對(duì)來自外部的數(shù)據(jù)查看其NAT映射記錄,對(duì)沒有相應(yīng)記錄的數(shù)據(jù)包進(jìn)行拒絕,提高了網(wǎng)絡(luò)安全性。

那么,NAT與此同時(shí)也帶來一些弊端:首先是,NAT設(shè)備會(huì)對(duì)數(shù)據(jù)包進(jìn)行編輯修改,這樣就降低了發(fā)送數(shù)據(jù)的效率;此外,各種協(xié)議的應(yīng)用各有不同,有的協(xié)議是無(wú)法通過NAT的(不能通過NAT的協(xié)議還是蠻多的),這就需要通過穿透技術(shù)來解決。我們后面會(huì)重點(diǎn)討論穿透技術(shù)。

二、NAT介紹

1.NAT工作原理:


首先,NAT A 網(wǎng)下的設(shè)備 1(192.168.1.101)想與某公網(wǎng) IP 通訊,設(shè)備 1 將包發(fā)給 NAT A,然后 NAT A 對(duì)源 IP 進(jìn)行轉(zhuǎn)換(123.122.53.20)發(fā)給 NAT B(中間可能還會(huì)經(jīng)過多重 NAT)。

這樣做的目的是,NAT B 并不知曉 NAT A 下的各個(gè)設(shè)備,他只能與 NAT A 本身通訊,因此發(fā)送給 NAT B 的包源 IP 必須是 NAT A 的公網(wǎng) IP,不然 NAT B 沒有辦法進(jìn)行回復(fù)。

接下來 NAT B 將回復(fù)包再發(fā)回 NAT A,此時(shí)就是 NAT 發(fā)揮作用的時(shí)候了,NAT A 現(xiàn)在要做的就是將包再分發(fā)回之前的設(shè)備,如何確定要發(fā)給誰(shuí)呢?NAT 中記錄了一張表,之前 192.168.1.101 通過 2333 端口與 42.120.241.46 端口 443 通訊了,并且 NAT A 是用 60001 的端口轉(zhuǎn)發(fā)出去的,那么這次接受到發(fā)往該 NAT 60001 端口的包時(shí)就應(yīng)該再通過 2333 端口轉(zhuǎn)發(fā)給 192.168.1.101。經(jīng)過這樣的過程,NAT A 下的設(shè)備都可以連接到互聯(lián)網(wǎng)了!

2.NAT特性:

1.網(wǎng)絡(luò)訪問只能先由私網(wǎng)側(cè)發(fā)起,公網(wǎng)無(wú)法主動(dòng)訪問私網(wǎng)主機(jī);
2.NAT網(wǎng)關(guān)在兩個(gè)訪問方向上完成兩次地址的轉(zhuǎn)換或翻譯,出方向做源信息替換,入方向做目的信息替換;
3.NAT網(wǎng)關(guān)的存在對(duì)通信雙方是保持透明的;
4.NAT網(wǎng)關(guān)為了實(shí)現(xiàn)雙向翻譯的功能,需要維護(hù)一張關(guān)聯(lián)表,把會(huì)話的信息保存下來。

3.NAT類型:

NAT分為基礎(chǔ)型NAT(靜態(tài)NAT即Static NAT,動(dòng)態(tài)NAT即Dynamic NAT/Pooled NAT)和NAPT(Network Address Port Translation)兩種,但由于基礎(chǔ)型NAT已不常用,我們通常提到的NAT就代指NAPT。NAPT是指網(wǎng)絡(luò)地址轉(zhuǎn)換過程中使用了端口復(fù)用技術(shù),即PAT(Port address Translation)。


對(duì)于基本使用的NAPT,又分為對(duì)稱和錐型NAT。

1.錐型NAT,有完全錐型、受限制錐型、端口受限制錐型三種:

a) Full Cone NAT(完全圓錐型):

特點(diǎn):IP和端口都不受限。

表現(xiàn)形式:從同一私網(wǎng)地址端口192.168.0.8:4000發(fā)至公網(wǎng)的所有請(qǐng)求都映射成同一個(gè)公網(wǎng)地址端口1.2.3.4:62000 ,192.168.0.8可以收到任意外部主機(jī)發(fā)到1.2.3.4:62000的數(shù)據(jù)報(bào)。

b) Restricted Cone NAT (限制圓錐型):

特點(diǎn):IP受限,端口不受限。

表現(xiàn)形式: 從同一私網(wǎng)地址端口192.168.0.8:4000發(fā)至公網(wǎng)的所有請(qǐng)求都映射成同一個(gè)公網(wǎng)地址端口1.2.3.4:62000,只有當(dāng)內(nèi)部主機(jī)192.168.0.8先給服務(wù)器C 6.7.8.9發(fā)送一個(gè)數(shù)據(jù)報(bào)后,192.168.0.8才能收到6.7.8.9發(fā)送到1.2.3.4:62000的數(shù)據(jù)報(bào)。

c)Port Restricted Cone NAT(端口限制圓錐型):

特點(diǎn):IP和端口都受限。

表現(xiàn)形式:從同一私網(wǎng)地址端口192.168.0.8:4000發(fā)至公網(wǎng)的所有請(qǐng)求都映射成同一個(gè)公網(wǎng)地址端口1.2.3.4:62000,只有當(dāng)內(nèi)部主機(jī)192.168.0.8先向外部主機(jī)地址端口6.7.8.9:8000發(fā)送一個(gè)數(shù)據(jù)報(bào)后,192.168.0.8才能收到6.7.8.9:8000發(fā)送到1.2.3.4:62000的數(shù)據(jù)報(bào)。

2. Symmetric NAT(對(duì)稱NAT):

特點(diǎn):對(duì)每個(gè)外部主機(jī)或端口的會(huì)話都會(huì)映射為不同的端口(洞)。

表現(xiàn)形式:只有來自同一內(nèi)部IP:PORT、且針對(duì)同一目標(biāo)IP:PORT的請(qǐng)求才被NAT轉(zhuǎn)換至同一個(gè)公網(wǎng)(外部)IP:PORT,否則的話,NAT將為之分配一個(gè)新的外部(公網(wǎng))IP:PORT。并且,只有曾經(jīng)收到過內(nèi)部主機(jī)請(qǐng)求的外部主機(jī)才能向內(nèi)部主機(jī)發(fā)送數(shù)據(jù)包。
對(duì)稱的NAT不保證所有會(huì)話中的(私有地址,私有端口)和(公開IP,公開端口)之間綁定的一致性。相反,它為每個(gè)新的會(huì)話分配一個(gè)新的端口號(hào)。

NAT的限制:對(duì)稱NAT > 端口限制圓錐型 > 限制圓錐型 > 完全圓錐型

不同的NAT組合打洞的方式也有所不同,有點(diǎn)可以打洞,有的則不能打洞,如兩個(gè)都是對(duì)稱型設(shè)備則無(wú)法實(shí)現(xiàn)打洞。不同組合打洞結(jié)果如下:

Peer APeer B是否可以打洞
全錐型全錐型
全錐型受限錐型
全錐型端口受限錐型
全錐型對(duì)稱型
受限錐型受限錐型
受限錐型端口受限錐型
受限錐型對(duì)稱型
端口受限錐型端口受限錐型
端口受限錐型對(duì)稱型
對(duì)稱型對(duì)稱型

4.NAT類型檢測(cè)

前提條件:有一個(gè)公網(wǎng)的Server并且綁定了兩個(gè)公網(wǎng)IP(IP-1,IP-2)。這個(gè)Server做UDP監(jiān)聽(IP-1,Port-1),(IP-2,Port-2)并根據(jù)客戶端的要求進(jìn)行應(yīng)答。

第一步:檢測(cè)客戶端是否有能力進(jìn)行UDP通信以及客戶端是否位于NAT后?

客戶端建立UDP socket,然后用這個(gè)socket向服務(wù)器(IP-1,Port-1)發(fā)送數(shù)據(jù)包,要求服務(wù)器返回客戶端的IP和Port。客戶端發(fā)送請(qǐng)求后立即開始接受數(shù)據(jù)包,要設(shè)定socket Timeout(300ms),防止無(wú)限堵塞. 重復(fù)這個(gè)過程若干次。如果每次都超時(shí),無(wú)法接受到服務(wù)器的回應(yīng),則說明客戶端無(wú)法進(jìn)行UDP通信,可能是防火墻或NAT阻止UDP通信,這樣的客戶端也就不能P2P了(檢測(cè)停止)。
當(dāng)客戶端能夠接收到服務(wù)器的回應(yīng)時(shí),需要把服務(wù)器返回的客戶端(IP,Port)和這個(gè)客戶端socket的 (LocalIP,LocalPort)比較。如果完全相同則客戶端不在NAT后,這樣的客戶端具有公網(wǎng)IP可以直接監(jiān)聽UDP端口接收數(shù)據(jù)進(jìn)行通信(檢測(cè)停止)。否則客戶端在NAT后要做進(jìn)一步的NAT類型檢測(cè)(繼續(xù))。

第二步:檢測(cè)客戶端NAT是否是Full Cone NAT?

客戶端建立UDP socket,然后用這個(gè)socket向服務(wù)器(IP-1,Port-1)發(fā)送數(shù)據(jù)包,要求服務(wù)器用另一對(duì)(IP-2,Port-2)響應(yīng)客戶端的請(qǐng)求往回發(fā)一個(gè)數(shù)據(jù)包。客戶端發(fā)送請(qǐng)求后立即開始接受數(shù)據(jù)包,要設(shè)定socket Timeout(300ms),防止無(wú)限堵塞. 重復(fù)這個(gè)過程若干次。如果每次都超時(shí),無(wú)法接受到服務(wù)器的回應(yīng),則說明客戶端的NAT不是一個(gè)Full Cone NAT,具體類型有待下一步檢測(cè)(繼續(xù))。

如果能夠接受到服務(wù)器從(IP-2,Port-2)返回的應(yīng)答UDP包,則說明客戶端是一個(gè)Full Cone NAT,這樣的客戶端能夠進(jìn)行UDP-P2P通信(檢測(cè)停止)。

第三步:檢測(cè)客戶端NAT是否是Symmetric NAT?

客戶端建立UDP socket,然后用這個(gè)socket向服務(wù)器(IP-1,Port-1)發(fā)送數(shù)據(jù)包,要求服務(wù)器返回客戶端的IP和Port, 客戶端發(fā)送請(qǐng)求后立即開始接受數(shù)據(jù)包,要設(shè)定socket Timeout(300ms),防止無(wú)限堵塞. 重復(fù)這個(gè)過程直到收到回應(yīng)(一定能夠收到,因?yàn)榈谝徊奖WC了這個(gè)客戶端可以進(jìn)行UDP通信)。
用同樣的方法用同一個(gè)socket向服務(wù)器的(IP-2,Port-2)發(fā)送數(shù)據(jù)包要求服務(wù)器返回客戶端的IP和Port。
比較上面兩個(gè)過程從服務(wù)器返回的客戶端(IP,Port),如果兩個(gè)過程返回的(IP,Port)有一個(gè)不同(IP不同或者port不同),則說明客戶端為Symmetric NAT,這樣的客戶端無(wú)法進(jìn)行UDP-P2P通信(檢測(cè)停止)。

否則是Restricted Cone NAT,是否為Port Restricted Cone NAT有待檢測(cè)(繼續(xù))。

第四步:檢測(cè)客戶端NAT是否是Restricted Cone NAT還是Port Restricted Cone NAT?

客戶端建立UDP socket,然后用這個(gè)socket向服務(wù)器(IP-1,Port-1)發(fā)送數(shù)據(jù)包,要求服務(wù)器用IP-1和一個(gè)不同于Port-1的端口發(fā)送一個(gè)UDP數(shù)據(jù)包響應(yīng)客戶端, 客戶端發(fā)送請(qǐng)求后立即開始接受數(shù)據(jù)包,要設(shè)定socket Timeout(300ms),防止無(wú)限堵塞. 重復(fù)這個(gè)過程若干次。如果每次都超時(shí),無(wú)法接受到服務(wù)器的回應(yīng),則說明客戶端是一個(gè)Port Restricted Cone NAT,如果能夠收到服務(wù)器的響應(yīng)則說明客戶端是一個(gè)Restricted Cone NAT。以上兩種NAT都可以進(jìn)行UDP-P2P通信。

三、P2P通信

根據(jù)客戶端的不同,客戶端之間進(jìn)行P2P傳輸?shù)姆椒ㄒ猜杂胁煌?#xff0c;這里介紹了現(xiàn)有的穿越中間件進(jìn)行P2P通信的幾種技術(shù)。

1 中繼(Relaying)

這是最可靠但也是最低效的一種P2P通信實(shí)現(xiàn)。其原理是通過一個(gè)有公網(wǎng)IP的服務(wù)器中間人對(duì)兩個(gè)內(nèi)網(wǎng)客戶端的通信數(shù)據(jù)進(jìn)行中繼和轉(zhuǎn)發(fā)。如下圖所示:

Server S||+----------------------+----------------------+| |NAT A NAT B| || | Client A Client B

客戶端A和客戶端B不直接通信,而是先都與服務(wù)端S建立鏈接,然后再通過服務(wù)器S和對(duì)方建立的通路來中繼傳遞的數(shù)據(jù)。這種方法的缺陷很明顯, 當(dāng)鏈接的客戶端變多之后,會(huì)顯著增加服務(wù)器的負(fù)擔(dān),完全沒體現(xiàn)出P2P的優(yōu)勢(shì)。但這種方法的好處是能保證成功,因此在實(shí)踐中也常作為一種備選方案。

2 逆向鏈接(Connection reversal)

第二種方法在當(dāng)兩個(gè)端點(diǎn)中有一個(gè)不存在中間件的時(shí)候有效。例如,客戶端A在NAT之后,而客戶端B擁有全局IP地址,如下圖:

Server S18.181.0.31:1235||+----------------------+----------------------+| |NAT A | 155.99.25.11:62000 || || |Client A Client B10.0.0.1:1234 138.76.29.7:1234 

客戶端A內(nèi)網(wǎng)地址為10.0.0.1,且應(yīng)用程序正在使用TCP端口1234。A和服務(wù)器S建立了一個(gè)鏈接,服務(wù)器的IP地址為18.181.0.31,監(jiān)聽1235端口。NAT A給客戶端A分配了TCP端口62000,地址為NAT的公網(wǎng)IP地址155.99.25.11, 作為客戶端A對(duì)外當(dāng)前會(huì)話的臨時(shí)IP和端口。因此S認(rèn)為客戶端A就是155.99.25.11:62000。而B由于有公網(wǎng)地址,所以對(duì)S來說B就是138.76.29.7:1234。

當(dāng)客戶端B想要發(fā)起一個(gè)對(duì)客戶端A的P2P鏈接時(shí),要么鏈接A的外網(wǎng)地址155.99.25.11:62000,要么鏈接A的內(nèi)網(wǎng)地址10.0.0.1:1234,然而兩種方式鏈接都會(huì)失敗。

鏈接10.0.0.1:1234失敗自不用說,為什么鏈接155.99.25.11:62000也會(huì)失敗呢?來自B的TCP SYN握手請(qǐng)求到達(dá)NAT A的時(shí)候會(huì)被拒絕,因?yàn)閷?duì)NAT A來說只有外出的鏈接才是允許的。

在直接鏈接A失敗之后,B可以通過S向A中繼一個(gè)鏈接請(qǐng)求,從而從A方向“逆向“地建立起A-B之間的點(diǎn)對(duì)點(diǎn)鏈接。

很多當(dāng)前的P2P系統(tǒng)都實(shí)現(xiàn)了這種技術(shù),但其局限性也是很明顯的,只有當(dāng)其中一方有公網(wǎng)IP時(shí)鏈接才能建立。越來越多的情況下, 通信的雙方都在NAT之后,因此就要用到我們下面介紹的第三種技術(shù)了。

3 UDP打洞(UDP hole punching)

第三種P2P通信技術(shù),被廣泛采用的,名為“P2P打洞“。P2P打洞技術(shù)依賴于通常防火墻和錐型NAT允許正當(dāng)?shù)腜2P應(yīng)用程序在中間件中打洞且與對(duì)方建立直接鏈接的特性。 以下主要考慮兩種常見的場(chǎng)景,以及應(yīng)用程序如何設(shè)計(jì)去完美地處理這些情況。第一種場(chǎng)景代表了大多數(shù)情況,即兩個(gè)需要直接鏈接的客戶端處在兩個(gè)不同的NAT 之后;第二種場(chǎng)景是兩個(gè)客戶端在同一個(gè)NAT之后,但客戶端自己并不需要知道。

a) 端點(diǎn)在不同的NAT之后

假設(shè)客戶端A和客戶端B的地址都是內(nèi)網(wǎng)地址,且在不同的NAT后面。A、B上運(yùn)行的P2P應(yīng)用程序和服務(wù)器S都使用了UDP端口1234,A和B分別初始化了 與Server的UDP通信,地址映射如圖所示:

Server S18.181.0.31:1234||+----------------------+----------------------+| |NAT A NAT B 155.99.25.11:62000 138.76.29.7:31000| || |Client A Client B10.0.0.1:1234 10.1.1.3:1234

現(xiàn)在假設(shè)客戶端A打算與客戶端B直接建立一個(gè)UDP通信會(huì)話。如果A直接給B的公網(wǎng)地址138.76.29.7:31000發(fā)送UDP數(shù)據(jù),NAT B將很可能會(huì)無(wú)視進(jìn)入的數(shù)據(jù)(除非是Full Cone NAT),因?yàn)樵吹刂泛投丝谂cS不匹配,而最初只與S建立過會(huì)話。B往A直接發(fā)信息也類似。

假設(shè)A開始給B的公網(wǎng)地址發(fā)送UDP數(shù)據(jù)的同時(shí),給服務(wù)器S發(fā)送一個(gè)中繼請(qǐng)求,要求B開始給A的公網(wǎng)地址發(fā)送UDP信息。A往B的輸出信息會(huì)導(dǎo)致NAT A打開 一個(gè)A的內(nèi)網(wǎng)地址與與B的外網(wǎng)地址之間的新通訊會(huì)話,B往A亦然。一旦新的UDP會(huì)話在兩個(gè)方向都打開之后,客戶端A和客戶端B就能直接通訊, 而無(wú)須再通過引導(dǎo)服務(wù)器S了。

UDP打洞技術(shù)有許多有用的性質(zhì)。一旦一個(gè)的P2P鏈接建立,鏈接的雙方都能反過來作為“引導(dǎo)服務(wù)器”來幫助其他中間件后的客戶端進(jìn)行打洞,極大減少了服務(wù)器的負(fù)載。應(yīng)用程序不需要知道中間件具體是什么(如果有的話),因?yàn)橐陨系倪^程在沒有中間件或者有多個(gè)中間件的情況下也一樣能建立通信鏈路。

b) 端點(diǎn)在相同的NAT之后

現(xiàn)在考慮這樣一種情景,兩個(gè)客戶端A和B正好在同一個(gè)NAT之后(而且可能他們自己并不知道),因此在同一個(gè)內(nèi)網(wǎng)網(wǎng)段之內(nèi)。 客戶端A和服務(wù)器S建立了一個(gè)UDP會(huì)話,NAT為此分配了公網(wǎng)端口62000,B同樣和S建立會(huì)話,分配到了端口62001,如下圖:

Server S18.181.0.31:1234||NATA-S 155.99.25.11:62000B-S 155.99.25.11:62001|+----------------------+----------------------+| |Client A Client B 10.0.0.1:1234 10.1.1.3:1234

假設(shè)A和B使用了上節(jié)介紹的UDP打洞技術(shù)來建立P2P通路,那么會(huì)發(fā)生什么呢?

首先A和B會(huì)得到由S觀測(cè)到的對(duì)方的公網(wǎng)IP和端口號(hào),然后給對(duì)方的地址發(fā)送信息。 兩個(gè)客戶端只有在NAT允許內(nèi)網(wǎng)主機(jī)對(duì)內(nèi)網(wǎng)其他主機(jī)發(fā)起UDP會(huì)話的時(shí)候才能正常通信,我們把這種情況稱之為”回環(huán)傳輸“(lookback translation),因?yàn)閺膬?nèi)部到達(dá)NAT的數(shù)據(jù)會(huì)被“回送”到內(nèi)網(wǎng)中而不是轉(zhuǎn)發(fā)到外網(wǎng)。

例如,當(dāng)A發(fā)送一個(gè)UDP數(shù)據(jù)包給B的公網(wǎng)地址時(shí),數(shù)據(jù)包最初有源IP地址和端口地址10.0.0.1:1234和 目的地址155.99.25.11:62001,NAT收到包后,將其轉(zhuǎn)換為源155.99.25.11:62000(A的公網(wǎng)地址)和目的10.1.1.3:1234,然后再轉(zhuǎn)發(fā)給B。即便NAT支持回環(huán)傳輸,這種轉(zhuǎn)換和轉(zhuǎn)發(fā)在此情況下也是沒必要的,且有可能會(huì)增加A與B的對(duì)話延時(shí)和加重NAT的負(fù)擔(dān)。

對(duì)于這個(gè)情況,優(yōu)化方案是很直觀的。當(dāng)A和B最初通過S交換地址信息時(shí),他們應(yīng)該包含自身的IP地址和端口號(hào)(從自己看),同時(shí)也包含從服務(wù)器看的自己的 地址和端口號(hào)。然后客戶端同時(shí)開始從對(duì)方已知的兩個(gè)的地址中同時(shí)開始互相發(fā)送數(shù)據(jù),并使用第一個(gè)成功通信的地址作為對(duì)方地址。

如果兩個(gè)客戶端在同一個(gè) NAT后,發(fā)送到對(duì)方內(nèi)網(wǎng)地址的數(shù)據(jù)最有可能先到達(dá),從而可以建立一條不經(jīng)過NAT的通信鏈路;如果兩個(gè)客戶端在不同的NAT之后,發(fā)送給對(duì)方內(nèi)網(wǎng)地址的數(shù)據(jù)包根本就到達(dá)不了對(duì)方,但仍然可以通過公網(wǎng)地址來建立通路。

值得一提的是,雖然這些數(shù)據(jù)包通過某種方式驗(yàn)證,但是在不同NAT的情況下完全有可能會(huì)導(dǎo)致A往B發(fā)送的信息發(fā)送到其他A內(nèi)網(wǎng)網(wǎng)段中無(wú)關(guān)的結(jié)點(diǎn)上去的。

c) 端點(diǎn)在多級(jí)NAT之后

在一些拓樸結(jié)構(gòu)中,可能會(huì)存在多級(jí)NAT設(shè)備,在這種情況下,如果沒有關(guān)于拓樸的具體信息, 兩個(gè)Peer要建立“最優(yōu)”的P2P鏈接是不可能的,下面來說為什么。以下圖為例:

Server S18.181.0.31:1234||NAT XA-S 155.99.25.11:62000B-S 155.99.25.11:62001||+----------------------+----------------------+| |NAT A NAT B 192.168.1.1:30000 192.168.1.2:31000| || |Client A Client B10.0.0.1:1234 10.1.1.3:1234

假設(shè)NAT X是一個(gè)網(wǎng)絡(luò)提供商ISP部署的工業(yè)級(jí)NAT,其下子網(wǎng)共用一個(gè)公網(wǎng)地址155.99.25.11,NAT A和NAT B分別是其下不同用戶的網(wǎng)關(guān)部署的NAT。只有服務(wù)器S 和NAT X有全局的路由地址。Client A在NAT A的子網(wǎng)中,同時(shí)Client B在NAT B的子網(wǎng)中,每經(jīng)過一級(jí)NAT都要進(jìn)行一次網(wǎng)絡(luò)地址轉(zhuǎn)換。

現(xiàn)在假設(shè)A和B打算建立直接P2P鏈接,用一般的方法(通過Server S來打洞)自然是沒問題的,那能不能優(yōu)化呢?一種想當(dāng)然的優(yōu)化辦法是A直接把信息發(fā)送給NAT B的內(nèi)網(wǎng)地址192.168.1.2:31000,且B通過NAT B把信息發(fā)送給A的路由地址192.168.1.1:30000,不幸的是,A和B都沒有辦法得知這兩個(gè)目的地址,因?yàn)镾只看見了客戶端 ‵全局‵地址155.99.25.11。退一步說,即便A和B通過某種方法得知了那些地址,我們也無(wú)法保證他們是可用的。因?yàn)镮SP分配的子網(wǎng)地址可能和NAT A B分配的子網(wǎng)地址 域相沖突。因此客戶端沒有其他選擇,只能使用S來進(jìn)行打洞并進(jìn)行回環(huán)傳輸。

打洞流程

假設(shè)A現(xiàn)在希望建立一條到B的udp會(huì)話,那么這個(gè)建立基本流程是:

  • A,B分別建立到Server S的udp會(huì)話,那么Server S此時(shí)是知道A,B各自的外網(wǎng)ip+端口
  • Server S再和B的udp會(huì)話里告訴A的地址(外網(wǎng)ip+端口: 120.27.209.161:6000),同理把B的地址(120.26.10.118:3000)告訴A
  • B向A地址(120.27.209.161:6000)發(fā)送一個(gè)"握手"udp包,打通A->B的udp鏈路
  • 此時(shí)A可以向B(120.26.10.118:3000)發(fā)送udp包,A->B的會(huì)話建立成功
  • 四.代碼示例(golang)

    服務(wù)端 server.go

    package mainimport ("fmt""net""time" )const serverPort = 9000func main() {// 監(jiān)聽UDPconn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero,Port: serverPort,})if err != nil {fmt.Printf("監(jiān)聽失敗:%s\n", err)return}fmt.Printf("開始監(jiān)聽:[%s]\n", conn.LocalAddr().String())// 釋放資源defer conn.Close()// 存放連接的客戶端二元組peers := make([]*net.UDPAddr, 0, 2)b := make([]byte, 500)for {n, addr, err := conn.ReadFromUDP(b)if err != nil {fmt.Printf("讀取信息失敗:%s\n", err)return}// 將鏈接存起來peers = append(peers, addr)// 接受到的消息fmt.Printf("收到客戶端[%s]的消息:%s\n", addr.String(), b[:n])// 如果有2條鏈接了,就給客戶端響應(yīng)另一個(gè)客戶端二元組if len(peers) == 2 {fmt.Printf("可以進(jìn)行UDP打洞,建立[%s]--[%s]的連接\n", peers[0].String(), peers[1].String())conn.WriteToUDP([]byte(peers[1].String()), peers[0])conn.WriteToUDP([]byte(peers[0].String()), peers[1])time.Sleep(3 * time.Second)fmt.Println("中轉(zhuǎn)服務(wù)器退出,仍不影響peers通信")return}} }

    客戶端 client.go

    package mainimport ("fmt""net" )// 服務(wù)器地址(注:改為真實(shí)使用的服務(wù)器地址) const serverAddr = "127.0.0.1:9000" // 本地客戶端端口 const srcPort = 8888func main() {// 第一步:與服務(wù)建立UDP連接,接收服務(wù)器發(fā)送的另一個(gè)客戶端的地址信息srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: srcPort}raddr, _ := net.ResolveUDPAddr("udp4", serverAddr)fmt.Printf("本機(jī)地址[%s]\n", srcAddr)conn, err := net.DialUDP("udp4", srcAddr, raddr)if err != nil {fmt.Printf("連接服務(wù)器失敗:%s\n", err)return}_, err = conn.Write([]byte("hi"))if err != nil {fmt.Printf("與服務(wù)器發(fā)送消息失敗:%s\n", err)return}fmt.Println("與服務(wù)器發(fā)送消息成功,等待響應(yīng)...")// 開始等待服務(wù)器響應(yīng)消息b := make([]byte, 500)n, _, err := conn.ReadFromUDP(b)if err != nil {fmt.Printf("接收數(shù)據(jù)失敗:%s\n", err)return}// 與服務(wù)器的連接要斷開!!conn.Close()// 第二步:與另一個(gè)客戶端建立UDP連接,以此“打洞”// 另一個(gè)客戶端dstAddr, _ := net.ResolveUDPAddr("udp4", string(b[:n]))udpConn, err := net.DialUDP("udp4", srcAddr, dstAddr)if err != nil {fmt.Printf("與客戶端[%s]創(chuàng)建UDP失敗:%s\n", dstAddr, err)return}defer udpConn.Close()// 向另一個(gè)客戶端發(fā)送一條udp消息(對(duì)方的nat設(shè)備會(huì)丟棄該消息,非法來源),// 用意是在自身的nat設(shè)備打開一條可進(jìn)入的通道,這樣對(duì)方就可以發(fā)過來udp消息_, err = udpConn.Write([]byte("你好"))if err != nil {fmt.Printf("與客戶端發(fā)送消息失敗:%s\n", err)return}fmt.Printf("與客戶端[%s]建立成功,可以通信\n", dstAddr)// 標(biāo)準(zhǔn)輸入內(nèi)容go func() {msg := ""for {fmt.Scanln(&msg)_, err = udpConn.Write([]byte(msg))if err != nil {fmt.Printf("發(fā)送信息失敗:%s\n", err)continue}}}()// 接收另一個(gè)客戶端信息data := make([]byte, 500)for {dnum, _, err := udpConn.ReadFromUDP(data)if err != nil {fmt.Printf("接收信息失敗:%s\n", err)continue}fmt.Printf("來自[%s]的信息 >> %s\n", dstAddr, data[:dnum])} }

    總結(jié)

    以上是生活随笔為你收集整理的内网穿透实现P2P通信的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。