1. 程式人生 > >Linux socket跨區域網聊天和檔案傳輸

Linux socket跨區域網聊天和檔案傳輸

一直想寫一個跨區域網聊天和檔案傳輸,以及視訊聊天的軟體,這兩天剛好閒著沒啥事就把程式碼寫完了,程式碼已經上傳至github:https://github.com/vinllen/chat

其實之前想法P2P模式,P2P的話必須穿透NAT,現在的NAT有4種模式:

  1. 完全圓錐型NAT
  2. 受限圓錐型NAT
  3. 埠受限圓錐型NAT
  4. 對稱NAT(雙向NAT)
維基百科給出的定義如下:
  • 1.Full cone NAT,亦即著名的一對一(one-to-one)NAT

一旦一個內部地址(iAddr:port1)對映到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外傳送。任意外部主機都能通過給eAddr:port2發包到達iAddr:port1

  • 2.Address-Restricted cone NAT

一旦一個內部地址(iAddr:port1)對映到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外傳送。任意外部主機(hostAddr:any)都能通過給eAddr:port2發包到達iAddr:port1的前提是:iAddr:port1之前傳送過包到hostAddr:any. "any"也就是說埠不受限制


  • 3.Port-Restricted cone NAT
類似受限制錐形NAT(Restricted cone NAT),但是還有埠限制。

一旦一個內部地址(iAddr:port1)對映到外部地址(eAddr:port2),所有發自iAddr:port1的包都經由eAddr:port2向外傳送。一個外部主機(hostAddr:port3)能夠發包到達iAddr:port1的前提是:iAddr:port1之前傳送過包到hostAddr:port3.


  • 4.Symmetric NAT(對稱NAT)
每一個來自相同內部IP與埠,到一個特定目的地地址和埠的請求,都對映到一個獨特的外部IP地址和埠。同一內部IP與埠發到不同的目的地和埠的資訊包,都使用不同的對映
只有曾經收到過內部主機封包的外部主機,才能夠把封包發回


對於第1種特別簡單,因為埠存在對映,只要把包髮網出口路由的埠即可,路由會幫你轉發

對於第2,3種情況,可以採用如下辦法(內容來自該部落格:點選開啟連結):

假設網路模型如下:


限制性錐NAT 和埠限制性錐NAT (簡稱限制性NAT ),穿透限制性錐NAT 會丟棄它未知的源地址發向內部主機的資料包。所以如果現在ClientA-1 直接傳送UDP 資料包到ClientB-1 ,那麼資料包將會被NAT-B 無情的丟棄。所以採用下面的方法來建立ClientA-1 和ClientB-1 之間的通訊。

  • 1 .ClientA-1 (202.103.142.29:5000 )傳送資料包給Server ,請求和ClientB-1 (221.10.145.84:6000 )通訊。
  • 2. Server 將ClientA-1 的地址和埠(202.103.142.29:5000 )傳送給ClientB-1 ,告訴ClientB-1 ,ClientA-1 想和它通訊。
  • 3. ClientB-1 向ClientA-1 (202.103.142.29:5000 )傳送UDP 資料包,當然這個包在到達NAT-A 的時候,還是會被丟棄,這並不是關鍵的,因為傳送這個UDP 包只是為了讓NAT-B 記住這次通訊的目的地址:埠號,當下次以這個地址和埠為源的資料到達的時候就不會被NAT-B 丟棄,這樣就在NAT-B 上打了一個從ClientB-1 到ClientA-1 的孔。
  • 4. 為了讓ClientA-1 知道什麼時候才可以向ClientB-1 傳送資料,所以ClientB-1 在向ClientA-1 (202.103.142.29:5000 )打孔之後還要向Server 傳送一個訊息,告訴Server 它已經準備好了。
  • 5. Server 傳送一個訊息給ClientA-1 ,內容為:ClientB-1 已經準備好了,你可以向ClientB-1 傳送訊息了。
  • 6. ClientA-1 向ClientB-1 傳送UDP 資料包。這個資料包不會被NAT-B 丟棄,以後ClientB-1 向ClientA-1 傳送的資料包也不會被ClientA-1 丟棄,因為NAT-A 已經知道是ClientA-1 首先發起的通訊。至此,ClientA-1 和ClientB-1 就可以進行通訊了。
