1. 程式人生 > >C# UDP通訊

C# UDP通訊

最近剛搞完一個TCP通訊的專案,但是發覺還是UDP+TCP的通訊方式效率穩定性比較高。所以又看了看關於UDP通訊的內容,

UDP通訊簡介:

UDP將網路資料流量壓縮成資料報的形式,每一個數據報用8個位元組(8 X 8位=64位)描述報頭資訊,剩餘位元組包含具體的傳輸資料。UDP報頭(只有8個位元組)相當於TCP的報頭(至少20個位元組)很短,UDP報頭由4個域組成,每個域各佔2個位元組,具體為源埠、目的埠、使用者資料報長度和校驗和,


關於UDP通訊的兩種方式:

1、socket通訊

2、udpClient通訊

1、同步

socket.Send();

socket.Recevie();

同步阻塞:

預設的同步通訊時receive()會阻塞,掛起等待遠端主機的訊息;

同步非阻塞:

2、非同步

socket.beginSend();

socket.beginReceive();

回撥函式何時回撥?

關於UDP穿透NAT,NAT的型別:

1.       NAT分類
根據Stun協議(RFC3489),NAT大致分為下面四類
1)      Full Cone
這種NAT內部的機器A連線過外網機器C後,NAT會開啟一個埠.然後外網的任何發到這個開啟的埠的UDP資料報都可以到達A.不管是不是C發過來的.
例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)


任何傳送到 NAT(202.100.100.100:8000)的資料都可以到達A(192.168.8.100:5000)
2)      Restricted Cone
這種NAT內部的機器A連線過外網的機器C後,NAT開啟一個埠.然後C可以用任何埠和A通訊.其他的外網機器不行.
例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
任何從C傳送到 NAT(202.100.100.100:8000)的資料都可以到達A(192.168.8.100:5000)
3)      Port Restricted Cone

這種NAT內部的機器A連線過外網的機器C後,NAT開啟一個埠.然後C可以用原來的埠和A通訊.其他的外網機器不行.
例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
C(202.88.88.88:2000)傳送到 NAT(202.100.100.100:8000)的資料都可以到達A(192.168.8.100:5000)
以上三種NAT通稱Cone NAT.我們只能用這種NAT進行UDP打洞.
4)      Symmetic
對於這種NAT.連線不同的外部目標.原來NAT開啟的埠會變化.而Cone NAT不會.雖然可以用埠猜測.但是成功的概率很小.因此放棄這種NAT的UDP打洞.
2.       UDP hole punching
對於Cone NAT.要採用UDP打洞.需要一個公網機器C來充當”介紹人”.內網的A,B先分別和C通訊.開啟各自的NAT埠.C這個時候知道A,B的公網IP:Port. 現在A和B想直接連線.比如A給B發.除非B是Full Cone.否則不能通訊.反之亦然.但是我們可以這樣.
A要連線B.A給B發一個UDP包.同時.A讓那個介紹人給B發一個命令,讓B同時給A發一個UDP包.這樣雙方的NAT都會記錄對方的IP,然後就會允許互相通訊.
3.       同一個NAT後面的情況
如果A,B在同一個NAT後面.如果用上面的技術來進行互連.那麼如果NAT支援loopback(就是本地到本地的轉換),A,B可以連線,但是比較浪費頻寬和NAT.有一種辦法是,A,B和介紹人通訊的時候,同時把自己的local IP也告訴伺服器.A,B通訊的時候,同時發local ip和公網IP.誰先到就用哪個IP.但是local ip就有可能不知道發到什麼地方去了.比如A,B在不同的NAT後面但是他們各自的local ip段一樣.A給B的local IP發的UDP就可能發給自己內部網裡面的某某某了.

關於UDP通訊穿透NAT的流程:

