點對點穿透,需要實作的是對NAT的穿透。想實作NAT的穿透,當然要先了解NAT到底是什麼,以及NAT是用來幹什麼的。
NAT全稱<code>Network Address Translation</code>,意思是<code>網絡位址轉換</code>,在1994年提出。它可以對不同的IP及端口進行映射,将一個網絡位址轉換為另一個。NAT的主要用途,大家可以看路由器。路由器具有一個WAN口及多個LAN口;WAN口對外,連接配接網際網路,擁有公網IP;LAN口對内,建構本地網絡,配置設定的是私網IP。當處于LAN網下的本地主機想要通路網際網路的時候,路由器就會通過NAT技術,将LAN 口的私網IP映射到WAN口的公網IP,實作網絡位址的轉換,這樣本地主機就可以通路網際網路了。
NAT技術的出現,有效減緩了IPv4時代可用IP位址枯竭的問題。至于為何可以緩解IP位址枯竭,我們依舊可以參照路由器來加以了解。路由器的LAN網下擴充了多台本地主機,這些本地主機需要不同的IP位址加以區分。如果它們都是接在英特網下,那麼每台主機都需要消耗一個公網IP,但是通過路由器的NAT服務,這些本地主機可以先配置設定不同的私網IP,然後在需要連接配接到英特網的時候映射到公網IP的不同端口上完成對網際網路的通路,進而節省IP位址的消耗。至于私網IP位址,由于NAT的存在,本地網絡與英特網處于隔離狀态,不必擔心與其他網絡産生沖突。
上面是對NAT的一些簡單的介紹,以及NAT工作方式的簡單描述,如果覺得難以了解,可以自行查閱更多資料。
舉個例子,假如路由器WAN口擷取到的公網IP是<code>55.66.77.88</code>(随便寫的),LAN網下某台本地主機擷取到的私網IP是<code>192.168.0.100</code>(路由器多是<code>192.168.0.0</code>網段)。現在本地主機想要向英特網發起連接配接,它通過自己的<code>10000</code>端口發起了連接配接,路由器知道有本地主機向英特網發起連接配接,便會配置設定一個WAN口的可用端口做映射,比如配置設定到的是<code>5000</code>端口。這時,<code>192.168.0.100</code>的<code>10000</code>端口便和<code>55.66.77.88</code>的<code>5000</code>端口産生了映射關系。<code>55.66.77.88</code>的<code>5000</code>端口收到的網絡包便會轉發到<code>192.168.0.100</code>的<code>10000</code>端口,<code>192.168.0.100</code>的<code>10000</code>端口收到的網絡包也會轉發到<code>55.66.77.88</code>的<code>5000</code>端口。當然,由于是網絡位址轉換,這途中還會有拆包,重新裝包的過程,不做詳細說明。
大緻知道NAT怎麼工作的之後,接下來了解為什麼要穿透NAT。
按照上面的描述,NAT的工作需要LAN網下的本地裝置主動發起網絡連接配接,然後NAT服務才會将這個連接配接映射到WAN口的公網IP完成轉換。也就是說,如果本地主機沒有主動發起連接配接,那麼這個映射就不會存在,那麼公網上的機器就無法通路到私網上的機器。也就是說,隻能是私網機器主動連接配接公網機器,而不能是公網機器主動連接配接私網機器。而為了實作公網機器主動連接配接私網機器,我們就需要穿透NAT,這就是NAT穿透的由來。
目前比較好實作NAT穿透的方式是采用UDP連接配接對NAT進行<code>打洞</code>,然後完成連接配接。何為<code>打洞</code>呢?就是為了使NAT産生一個可用的映射。具體步驟就是在私網機器上用UDP向某台公網機器發起連接配接,使得NAT産生一個可以使用的映射<code>(洞)</code>。然後通過這個映射<code>(洞)</code>,就可以穿透NAT。
至于為什麼要用UDP,這是由于UDP的某些特性。UDP通信需要先綁定本地機器的端口,完成後就可以從這個端口收發資料,至于從哪裡收,發到哪裡,可以在收發資料的時候再決定,這也就意味着我可以用這一個端口同時和多個對象通信,隻要我收發資料的時候指定不同的對象即可。當本地機器用UDP向英特網上的某個伺服器發送資料的時候,這個映射不但能用來和這個伺服器進行資料互動,也能用來接收其他主機發來的資料。NAT穿透就是本地主機向公網上的某台伺服器發送資料,這時伺服器就可以獲得NAT對這台主機的映射,在之前舉得例子中就是<code>55.66.77.88:5000</code>這個位址。由于NAT會将<code>55.66.77.88:5000</code>收到的資料轉發至本地主機,是以公網上的其他機器可以從伺服器擷取到<code>55.66.77.88:5000</code>這個網絡位址,然後通過這個網絡位址向私網下的機器發出資料。
而至于為什麼不用TCP,也是由于TCP的某些特性。TCP通信的步驟與UDP不同,它需要先在兩個對象之間建立一個專用通道,再用這個通道收發資料。也就是說外人無法插手。這樣一來,雖然其他機器可以通過伺服器擷取到NAT的映射對象,也沒辦法利用它向私網下的機器發出資料。
<code>關于TCP與UDP的更多細節,請參考SOCKET程式設計。</code>
既然TCP在連接配接過程中其他人不能插手,但是等它連接配接結束之後呢?NAT對TCP連接配接的端口映射在連接配接結束後就立馬銷毀了嗎?接着深入,發現NAT存在一個老化機制。接下來看看老化是什麼意思。NAT生成某個映射後,會将這個映射儲存下來,但是即使端口号非常多,它也不是無限的,而既然端口号是有限資源,那麼就不能保證映射表的無限擴充。為了合理利用資源,當某個映射一段時間内沒有發生資料互動,NAT就會認為這個映射已經沒有人使用了,就會将這個映射銷毀,回收端口号。這個時間,就叫做老化時間。也就是說,老化是一種映射的回收機制。
但是沒有關系,SOCKET程式設計中允許有一些特殊的選項,其中有一個叫SO_REUSEADDR的選項。
<code>SO_REUSEADDR用于對TCP套接字處于TIME_WAIT狀态下的socket,才可以重複綁定使用。server程式總是應該在調用bind()之前設定SO_REUSEADDR套接字選項。TCP,先調用close()的一方會進入TIME_WAIT狀态</code> SO_REUSEADDR提供如下四個功能: <code>SO_REUSEADDR允許啟動一個監聽伺服器并捆綁其衆所周知端口,即使以前建立的将此端口用做他們的本地端口的連接配接仍存在。這通常是重新開機監聽伺服器時出現,若不設定此選項,則bind時将出錯。</code> <code>SO_REUSEADDR允許在同一端口上啟動同一伺服器的多個執行個體,隻要每個執行個體捆綁一個不同的本地IP位址即可。對于TCP,我們根本不可能啟動捆綁相同IP位址和相同端口号的多個伺服器。</code> <code>SO_REUSEADDR允許單個程序捆綁同一端口到多個套接口上,隻要每個捆綁指定不同的本地IP位址即可。這一般不用于TCP伺服器。</code> <code>SO_REUSEADDR允許完全重複的捆綁:當一個IP位址和端口綁定到某個套接口上時,還允許此IP位址和端口捆綁到另一個套接口上。一般來說,這個特性僅在支援多點傳播的系統上才有,而且隻對UDP套接口而言(TCP不支援多點傳播)。</code> SO_REUSEPORT選項有如下語義: <code>此選項允許完全重複捆綁,但僅在想捆綁相同IP位址和端口的套接口都指定了此套接口選項才行。</code> <code>如果被捆綁的IP位址是一個多點傳播位址,則SO_REUSEADDR和SO_REUSEPORT等效。</code> 使用這兩個套接口選項的建議: <code>在所有TCP伺服器中,在調用bind之前設定SO_REUSEADDR套接口選項;</code> <code>當編寫一個同一時刻在同一主機上可運作多次的多點傳播應用程式時,設定SO_REUSEADDR選項,并将本組的多點傳播位址作為本地IP位址捆綁。</code> if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&nOptval , sizeof(int)) < 0) ...
關于SO_REUSEADDR的特性,網上介紹很多,這裡貼出幾個連結:
<a href="http://blog.csdn.net/zhongguoren666/article/details/8153271">SO_REUSEADDR例解</a>
<a href="http://blog.csdn.net/aspnet_lyc/article/details/37544421?utm_source=tuicool&utm_medium=referral">SO_REUSEADDR 套接字選項應用執行個體</a>
有了<code>SO_REUSEADDR</code>就好辦了,在剛斷開連接配接的時候NAT的映射還沒有被老化,而由于<code>SO_REUSEADDR</code>套接字選項的關系,也可以立馬進行下一次連接配接。也就是說隻要我們在NAT服務設定的老化時間内重建立立好連接配接,那麼這個映射就可以繼續使用。
從原理上來說應該是存在可行性的,如果有偏頗,忘指正。後續會嘗試搭建環境,寫程式做個試驗。
在這裡寫一下之前做的試驗:連接配接關閉之後NAT的端口映射直接失效,根本無法建立下一次連接配接,知識儲備還差點兒,太想當然了。