p2p穿透源碼
Ⅰ 什麼是p2p網貸平台源碼網貸源碼有哪些
網貸,又稱為p2p網路借貸。P2P是英文peer to peer的縮寫,意即「個人對個人」。網路信貸起源於英國,隨後發展到美國、德國和其他國家,其典型的模式為:網路信貸公司提供平台,由借貸雙方自由競價,撮合成交。資金借出人獲取利息收益,並承擔風險;資金借入人到期償還本金,網路信貸公司收取中介服務費。p2p網貸平台源碼基本上是不可能給公開下載的,網上有部分p2p網貸源碼泄露了,那源碼也是被修改過很多次的了,會隱藏也多bug的。下載安裝後期維護成本很高,還不利於二次開發!你可以去看看迪蒙的網貸系統,中國網貸系統第一品牌,安全可靠,功能也很齊全,完全能滿足企業定製開發需求。
Ⅱ 廣域網實現p2p文件傳輸 如何實現nat穿透 求java或C++源代碼
假設有兩台分別處於各自的私有網路中的主機:A和B;N1和N2是兩個NAT設備;S是一個使用了一個眾所周知的、從全球任何地方都能訪問得到的IP地址的公共伺服器
步驟一:A和B分別和S建立UDP連接;NAT設備N1和N2創建UDP轉換狀態並分配臨時的外部埠號
步驟二:S將這些埠號傳回A和B
步驟三:A和B通過轉換好的埠直接聯繫到對方的NAT設備;NAT設備則利用先前創建的轉換狀態將分組發往A和B
源碼已發送請查收
Ⅲ 詳解P2P技術中的NAT穿透原理(轉載)
課程地址:零聲學院 WebRTC入門與提高 https://ke.qq.com/course/435382?tuin=137bb271
技術支持QQ群:782508536
最近介入測試P2P的相關邏輯,因此對NAT穿透原理做了一定程度的了解(當然也沒有很深入)。本篇文章也是綜合和參考了些網路上和文獻里的一些資料(文中沒有對引用處進行標記,請見諒)。寫本文的目的就是,用自己的語言描述了這個過程,同時也在描述過程中加入了一些自己的理解,形成一篇文章作為要點的記錄。對於這一塊的知識,自己也有很多盲點,還請各路大神多多指教。
NAT(Network Address Translation,網路地址轉換),也叫做網路掩蔽或者IP掩蔽。NAT是一種網路地址翻譯技術,主要是將內部的私有IP地址(private IP)轉換成可以在公網使用的公網IP(public IP)。
時光回到上個世紀80年代,當時的人們在設計網路地址的時候,覺得再怎麼樣也不會有超過32bits位長即2的32次冪台終端設備連入互聯網,再加上增加ip的長度(即使是從4位元組增到6位元組)對當時設備的計算、存儲、傳輸成本也是相當巨大的。後來逐漸發現IP地址不夠用了,然後就NAT就誕生了!(雖然ipv6也是解決辦法,但始終普及不開來,而且未來到底ipv6夠不夠用仍是未知)。
因此,NAT技術能夠興起的原因還是因為在我們國家公網IP地址太少了,不夠用,所以才會採取這種地址轉換的策略。可見,NAT的本質就是讓一群機器公用同一個IP,這樣就暫時解決了IP短缺的問題。
優勢其實上面已經剛剛討論過了,根據定義,比較容易看出,NAT可以同時讓多個計算機同時聯網,並隱藏其內網IP,因此也增加了內網的網路安全性;此外,NAT對來自外部的數據查看其NAT映射記錄,對沒有相應記錄的數據包進行拒絕,提高了網路安全性。
那麼,NAT與此同時也帶來一些弊端:首先是,NAT設備會對數據包進行編輯修改,這樣就降低了發送數據的效率;此外,各種協議的應用各有不同,有的協議是無法通過NAT的(不能通過NAT的協議還是蠻多的),這就需要通過穿透技術來解決。我們後面會重點討論穿透技術。
簡單的背景了解過後,下面介紹下NAT實現的主要方式,以及NAT都有哪些類型。
1)靜態NAT:也就是靜態地址轉換。是指一個公網IP對應一個私有IP,是一對一的轉換,同時注意,這里只進行了IP轉換,而沒有進行埠的轉換。舉個栗子:
2)NAPT:埠多路復用技術。與靜態NAT的差別是,NAPT不但要轉換IP地址,還要進行傳輸層的埠轉換。具體的表現形式就是,對外只有一個公網IP,通過埠來區別不同私有IP主機的數據。再舉個栗子。
通過上面NAT實現方式的介紹,我們其實不難看出,現實環境中NAPT的應用顯然是更廣泛的。因此下面就重點介紹下NAPT的主要類型有哪些。
對於NAPT我們主要分為兩大類:錐型NAT和對稱型NAT。其中錐型NAT又分:完全錐型,受限錐型和埠受限錐型。概括的說:對稱型NAT是一個請求對應一個埠;錐型NAT(非對稱NAT)是多個請求(外部發向內部)對應一個埠,只要源IP埠不變,無論發往的目的IP是否相同,在NAT上都映射為同一個埠,形象的看起來就像錐子一樣。下面分別介紹這四種類型及其差異。
1)完全錐型NAT(Full Cone NAT,後面簡稱FC)
特點:IP和埠都不受限。
表現形式:將來自內部同一個IP地址同一個埠號(IP_IN_A : PORT_IN_A)的主機監聽/請求,映射到公網IP某個埠(IP_OUT_B : PORT_OUT_B)的監聽。任意外部IP地址與埠對其自己公網的IP這個映射後的埠訪問(IP_OUT_B : PORT_OUT_B),都將重新定位到內部這個主機(IP_IN_A : PORT_IN_A)。該技術中,基於C/S架構的應用可以在任何一端發起連接。是不是很繞啊。再簡單一點的說,就是,只要客戶端,由內到外建立一個映射(NatIP:NatPort -> A:P1)之後,其他IP的主機B或埠A:P2都可以使用這個洞給客戶端發送數據。見下圖()。
2)受限錐型NAT(Restricted Cone NAT)
特點:IP受限,埠不受限。
表現形式:與完全錐形NAT不同的是,在公網映射埠後,並不允許所有IP進行對於該埠的訪問,要想通信必需內部主機對某個外部IP主機發起過連接,然後這個外部IP主機就可以與該內部主機通信了,但埠不做限制。舉個栗子。當客戶端由內到外建立映射(NatIP:NatPort –> A:P1),A機器可以使用他的其他埠(P2)主動連接客戶端,但B機器則不被允許。因為IP受限啦,但是埠隨便。見下圖(綠色是允許通信,紅色是禁止通信)。
3)埠受限型NAT(Port Restricted Cone NAT)
特點:IP和埠都受限。
表現形式:該技術與受限錐形NAT相比更為嚴格。除具有受限錐形NAT特性,對於回復主機的埠也有要求。也就是說:只有當內部主機曾經發送過報文給外部主機(假設其IP地址為A且埠為P1)之後,外部主機才能以公網IP:PORT中的信息作為目標地址和目標埠,向內部主機發送UDP報文,同時,其請求報文的IP必須是A,埠必須為P1(使用IP地址為A,埠為P2,或者IP地址為B,埠為P1都將通信失敗)。例子見下圖。這一要求進一步強化了對外部報文請求來源的限制,從而較Restrictd Cone更具安全性。
4)對稱型NAT(Symmetric NAT)
特點:對每個外部主機或埠的會話都會映射為不同的埠(洞)。
表現形式:只有來自同一內部IP:PORT、且針對同一目標IP:PORT的請求才被NAT轉換至同一個公網(外部)IP:PORT,否則的話,NAT將為之分配一個新的外部(公網)IP:PORT。並且,只有曾經收到過內部主機請求的外部主機才能向內部主機發送數據包。內部主機用同一IP與同一埠與外部多IP通信。客戶端想和伺服器A(IP_A:PORT_A)建立連接,是通過NAT映射為NatIP:NatPortA來進行的。而客戶端和伺服器B(IP_B:PORT_B)建立連接,是通過NAT映射為NatIP:NatPortB來進行的。即同一個客戶端和不同的目標IP:PORT通信,經過NAT映射後的公網IP:PORT是不同的。此時,如果B想要和客戶端通信,也只能通過NatIP:NatPortB(也就是紫色的洞洞)來進行,而不能通過NatIP:NatPortA(也就是黃色的洞洞)。
以上,就是NAPT的四種NAT類型。可以看出由類型1)至類型4),NAT的限制是越來越大的。
根據上面的介紹,我們可以了解到,在實際的網路情況中,各個設備所處的網路環境是不同的。那麼,如果這些設備想要進行通信,首先判斷出設備所處的網路類型就是非常重要的一步。舉個例子來說:對於視頻會議和VoIP軟體,對位於不同NAT內部的主機通信需要靠伺服器來轉發完成,這樣就會增加伺服器的負擔。為了解決這種問題,要盡量使位於不同NAT內部的主機建立直接通信,其中,最重要的一點就是要判斷出NAT的類型,然後才能根據NAT的類型,設計出直接通信方案。不然的話,兩個都在NAT的終端怎麼通信呢?我們不知道對方的內網IP,即使把消息發到對方的網關,然後呢?網關怎麼知道這條消息給誰,而且誰允許網關這么做了?
為了解決這個問題,也就是處於內網的主機之間能夠穿越它們之間的NAT建立直接通信,已經提出了許多方法,STUN(Session Traversal Utilities for NAT,NAT會話穿越應用程序)技術就是其中比較重要的一種解決方法,並得到了廣泛的應用。在這個部分,我們將重點介紹下STUN技術的原理。(PS:除此之外,還有UPNP技術,ALG應用層網關識別技術,SBC會話邊界控制,ICE互動式連接建立,TURN中繼NAT穿越技術等等,本文不一一做介紹。)
STUN是一種網路協議,它允許位於NAT(或多重NAT)後的客戶端找出自己的公網地址,查出自己位於哪種類型的NAT之後以及NAT為某一個本地埠所綁定的Internet端埠。這些信息被用來在兩個同時處於NAT路由器之後的主機之間建立UDP通信。該協議由RFC 5389定義。STUN由三部分組成:STUN客戶端、STUN伺服器端、NAT路由器。STUN服務端部署在一台有著兩個公網IP的伺服器上。大概的結構參考下圖。STUN客戶端通過向伺服器端發送不同的消息類型,根據伺服器端不同的響應來做出相應的判斷,一旦客戶端得知了Internet端的UDP埠,通信就可以開始了。
STUN協議定義了三類測試過程來檢測NAT類型。
Test1: STUN Client通過埠{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(沒有設置任何屬性)。STUN Server收到該請求後,通過埠{IP-S1:Port-S1}把它所看到的STUN Client的IP和埠{IP-M1,Port-M1}作為Binding Response的內容回送給STUN Client。 Test1#2:STUN Client通過埠{IP-C1:Port-C1}向STUN Server{IP-S2:Port-S2}發送一個Binding Request(沒有設置任何屬性)。STUN Server收到該請求後,通過埠{IP-S2:Port-S2}把它所看到的STUN Client的IP和埠{IP-M1#2,Port-M1#2}作為Binding Response的內容回送給STUN Client。
Test2: STUN Client通過埠{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(設置了Change IP和Change Port屬性)。STUN Server收到該請求後,通過埠{IP-S2:Port-S2}把它所看到的STUN Client的IP和埠{IP-M2,Port-M2}作為Binding Response的內容回送給STUN Client。
Test3: STUN Client通過埠{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(設置了Change Port屬性)。STUN Server收到該請求後,通過埠{IP-S1:Port-S2}把它所看到的STUN Client的IP和埠{IP-M3,Port-M3}作為Binding Response的內容回送給STUN Client。
STUN協議的輸出是: 1)公網IP和Port 2)防火牆是否設置 3)客戶端是否在NAT之後,及所處的NAT的類型
因此我們進而整理出,通過STUN協議,我們可以檢測的類型一共有以下七種:
A:公開的互聯網IP。主機擁有公網IP,並且沒有防火牆,可自由與外部通信 B:完全錐形NAT。 C:受限制錐形NAT。 D:埠受限制形NAT。 E:對稱型UDP防火牆。主機出口處沒有NAT設備,但有防火牆,且防火牆規則如下:從主機UDP埠A發出的數據包保持源地址,但只有從之前該主機發出包的目的IP/PORT發出到該主機埠A的包才能通過防火牆。 F:對稱型NAT G:防火牆限制UDP通信。
輸入和輸出准備好後,附上一張維基網路的流程圖,就可以描述STUN協議的判斷過程了。
STEP1:檢測客戶端是否有能力進行UDP通信以及客戶端是否位於NAT後 -- Test1 客戶端建立UDP socket,然後用這個socket向伺服器的(IP-1,Port-1)發送數據包要求伺服器返回客戶端的IP和Port,客戶端發送請求後立即開始接受數據包。重復幾次。 a)如果每次都超時收不到伺服器的響應,則說明客戶端無法進行UDP通信,可能是:G防火牆阻止UDP通信 b)如果能收到回應,則把伺服器返回的客戶端的(IP:PORT)同(Local IP: Local Port)比較: 如果完全相同則客戶端不在NAT後,這樣的客戶端是:A具有公網IP可以直接監聽UDP埠接收數據進行通信或者E。 否則客戶端在NAT後要做進一步的NAT類型檢測(繼續)。
STEP2:檢測客戶端防火牆類型 -- Test2 STUN客戶端向STUN伺服器發送請求,要求伺服器從其他IP和PORT向客戶端回復包: a)收不到伺服器從其他IP地址的回復,認為包前被前置防火牆阻斷,網路類型為E b)收到則認為客戶端處在一個開放的網路上,網路類型為A
STEP3:檢測客戶端NAT是否是FULL CONE NAT -- Test2 客戶端建立UDP socket然後用這個socket向伺服器的(IP-1,Port-1)發送數據包要求伺服器用另一對(IP-2,Port-2)響應客戶端的請求往回發一個數據包,客戶端發送請求後立即開始接受數據包。 重復這個過程若干次。 a)如果每次都超時,無法接受到伺服器的回應,則說明客戶端的NAT不是一個Full Cone NAT,具體類型有待下一步檢測(繼續)。 b)如果能夠接受到伺服器從(IP-2,Port-2)返回的應答UDP包,則說明客戶端是一個Full Cone NAT,這樣的客戶端能夠進行UDP-P2P通信。
STEP4:檢測客戶端NAT是否是SYMMETRIC NAT -- Test1#2 客戶端建立UDP socket然後用這個socket向伺服器的(IP-1,Port-1)發送數據包要求伺服器返回客戶端的IP和Port, 客戶端發送請求後立即開始接受數據包。 重復這個過程直到收到回應(一定能夠收到,因為第一步保證了這個客戶端可以進行UDP通信)。 用同樣的方法用一個socket向伺服器的(IP-2,Port-2)發送數據包要求伺服器返回客戶端的IP和Port。 比較上面兩個過程從伺服器返回的客戶端(IP,Port),如果兩個過程返回的(IP,Port)有一對不同則說明客戶端為Symmetric NAT,這樣的客戶端無法進行UDP-P2P通信(檢測停止)因為對稱型NAT,每次連接埠都不一樣,所以無法知道對稱NAT的客戶端,下一次會用什麼埠。否則是Restricted Cone NAT,是否為Port Restricted Cone NAT有待檢測(繼續)。
STEP5:檢測客戶端NAT是Restricted Cone 還是 Port Restricted Cone -- Test3 客戶端建立UDP socket然後用這個socket向伺服器的(IP-1,Port-1)發送數據包要求伺服器用IP-1和一個不同於Port-1的埠發送一個UDP 數據包響應客戶端, 客戶端發送請求後立即開始接受數據包。重復這個過程若干次。如果每次都超時,無法接受到伺服器的回應,則說明客戶端是一個Port Restricted Cone NAT,如果能夠收到伺服器的響應則說明客戶端是一個Restricted Cone NAT。以上兩種NAT都可以進行UDP-P2P通信。
通過以上過程,至此,就可以分析和判斷出客戶端是否處於NAT之後,以及NAT的類型及其公網IP,以及判斷客戶端是否具備P2P通信的能力了。當然這是自己個人筆記的第一篇,後面,再作一篇筆記《NAT穿透原理淺析(二)》分析下不同NAT類型的穿透打洞策略。
Ⅳ p2p網貸系統源碼怎麼使用
杭州融都網貸系統,專注於高端定製的平台就比較安全,有專門人員定期維護,檢查漏洞,補丁跟新等,而且伺服器部署高安全。全體24小時都有人在時刻檢查 特別穩定
Ⅳ 如何製作能穿透區域網的p2p聊天軟體
一般區域網使用了NAT(網路地址轉換技術)和防火牆。P2P穿越NAT和防火牆主要有兩種技術:稱為打洞的UDP Punch技術和利用NAT設備管理介面的UPnP技術。一般使用UDP Punch技術,它實現起來簡單一點。TCP實現起來比較復雜。關於這兩種技術實現細節你可以查找相關書籍進行深入了解。
Ⅵ 以太坊源碼分析--p2p節點發現
節點發現功能主要涉及 Server Table udp 這幾個數據結構,它們有獨自的事件響應循環,節點發現功能便是它們互相協作完成的。其中,每個以太坊客戶端啟動後都會在本地運行一個 Server ,並將網路拓撲中相鄰的節點視為 Node ,而 Table 是 Node 的容器, udp 則是負責維持底層的連接。下面重點描述它們中重要的欄位和事件循環處理的關鍵部分。
PrivateKey - 本節點的私鑰,用於與其他節點建立時的握手協商
Protocols - 支持的所有上層協議
StaticNodes - 預設的靜態 Peer ,節點啟動時會首先去向它們發起連接,建立鄰居關系
newTransport - 下層傳輸層實現,定義握手過程中的數據加密解密方式,默認的傳輸層實現是用 newRLPX() 創建的 rlpx ,這不是本文的重點
ntab - 典型實現是 Table ,所有 peer 以 Node 的形式存放在 Table
ourHandshake - 與其他節點建立連接時的握手信息,包含本地節點的版本號以及支持的上層協議
addpeer - 連接握手完成後,連接過程通過這個通道通知 Server
Server 的監聽循環,啟動底層監聽socket,當收到連接請求時,Accept後調用 setupConn() 開始連接建立過程
Server的主要事件處理和功能實現循環
Node 唯一表示網路上的一個節點
IP - IP地址
UDP/TCP - 連接使用的UDP/TCP埠號
ID - 以太坊網路中唯一標識一個節點,本質上是一個橢圓曲線公鑰(PublicKey),與 Server 的 PrivateKey 對應。一個節點的IP地址不一定是固定的,但ID是唯一的。
sha - 用於節點間的距離計算
Table 主要用來管理與本節點與其他節點的連接的建立更新刪除
bucket - 所有 peer 按與本節點的距離遠近放在不同的桶(bucket)中,詳見之後的 節點維護
refreshReq - 更新 Table 請求通道
Table 的主要事件循環,主要負責控制 refresh 和 revalidate 過程。
refresh.C - 定時(30s)啟動Peer刷新過程的定時器
refreshReq - 接收其他線程投遞到 Table 的 刷新Peer連接 的通知,當收到該通知時啟動更新,詳見之後的 更新鄰居關系
revalidate.C - 定時重新檢查以連接節點的有效性的定時器,詳見之後的 探活檢測
udp 負責節點間通信的底層消息控制,是 Table 運行的 Kademlia 協議的底層組件
conn - 底層監聽埠的連接
addpending - udp 用來接收 pending 的channel。使用場景為:當我們向其他節點發送數據包後(packet)後可能會期待收到它的回復,pending用來記錄一次這種還沒有到來的回復。舉個例子,當我們發送ping包時,總是期待對方回復pong包。這時就可以將構造一個pending結構,其中包含期待接收的pong包的信息以及對應的callback函數,將這個pengding投遞到udp的這個channel。 udp 在收到匹配的pong後,執行預設的callback。
gotreply - udp 用來接收其他節點回復的通道,配合上面的addpending,收到回復後,遍歷已有的pending鏈表,看是否有匹配的pending。
Table - 和 Server 中的ntab是同一個 Table
udp 的處理循環,負責控制消息的向上遞交和收發控制
udp 的底層接受數據包循環,負責接收其他節點的 packet
以太坊使用 Kademlia 分布式路由存儲協議來進行網路拓撲維護,了解該協議建議先閱讀 易懂分布式 。更權威的資料可以查看 wiki 。總的來說該協議:
源碼中由 Table 結構保存所有 bucket , bucket 結構如下
節點可以在 entries 和 replacements 互相轉化,一個 entries 節點如果 Validate 失敗,那麼它會被原本將一個原本在 replacements 數組的節點替換。
有效性檢測就是利用 ping 消息進行探活操作。 Table.loop() 啟動了一個定時器(0~10s),定期隨機選擇一個bucket,向其 entries 中末尾的節點發送 ping 消息,如果對方回應了 pong ,則探活成功。
Table.loop() 會定期(定時器超時)或不定期(收到refreshReq)地進行更新鄰居關系(發現新鄰居),兩者都調用 doRefresh() 方法,該方法對在網路上查找離自身和三個隨機節點最近的若干個節點。
Table 的 lookup() 方法用來實現節點查找目標節點,它的實現就是 Kademlia 協議,通過節點間的接力,一步一步接近目標。
當一個節點啟動後,它會首先向配置的靜態節點發起連接,發起連接的過程稱為 Dial ,源碼中通過創建 dialTask 跟蹤這個過程
dialTask表示一次向其他節點主動發起連接的任務
在 Server 啟動時,會調用 newDialState() 根據預配置的 StaticNodes 初始化一批 dialTask , 並在 Server.run() 方法中,啟動這些這些任務。
Dial 過程需要知道目標節點( dest )的IP地址,如果不知道的話,就要先使用 recolve() 解析出目標的IP地址,怎麼解析?就是先要用藉助 Kademlia 協議在網路中查找目標節點。
當得到目標節點的IP後,下一步便是建立連接,這是通過 dialTask.dial() 建立連接
連接建立的握手過程分為兩個階段,在在 SetupConn() 中實現
第一階段為 ECDH密鑰建立 :
第二階段為協議握手,互相交換支持的上層協議
如果兩次握手都通過,dialTask將向 Server 的 addpeer 通道發送 peer 的信息
Ⅶ IPFS(四) 源碼解讀之-p2p
package p2p
import (
"context"
"errors"
"time"
net "gx/ipfs//go-libp2p-net"
manet "gx/ipfs//go-multiaddr-net"
ma "gx/ipfs//go-multiaddr"
pro "gx/ipfs//go-libp2p-protocol"
pstore "gx/ipfs//go-libp2p-peerstore"
p2phost "gx/ipfs//go-libp2p-host"
peer "gx/ipfs//go-libp2p-peer"
)
//P2P結構保存當前正在運行的流/監聽器的信息
// P2P structure holds information on currently running streams/listeners
type P2P struct {
//監聽器
Listeners ListenerRegistry
//數據流
Streams StreamRegistry
//節點ID
identity peer.ID
//節點地址
peerHost p2phost.Host
//一個線程安全的對等節點存儲
peerstore pstore.Peerstore
}
//創建一個新的p2p結構
// NewP2P creates new P2P struct
//這個新的p2p結構不包含p2p結構中的監聽器和數據流
func NewP2P(identity peer.ID, peerHost p2phost.Host, peerstore pstore.Peerstore) *P2P {
return &P2P{
identity: identity,
peerHost: peerHost,
peerstore: peerstore,
}
}
//新建一個數據流 工具方法 構建一個有節點id,內容和協議的流
func (p2p P2P) newStreamTo(ctx2 context.Context, p peer.ID, protocol string) (net.Stream, error) {
//30s 後會自動timeout
ctx, cancel := context.WithTimeout(ctx2, time.Second 30) //TODO: configurable?
defer cancel()
err := p2p.peerHost.Connect(ctx, pstore.PeerInfo{ID: p})
if err != nil {
return nil, err
}
return p2p.peerHost.NewStream(ctx2, p, pro.ID(protocol))
}
//對話為遠程監聽器創建新的P2P流
//創建一個新的p2p流實現對對話的監聽
// Dial creates new P2P stream to a remote listener
//Multiaddr是一種跨協議、跨平台的表示格式的互聯網地址。它強調明確性和自我描述。
//對內接收
func (p2p P2P) Dial(ctx context.Context, addr ma.Multiaddr, peer peer.ID, proto string, bindAddr ma.Multiaddr) ( ListenerInfo, error) {
//獲取一些節點信息 network, host, nil
lnet, _, err := manet.DialArgs(bindAddr)
if err != nil {
return nil, err
}
//監聽信息
listenerInfo := ListenerInfo{
//節點身份
Identity: p2p.identity,
////應用程序協議標識符。
Protocol: proto,
}
//調用newStreamTo 通過ctx(內容) peer(節點id) proto(協議標識符) 參數獲取一個新的數據流
remote, err := p2p.newStreamTo(ctx, peer, proto)
if err != nil {
return nil, err
}
//network協議標識
switch lnet {
//network為"tcp", "tcp4", "tcp6"
case "tcp", "tcp4", "tcp6":
//從監聽器獲取新的信息 nla.Listener, nil
listener, err := manet.Listen(bindAddr)
if err != nil {
if err2 := remote.Reset(); err2 != nil {
return nil, err2
}
return nil, err
}
//將獲取的新信息保存到listenerInfo
listenerInfo.Address = listener.Multiaddr()
listenerInfo.Closer = listener
listenerInfo.Running = true
//開啟接受
go p2p.doAccept(&listenerInfo, remote, listener)
default:
return nil, errors.New("unsupported protocol: " + lnet)
}
return &listenerInfo, nil
}
//
func (p2p *P2P) doAccept(listenerInfo *ListenerInfo, remote net.Stream, listener manet.Listener) {
//關閉偵聽器並刪除流處理程序
defer listener.Close()
//Returns a Multiaddr friendly Conn
//一個有好的 Multiaddr 連接
local, err := listener.Accept()
if err != nil {
return
}
stream := StreamInfo{
//連接協議
Protocol: listenerInfo.Protocol,
//定位節點
LocalPeer: listenerInfo.Identity,
//定位節點地址
LocalAddr: listenerInfo.Address,
//遠程節點
RemotePeer: remote.Conn().RemotePeer(),
//遠程節點地址
RemoteAddr: remote.Conn().RemoteMultiaddr(),
//定位
Local: local,
//遠程
Remote: remote,
//注冊碼
Registry: &p2p.Streams,
}
//注冊連接信息
p2p.Streams.Register(&stream)
//開啟節點廣播
stream.startStreaming()
}
//偵聽器將流處理程序包裝到偵聽器中
// Listener wraps stream handler into a listener
type Listener interface {
Accept() (net.Stream, error)
Close() error
}
//P2PListener保存關於偵聽器的信息
// P2PListener holds information on a listener
type P2PListener struct {
peerHost p2phost.Host
conCh chan net.Stream
proto pro.ID
ctx context.Context
cancel func()
}
//等待偵聽器的連接
// Accept waits for a connection from the listener
func (il *P2PListener) Accept() (net.Stream, error) {
select {
case c := <-il.conCh:
return c, nil
case <-il.ctx.Done():
return nil, il.ctx.Err()
}
}
//關閉偵聽器並刪除流處理程序
// Close closes the listener and removes stream handler
func (il *P2PListener) Close() error {
il.cancel()
il.peerHost.RemoveStreamHandler(il.proto)
return nil
}
// Listen創建新的P2PListener
// Listen creates new P2PListener
func (p2p P2P) registerStreamHandler(ctx2 context.Context, protocol string) ( P2PListener, error) {
ctx, cancel := context.WithCancel(ctx2)
list := &P2PListener{
peerHost: p2p.peerHost,
proto: pro.ID(protocol),
conCh: make(chan net.Stream),
ctx: ctx,
cancel: cancel,
}
p2p.peerHost.SetStreamHandler(list.proto, func(s net.Stream) {
select {
case list.conCh <- s:
case <-ctx.Done():
s.Reset()
}
})
return list, nil
}
// NewListener創建新的p2p偵聽器
// NewListener creates new p2p listener
//對外廣播
func (p2p P2P) NewListener(ctx context.Context, proto string, addr ma.Multiaddr) ( ListenerInfo, error) {
//調用registerStreamHandler 構造一個新的listener
listener, err := p2p.registerStreamHandler(ctx, proto)
if err != nil {
return nil, err
}
//構造新的listenerInfo
listenerInfo := ListenerInfo{
Identity: p2p.identity,
Protocol: proto,
Address: addr,
Closer: listener,
Running: true,
Registry: &p2p.Listeners,
}
go p2p.acceptStreams(&listenerInfo, listener)
//注冊連接信息
p2p.Listeners.Register(&listenerInfo)
return &listenerInfo, nil
}
//接受流
func (p2p *P2P) acceptStreams(listenerInfo *ListenerInfo, listener Listener) {
for listenerInfo.Running {
//一個有好的 遠程 連接
remote, err := listener.Accept()
if err != nil {
listener.Close()
break
}
}
//取消注冊表中的p2p偵聽器
p2p.Listeners.Deregister(listenerInfo.Protocol)
}
// CheckProtoExists檢查是否注冊了協議處理程序
// mux處理程序
// CheckProtoExists checks whether a protocol handler is registered to
// mux handler
func (p2p *P2P) CheckProtoExists(proto string) bool {
protos := p2p.peerHost.Mux().Protocols()
for _, p := range protos {
if p != proto {
continue
}
return true
}
return false
}
Ⅷ P2P穿透NAT的原理
NAT 俗稱網路地址轉換,基本 NAT 都部署在路由器或者交換機上。
主要還是IP地址的不足,使用少量的公有IP 地址代表較多的私有IP 地址的方式,將有助於減緩可用的IP地址空間的枯竭。用大白話:比如你有一個路由器(家用的那種就可以)這個路由器本身鏈接了公網(被分配到了一個公網的IP地址)。路由器後面有接了N多個設備,每個設備都分配到了一個私有的地址(內網地址),這些地址可以通過這個路由器和外網交互(並非是代理的中繼方式)。
1. 基本NAT: 這種NAT下的私有IP只有少部分(並不是全部)可以和外網通訊。每次這些私有地址向外網發送數據的時候,NAT就會把這個數據報的 源地址IP 修改成NAT的 公網地址 (這樣接收方以為這個數據報是從NAT發給我的,我後面數據報在會給這個NAT就好了),並在NAT內存下對應的 映射關系 (埠和內網私有IP的映射)。當NAT收外網的數據報的時候,根據數據報的埠號找到內網的私有IP地址,將這個數據報的目的IP地址修改成內網私有IP地址(整個過程NAT負責了IP地址來回替換,輔助完成了內網機器和外網設備通訊,這里的外網設備有可能也是一個NAT)。如果收到的數據報在映射表中找不到對應映射,這個數據報就會被丟棄。
存在問題: 基本的NAT由於不會改動埠信息,當內部2個不同機器(2個不同的私有IP)使用同樣的埠會出現映射錯誤了。
2. NPAT: 這類的NAT是在基本NAT基礎上演化而來,它不僅修改 IP地址 還有 埠號 。基本可以滿足NAT內全部的網路訪問要求。NAT內的私有IP地址第一次向外網發送數據,NAT會選一個 映射表 裡面還沒有被使用過的 埠號 ,然後修改 發送數據報 的 源IP地址 和 源埠號 並發送出去,並把 私有IP地址 和 源埠號 ,和修改後的 埠號 映射關系記錄下來。之後這個私有IP地址發送數據會首先判別之前是不是已經有這個映射關系,如果有就繼續按照映射關系修改數據報。當NAT收到外部的數據,會從映射表找到對應關系。修改數據報的 目的IP地址 和 目的埠號 ,如果映射表中沒有記錄這個數據報就會被丟棄了。
解決了基礎NAT的埠問題: 通過修改埠的可以讓NAT內的多個私有IP地址可以使用相同的埠進行通信。因為NPAT更加靈活所以現在基本的NAT技術就是指的NPAT技術。
如果2台機器在同一個NAT下,那麼他們可以直接通信了。
情況 1: 如果2台機器有一台在NAT下另一個在外網,那麼只能NAT內的私有機器主動鏈接外網機器,因為外網的機器主動鏈接內網的機器,NAT映射表並不存在這個數據轉發項,這個數據報就被丟棄了。
情況 2: 如果2台機器都在不同的NAT下,那麼不管哪一方發起鏈接請求數據報都不會到達對應的機器。這種情況非常常見。
那麼最簡單的方式解決上面2個問題:就是在公網部署一台 中繼伺服器 ,雙方機器都鏈接這台伺服器。然後 中繼伺服器 幫助這2台伺服器轉發數據。這種方式最簡單也是效率最低的。
到這里P2P正式登場了: 比如有下面這樣的網路拓撲結構(就是上面情況2)
如果 NAT-A, IP:40.32.5.125 和 NAT-B, IP:234.12.3.8 需要直接通信的話,基本不可能,所以需要做些手段。
假設需要 手機 (圖中)建立TCP鏈接到 電腦 (圖中)需要如下手續:
**1. **由被鏈接方發送數據報(可以是UDP甚至是TCP的SYNC握手包)
[圖片上傳失敗...(image-4b61ae-1528199423593)]
綠色線表示了數據報流動的方向
圖中 電腦 給 NAT-A IP:40.32.5.125 / 埠:4553 發出了一個數據報,這個數據報在經過 NAT-B 的時候 源IP地址 被修改成了 NAT-B的IP地址234.12.3.8 , 源埠 被修改成了 678 。這個數據報在到達 NAT-A 的時候,在映射表中找不到 678 埠對應的內部私有IP的映射。所以這個數據報一定會被 NAT-A 丟棄掉。但是經過這次數據報發送,在 NAT-B 的映射表裡面就會標記 40.32.5.125:4553 我已經發送過數據過去了,那麼後面只要從這個地址發送來的數據報,我就可以轉發到內網正確的設備上面。
**2. **完成第一步後,鏈接方可以發起數據請求
p2p可以合理的利用互聯網的資源,比如兩個人視頻聊天。完全可以通過p2p打穿NAT後互發數據(QQ就是這么辦的)。但是有的時候設備之間的直連性能很差,比如:電信的用戶和移動的用戶視頻。這個問題是ISP廠商之間的過渡帶寬太窄,就算是設備直連但是依舊速度不快。所以這里情況下需要自己搭建伺服器中繼(中繼也不是完全無作用)。這個伺服器就是網關的作用,一般有多個網卡。不同的網卡對接不同的ISP廠商,然後互相轉發。
Ⅸ P2PSearcher穿透模式是什麼如何進入穿透模式
P2PSearcher穿透模式是什麼
軟體左下角有一項p2p網路連接判斷,如果是 「伺服器已連接」 ,證明p2p網路未被封殺。
如果是「已進入穿透模式」則表明p2p網路被封殺,需要通過穿透模塊完成資源搜索。
p2p未被封殺情況下,如何進入穿透模式?
經過幾個版本的改進,穿透模式下資源已經逐漸豐富。某些情況下,穿透模式效果甚至優於正常情況。如果在p2p網路正常想進入穿透模式,可以在軟體安裝目錄下新建一個名為 woyaochuanyue.txt 的空白文檔,重啟軟體即可。