思路如下(參照原始碼):

  1、 frmServer啟動兩個網路偵聽,主連線偵聽,協助打洞的偵聽。

  2、 frmClientA和frmClientB分別與frmServer的主連線保持聯絡。

  3、 當frmClientA需要和frmClientB建立直接的udp連線時,首先連線frmServer的協助打洞埠,併發送協助連線申請,同時在該埠號上啟動偵聽。

     4、  frmServer的協助打洞連線收到frmClientA的申請後通過主連線通知frmClientB,並將frmClientA經過NAT-A轉換後的公網IP地址和埠等資訊告訴frmClientB。

  5、 frmClientB收到frmServer的連線通知後首先與frmServer的協助打洞埠連線,傳送一些資料後立即斷開,目的是讓frmServer能知道frmClientB經過NAT-B轉換後的公網IP和埠號。

  6、 frmClientB嘗試與frmClientA的經過NAT-A轉換後的公網IP地址和埠進行connect,不同的路由器會有不同的結果,多數路由器對未知不請自到的SYN請求包直接丟棄而導致connect失敗,但NAT-A會紀錄此次連線的源地址和埠號,為接下來真正的連線做好了準備,這就是所謂的打洞,即frmClientB向frmClientA打了一個洞,下次frmClientA就能直接連線到frmClientB剛才使用的埠號了。

  7、 客戶端frmClientB打洞的同時在相同的埠上啟動偵聽。frmClientB在一切準備就緒以後通過與frmServer的主連接回復訊息“可以了,已經準備”,frmServer在收到以後將frmClientB經過NAT-B轉換後的公網IP和埠號告訴給frmClientA。

  8、 frmClientA收到frmServer回覆的frmClientB的公網IP和埠號等資訊以後,開始連線到frmClientB公網IP和埠號,由於在步驟6中frmClientB曾經嘗試連線過frmClientA的公網IP地址和埠,NAT-A紀錄了此次連線的資訊,所以當frmClientA主動連線frmClientB時,NAT-B會認為是合法的SYN資料,並允許通過,從而直接的udp連線建立起來了。

3、STUN協議

簡單的說STUN協議就是用來協助檢測本機的內網IP是否與外網IP一致以及檢測NAT型別,以便判斷是否能夠UDP打洞。網上有STUN伺服器可以提供檢測。

結論:
1、採用TCP通訊時,客戶端不需要bind()他自己的IP和埠號,而伺服器必須要bind()自己本機的IP和埠號;
2、若採用UDP通訊時(這裡是有客戶端和伺服器之分才這麼說的,若是指定特定埠的UDP對等通訊則不一樣了),客戶端可以也不需要bind()他自己的IP和埠號,而伺服器需要bind自己IP地址和埠號;


原因:

1、
因為伺服器是時時在監聽有沒有客戶端的連線,如果伺服器不繫結IP和埠的話,客戶端上線的時候怎麼連到伺服器呢,所以伺服器要繫結IP和埠,而客戶端就不需要了,客戶端上線是主動向伺服器發出請求的,因為伺服器已經綁定了IP和埠,所以客戶端上線的就向這個IP和埠發出請求,這時因為客戶開始發資料了(發上線請求),系統就給客戶端分配一個隨機埠,這個埠和客戶端的IP會隨著上線請求一起發給伺服器,服務收到上線請求後就可以從中獲起發此請求的客戶的IP和埠,接下來伺服器就可以利用獲起的IP和埠給客戶端迴應訊息了。

2、採用UDP通訊,
1)若有客戶端和伺服器之分的程式,建立sock後即可在該socket上用recvfrom/sendto方法傳送接受資料了,因為客戶端只需要用sendto傳送資料到指定的地址,當然若是bind了,程式也沒什麼問題,區別就是系統用預設自動bind()指定你自己的socket引數地址(特別是在指定特定埠的UDP對等通訊)只是這種情況沒有這樣用的。
那UDP伺服器是怎麼知道客戶端的IP地址和UDP埠?
一般來說有兩種方式:
一種是客戶端發訊息顯示顯式地告訴伺服器IP地址和埠,訊息內容就包括IP地址和UDP埠。
另外一種就是隱式的,伺服器從收到的包的頭部中得到包的源IP地址和埠。


2)若是沒有客戶端和伺服器之分的程式,即自己指定特定埠的UDP對等通訊,則客戶端和伺服器都需要bind()IP地址和埠了。
通常udp服務端根本不需要知道客戶端的socket,它直接建立一個socket用於傳送即可,udp通訊的關鍵只在於IP和埠。
多個客戶端如果需要點到點分發,必須給服務端socket迴圈設定每個客戶端的IP併發出,但更常用的是廣播分發,服務端socket設定一個X.X.X.255的廣播地址並始終向它傳送,每個客戶端建立的socket只需要繫結這個廣播地址便可以收到。