如何藉助伺服器,使兩個客戶端之間建立網路連接?

使用tcp協議,
兩個客戶端發送空包到伺服器,伺服器再分別獲取他們的ip和埠,然後轉發ip和埠,
這樣兩個客戶端直接連接進行傳遞數據,而不用伺服器進行數據傳遞的中介,
請問這種方法可行嗎?
如果可行的話,伺服器是不是就可以省下很多資源?


兩個都是內網客戶端?

如果兩個都是內網客戶端的話,這個方案太簡單了不可行,不過內網互聯的方案的確就是在這個基礎上再動點手腳,我沒搞過就不妄言了。


如果兩個客戶端都在公網上,
你直接問伺服器,給我網上的妹子,哦不,客戶端列表不就好了。


現在沒空回答長篇大論,簡單說說。
首先打洞技術最早出現大概是2004年有幾篇draft,stun協議大概也是那時候出現的。幾種nat類型fullcone 什麼的就是那篇draft定義的。這個draft非常粗糙,後來陸續有些新draft。其次,tcp打洞現實中幾乎不可能,沒見過產品里用的。一般打洞都是指udp。具體原因得展開講。最後,打洞本質是預測埠,也就是預測nat設備的埠分配演算法。如果你知道前端的nat設備的代碼,例如你知道它就是個linux2.6,那麼幾乎可以做到100%穿透。

此外還有一些細節,手機不詳細說了。有興趣可以問我。

另外,國內絕大多數p2p的產品,並沒有怎麼深入研究nat打洞,大多數時候他們都需要參與雙方有一方是公網ip或者是upnp之類。這裡有2個原因,一方面是nat打洞技術比較複雜,受運營商干擾較多,有時候運營商就有好幾層nat,設備難以預測;另一方面是打洞成功之後編碼複雜很多,以前只是一個tcp就好,現在要在udp上實現可靠傳輸,複雜度劇增。這個問題通常用虛擬網卡用ip over udp的隧道方案,或者用開源的tcp協議棧例如gtalk里libjingle庫。內置tcp協議棧的方法有不少缺陷,涉及到定時器精度等等。

關於預測,有很多奇技淫巧,不只是delta之類。

順便說一句:目前排名第一的回答提到的打洞,只是最容易的那種,所謂fullcone。事實上,你幾乎遇不到這種設備。例如linux bsd就都不是。這種設備據我測試過幾百種nat的經驗,大概不到10%。實際上,如果不考慮運營商的ip pool的那種大路由器,僅考慮家用小router,如果處理得當,可以做到任意2個nat之間打洞成功。

還有一個小細節。打洞時需要用到ip協議中的一些比較少用的東西,簡單說需要有rawsocket許可權。得要能控制ip頭和udp頭的內容。例如有時候你得控制ip ttl,以防止你的包到達對方觸發對方的firewall模塊,以及你要能接收到icmp並且從payload的前28位元組得到轉換後的包。這裡涉及到某些nat實現的bug利用。

再補充一點說明。預測指需要知道自己即將發出的包的映射後埠,也要知道對方即將發出的包的映射後埠。代碼能控制的是自己的源埠和目的埠。注意這裡甚至有測不準問題。有一種情況下即便你能預測還是不行。如果一定要穿,那隻能指望生日碰撞了。我可能沒說清楚,也許需要畫個圖會好一點。


題主好聰明,這就是P2P啊,迅雷下載就是基於這個原理,否則那麼多日本高清藝術作品還不得把迅雷伺服器壓垮了,並且這些玩意要是都走的迅雷的伺服器,那迅雷就得像快播一個下場了。

下面講一下實現原理,首先你的基本思路是對的,就是伺服器幫忙互相通告一下對方的地址,然後通過地址來建立P2P連接。這裡分兩種情況。
1. 如果有一方的機器在公網上,那麼就很簡單了,那個在公網的機器當做伺服器就可以了,so easy,不細講。
2. 如果兩台客戶端都在內網裡面,現在我們大多數的普通電腦都是這樣,都在一個路由器構建的內網中,這個就稍微複雜點。你需要用NAT打洞技術。先Mark,晚上有時間細講。如果你來不及,那就百度 「NAT 打洞」。
3. 如何區分客戶端的內網外網,客戶端伺服器代碼是你寫的,相信這個判斷應該難不倒你。

=========================NAT打洞的分隔線=========================

