tcp穿透兩個局域網連接

原文鏈接:https://www.cnblogs.com/mq0036/p/6589955.html

 

使用TCP協議的NAT穿透技術

    其實很早我就已經實現了使用TCP協議穿透NAT了,但是苦於一直沒有時間,所以沒有寫出來,現在終於放假有一點空閒,於是寫出來共享之。


    一直以來,說起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穿透。 


    於是問題的關鍵變成了如何複用一個TCP連接的本地終端,這其實不是協議的問題,而是一個API的問題。幸運的是,所有主流操作系統都支持一個特定的TCP套接字選項——SO_REUSEADDR。這個選項允許將多個socket綁定到同一個本地終端。我們建立socket的時候只要加上這麼一行:

setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &flag, len) ;   //C++就這麼做

 

_Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, True)  '這是vb.net 更加簡單


    知道上面的知識就很好辦了,下面我來說說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 

代碼中有一個我自己封裝的模仿vb6 winsock的控件ZXMSocket,這個socket可以讓你設置是否使用SO_REUSEADDR參數,socket是事件驅動的。

如果你要測試代碼,需要使用一個bat來啓動發送和接收程序(文件格式請參照bin/Debug文件夾下的run.bat文件),這個bat的功能是以命令行的方式告訴程序登錄服務器縮使用的用戶名,對於服務器來說,這個用戶名必須是唯一的,當然,這可能有點不科學,但是這畢竟只是一個demo。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章