一直以來,提及NAT穿透,不少人都會被告知使用UDP打孔這個技術,基本上沒有人會告訴你如何使用TCP協議去穿透(甚至有的人會直接告訴你TCP協議是沒法實現穿透的)。可是,衆所周知的是,UDP是一個無鏈接的數據報協議,使用它就必須本身維護收發數據包的完整性,這經常會大大增長程序的複雜度,並且一些程序因爲某些緣由,必須使用TCP協議,這樣就經常令一些開發TCP網絡程序的人員「談穿透色變」。那麼,使用TCP協議是否是就不能實現穿透呢?答案固然是否認的:TCP協議不只能實現NAT穿透,並且實現起來比UDP穿透甚至還簡單一些。 服務器
要了解如何使用TCP穿透NAT,就要首先看看如何使用UDP穿透NAT。
咱們假設在兩個不一樣的局域網後面分別有2臺客戶機A和 B,AB所在的局域網都分別經過一個路由器接入互聯網。互聯網上有一臺服務器S。 網絡
如今AB是沒法直接和對方發送信息的,AB都不知道對方在互聯網上真正的IP和端口, AB所在的局域網的路由器只容許內部向外主動發送的信息經過。對於B直接發送給A的路由器的消息,路由會認爲其「不被信任」而直接丟棄。 併發
要實現 AB直接的通信,就必須進行如下3步:A首先鏈接互聯網上的服務器S併發送一條消息(對於UDP這種無鏈接的協議其實直接初始會話發送消息便可),這樣S就獲取了A在互聯網上的實際終端(發送消息的IP和端口號)。接着 B也進行一樣的步驟,S就知道了AB在互聯網上的終端(這就是「打洞」)。接着S分別告訴A和B對方客戶端在互聯網上的實際終端,也即S告訴A客戶B的會話終端,S告訴B客戶A的會話終端。這樣,在AB都知道了對方的實際終端以後,就能夠直接經過實際終端發送消息了(由於先前雙方都向外發送過消息,路由上已經有容許數據進出的消息通道)。異步
用UDP來實現以上3步不存在什麼理論上的問題,由於UDP是無鏈接的協議,它容許socket進行「多對一」的通信(即幾個具備不一樣IP和端口號的socket向一個接收socket發送消息)。可是使用TCP就出現了問題:在通常狀況下,TCP socket不容許在已經創建鏈接的端口上再進行監聽和使用該本地端口。換句話說,當AB鏈接上服務器S後,S將AB的實際終端告訴對方,下一步本該是AB利用對方的實際終端進行直連,但這時你會發現對方的實際終端已經被佔用了(就是各自鏈接到服務器S的會話佔用了終端),沒法同時listen和 connect。因而不少人得出結論:TCP沒法實現NAT穿透。 socket
因而問題的關鍵變成了如何複用一個TCP鏈接的本地終端,這其實不是協議的問題,而是一個API的問題。幸運的是,全部主流操做系統都支持一個特定的TCP套接字選項——SO_REUSEADDR。這個選項容許將多個socket綁定到同一個本地終端。咱們創建socket的時候只要加上這麼一行:函數
知道上面的知識就很好辦了,下面我來講說TCP協議的穿透流程:
機器佈局仍是和上面使用UDP的同樣。如今假設客戶A想和客戶B創建TCP鏈接。
首先仍是 AB分別和服務器S分別創建鏈接,S記錄AB的互聯網實際終端。而後S分別向AB發送對方的實際終端。接着,從A和B向S鏈接時使用的端口,AB都異步調用connect函數鏈接對方的實際終端(就是S告訴的終端),同時,AB雙方都在同一個本地端口監聽到來的鏈接(也能夠先監聽,再connect更好)。因爲雙方都向對方發送了connect請求(假設各自的SYN封包已經穿過了本身的NAT),所以在對方connect請求到達本地的監聽端口時,路由器會認爲這個請求是剛剛那個connect會話的一部分,是已經被許可的,本地監聽端口就會用SYN-ACK響應,贊成鏈接。這樣,TCP穿透NAT的點對點鏈接就成功了。佈局
下面是示例代碼下載,VB.NET代碼,演示如何用TCP協議穿透NAT實現文件傳送,請用vs2005打開解決方案測試
http://dl2.csdn.net/down4/20070724/24133943521.rar spa
代碼中有一個我本身封裝的模仿vb6 winsock的控件ZXMSocket,這個socket可讓你設置是否使用SO_REUSEADDR參數,socket是事件驅動的。操作系統
若是你要測試代碼,須要使用一個bat來啓動發送和接收程序(文件格式請參照bin/Debug文件夾下的run.bat文件),這個bat的功能是以命令行的方式告訴程序登陸服務器縮使用的用戶名,對於服務器來講,這個用戶名必須是惟一的,固然,這可能有點不科學,可是這畢竟只是一個demo。
出處:http://blog.csdn.net/hk_5788/article/details/49952735