補充一下如何做NAT打洞。
原理:當內網機器A 以地址:192.168.1.1:9999向外發送請求,那麼所在的路由器會分配一個埠用來映射該機器的請求,比如19999。那麼伺服器看到的地址其實是外網地址:10.0.0.1:19999。當這樣的請求發生後的一段時間內(一般從半小時到數小時不等,取決於路由器),任何向10.0.0.1:19999發送的包,都會被路由器轉發到192.168.1.1:9999。這個就是NAT打洞可以實現的基本原理。

基於該原理,NAT打洞就很容易實施了。
1. 機器A,192.168.1.1,位於NetA,10.0.0.1後面. 機器B,192.168.2.2位於NetB後面 20.0.0.1.
2. 兩台機器分別以埠 A:9999,和B:8888向伺服器S發包,S就能得到了兩台機器的外網地址10.0.0.1:19999,20.0.0.1:18888,並且將地址發送到客戶端
3. 下面A想向B連接。那麼S告訴B,A要連你。此時B以埠8888向A的外網地址ACK包。NetA的路由器會將該包丟棄,因為安全策略不允許一個陌生的機器訪問內網。但是此時對於NetB的路由器來說,A就是一個「熟悉」的機器了,那麼來自於A的訪問就會被轉發到B。
4. A以埠9999向NetB發起連接,連接成功。

這裡有一個特例,在「原理」提到,當這樣的請求發生後的一段時間內(一般從半小時到數小時不等,取決於路由器),任何向10.0.0.1:19999發送的包,都會被路由器轉發到192.168.1.1:9999。
有一種NAT,它不會保持這樣的固定的埠映射,它會每一個連接,都啟用一個新的埠映射,對於這樣的NAT,那麼打洞就無法成功。
仔細看下1-4步驟,事實上兩台機器,只要有一台滿足持續的埠映射,那麼就可以打洞成功,在這個例子中,A機器其實是不需要滿足該要求的。
如果兩台機器所在的網路都無法打洞,那怎麼辦呢?有一種方案可行,就是在你的用戶中找到一個客戶端C,C可以打洞,那麼讓AC相連,BC相連,讓C成為中轉,就可以實現AB的相連了,這個邏輯上會比較複雜。
事實上,在目前我們所用的網路,絕大多數的NAT都是可以打洞的,目前無法打洞的NAT有,並且還很常見,就是你我手機所用的3G,4G.


服務端
mknod pipe1 pmknod pipe2 pnc -l -p 3333 〈pipe1 〉pipe2 nc -l -p 4444 〈pipe2 〉pipe1
然後兩個客戶端分別鏈接3333 4444這兩個埠 應該就能直接相互通訊了吧


如果是瀏覽器之間的通信,你可以嘗試使用webrtc來實現p2p。你只需要一個signal server來完成最初的地址搭橋,之後雙方便可使用基於SDP和ICE的協議進行通信。

不過不管client是處在瀏覽器環境還是OS環境,基本原理都是一樣的,需要解決的根本問題就是clients雙方需要知道對方的public ip address以及埠。

signal server是在此處的用處便是告訴clients對方的ip address 和埠。首先因為signal server的ip address是已知的,clients,設為alice bob, 都可以連接到signal server。連接上後signal server便知道alice bob的public ip。signal server便可以把對方的信息告訴clients。之後alice 和 bob就可以通信了。

比較麻煩的情況是穿透NAT,@chapman zhang 的答案已經把原理介紹得很好了,這裡不作詳述。只是概括一下:

一般情況下,如果需要穿透NAT,可搭建一個STUN server來幫助客戶端尋找public ip address,STUN server的NAT穿透率在86%左右,如果STUN server定址不成功,可以搭建一個TURN server來幫助客戶端轉發信息,間接地實現p2p通信,當然這時候所有網路流量都會經過TURN server來轉發,TURN server的網路流量負載非常的大,需要很大的帶寬來支持。


關鍵字:
STUN
ICE
好了。 自己google去吧。


推薦閱讀:

聽說一個IP可以綁定多個域名,那麼服務端是怎麼實現的?
服務端是如何主動推送信息到客戶端的?
用 thrift 或 gRPC 之類的框架做 app 和伺服器的通信合適嗎?
目前主流的伺服器有哪些?軟體和硬體?

TAG:伺服器 | Java編程 | TCPIP |