對於第4種情況,oh no,太難了,需要猜測埠號(部落格地址:點選開啟連結): 上面討論的都是怎樣穿透錐(Cone )NAT ,對稱NAT 和錐NAT 很不一樣。對於對稱NAT ,當一個私網內主機和外部多個不同主機通訊時,對稱NAT並不會像錐(Cone ,全錐,限制性錐,埠限制性錐)NAT那樣分配同一個埠。而是會新建立一個Session ,重新分配一個埠。參考上面穿透限制性錐NAT 的過程,在步驟3 時:ClientB-1 (221.10.145.84: ?)向ClientA-1 打孔的時候,對稱NAT 將給ClientB-1 重新分配一個埠號,而這個埠號對於Server 、ClientB-1 、ClientA-1 來說都是未知的。同樣, ClientA-1 根本不會收到這個訊息,同時在步驟4 ,ClientB-1 傳送給Server 的通知訊息中,ClientB-1 的socket 依舊是(221.10.145.84:6000 )。而且,在步驟6 時:ClientA-1 向它所知道但錯誤的ClientB-1 傳送資料包時,NAT-1 也會重新給ClientA-1 分配埠號。所以,穿透對稱NAT 的機會很小。下面是兩種有可能穿透對稱NAT 的策略。
1.同時開放TCP ( Simultaneous TCP open )策略
如果一個 對稱 NAT 接收到一個來自 本地 私有網 絡 外面的 TCP SYN 包, 這 個包想 發 起一個 “ 引入” 的 TCP 連 接,一般來 說 , NAT 會拒 絕這 個 連 接 請 求並扔掉 這 個 SYN 包,或者回送一個TCP RST (connection reset ,重建 連 接)包 給請 求方。但是,有一 種 情況 卻會接受這個“引入”連線。
RFC 規定:對於對稱NAT , 當 這 個接收到的 SYN 包中的源IP 地址 : 埠、目 標 IP 地址 : 埠都與NAT 登 記 的一個已 經 啟用的 TCP 會 話 中的地址資訊相符 時 , NAT 將會放行 這 個 SYN 包。 需要 特 別 指出 的是:怎樣才是一個已經啟用的TCP 連線?除了真正已經建立完成的TCP 連線外,RFC 規範指出: 如果 NAT 恰好看到一個 剛剛發 送出去的一個 SYN 包和 隨之 接收到的SYN 包中的地址 :埠 資訊相符合的 話 ,那 麼 NAT 將會 認為這 個 TCP 連 接已 經 被啟用,並將允 許這 個方向的 SYN 包 進 入 NAT 內部。 同時開放TCP 策略就是利用這個時機來建立連線的。
如果 Client A -1 和 Client B -1 能 夠 彼此正確的 預 知 對 方的 NAT 將會 給 下一個 TCP 連 接分配的公網 TCP 埠,並且兩個客 戶 端能 夠 同 時 地 發 起一 個面向對方的 “ 外出 ” 的 TCP 連 接 請求 ,並在 對 方的 SYN 包到達之前,自己 剛發 送出去的 SYN 包都能 順 利的穿 過 自己的 NAT 的 話 ,一條端 對 端的 TCP 連 接就 能 成功地建立了 。
2.UDP 埠猜測策略
同時開放TCP 策略非常依賴於猜測對方的下一個埠,而且強烈依賴於傳送連線請求的時機,而且還有網路的不確定性,所以能夠建立的機會很小,即使Server 充當同步時鐘的角色。下面是一種通過UDP 穿透的方法,由於UDP 不需要建立連線,所以也就不需要考慮“同時開放”的問題。
為了介紹ClientB-1 的詭計,先介紹一下STUN 協議。STUN (Simple Traversal of UDP Through NATs )協議是一個輕量級協議,用來探測被NAT 對映後的地址:埠。STUN 採用C/S 結構,需要探測自己被NAT 轉換後的地址:埠的Client 向Server 傳送請求,Server 返回Client 轉換後的地址:埠。
參考4.2 節中穿透NAT 的步驟2 ,當ClientB-1 收到Server 傳送給它的訊息後,ClientB-1 即開啟3 個socket 。socket-0 向STUN Server 傳送請求,收到回覆後,假設得知它被轉換後的地址:埠( 221.10.145.84:600 5 ),socket-1 向ClientA-1 傳送一個UDP 包,socket-2 再次向另一個STUN Server 傳送請求,假設得到它被轉換後的地址:埠( 221.10.145.84:60 20 )。通常,對稱NAT 分配埠有兩種策略,一種是按順序增加,一種是隨機分配。如果這裡對稱NAT 使用順序增加策略,那麼,ClientB-1 將兩次收到的地址:埠傳送給Server 後,Server 就可以通知ClientA-1 在這個埠範圍內猜測剛才ClientB-1 傳送給它的socket-1 中被NAT 對映後的地址:埠,ClientA-1 很有可能在孔有效期內成功猜測到埠號,從而和ClientB-1 成功通訊。
/************************************華麗分割線**********************************************************/ 以上內容大部分參考維基百科和chengweiv5的部落格,我自己也寫了程式碼,嘗試用第二種方式去穿透我們學校所在的NAT,結果悲劇了,找了好幾天的問題也沒找出來到底是程式碼的原因呢還是NAT限制為第4種的原因呢。 But專案不能這麼擱淺了。我採用了另外一條曲線救國的路線,就跟現在QQ模式一樣,用中轉伺服器進行訊息轉發實現。 1.首先搞了一臺具有公網ip的伺服器 2.配置好環境後(比如iptables等),把伺服器程式碼放置伺服器,執行 3.客戶端連線後就可以傳送檔案和聊天了 That's all,就這麼簡單。 唯一需要說明的就是傳送檔案時我呼叫的是sendfile的系統呼叫,接收檔案呼叫了Pat Patterson的包裹函式:利用splice接收檔案,理論上這兩個函式是直接在核心態進行檔案傳遞,而不用在核心態和普通態進行資料的拷貝,減少時間。我測試了一下,對幾M大小的檔案都沒問題,不過貌似檔案大了貌似傳不動了,目測sendfile有大小限制。。。這個就之後再改改了。 最後一點,本來還要實現視訊傳輸的,我的想法就是用opencv呼叫本地攝像頭,然後把圖片按每秒固定幀數截下來,按檔案方法傳輸,再在對端對圖片進行拼接成視訊。後來感覺有點麻煩,最後上網一查,果然我做的部分就是重造輪子的活,現在直接都有現成的檔案傳輸協議。這部分也等以後有時間再去完善吧。 Finally, 如果大家有誰有想法,可以和我交流交流,一起學習進步