使用socket完成udp通信

使用socket完成udp通信

來自專欄 編程學習筆記

上一篇筆記里分享了使用socket完成tcp網路傳輸的內容,詳見SOCKET套接字。在這一篇里主要分享使用套接字完成udp傳輸的具體步驟,udp和tcp在編程上的區別,tcp粘包現象的解決辦法等內容。

使用socket完成UDP通信,和tcp一樣,先寫一個服務端程序

一 服務端程序,#udp_server.py

步驟(列出需要用到的函數):

1.socket創建數據報套接字

2.bind綁定本機IP和埠

綁定時我們可以不用變數賦值這種方式,而是import sys模塊,sys.argv。

我們知道linux中ls是命令本身,$ls -l 中-l是命令選項,也叫命令行參數.

我們寫一個python程序

#argv.pyimport sysprint(sys.argv)

終端運行

$python3 argv.py -l music [argv.py,-l,music]

列印一個列表,argv.py是命令本身,-l是第一個參數,music是第二個參數,通過sys.argv列印出來的,也就是說sys模塊的argv這個屬性可以收集命令行的內容組成為一個列表,每一個元素是命令行中的一項。python3屬於解釋器名稱。需要注意接收的參數是字元串類型。

那我們如果需要將IP和埠不提前寫死,而是在調用是傳入呢?用input是程序運行起來了從控制台傳參,用命令行是在程序運行之前,也就是在調用時就傳進去。

$python3 argv.py 192.168.1.1 8888[argv.py,192.168.1.1,8888]

sys.agv[1]返回的是「192.168.1.1」

因此,可以在代碼中可以這樣綁定地址信息

HOST=sys.argv[1]PORT=int(sys.argv[2])ADDR=(HOST,PORT)

綁定過後就可以進行收發消息了

3.recvfrom接收信息

此時,不需要像tcp那樣,需要監聽listen和連接accept了,因為不涉及連接,監聽是為了 讓套接字能夠同時連接上多個客戶端,所以第三部分收發消息就可以了。

函數recvfrom(BUFFERSIZE)

功能:在UDP中接收消息

參數:表示一次最多可以接收多少位元組的消息

返回值:兩個,一個data表示接收到的消息,第二個是addr表示從哪個客戶端接收

到的消息。udp是誰發就接收誰的,不需要提前建立連接。沒人發時就阻塞。

另外,加入while True循環可以讓多次接收和回發。

注意:在tcp連接中是在accept連接時返回addr和一個新的套接字,tcp中的data返回值是在recv函數中。

4.sendto回發信息

回發消息函數sendto(data,addr)

功能:向一個網路終端發送消息

參數:data要發送的消息(注意消息必須是bytes類型)

addr要發送對象的地址

這裡因為沒有連接,並不知道具體要發給哪個客戶端,因此需要加入發給哪個客戶

端的地址,寫上一次接收到消息後返回的地址addr可以,寫其他地址也可以。

這裡我們讓它在發消息時指出當前時間。導入import time,在sendto中的data參數里調用time.ctime()來指明當前時間。

sockfd.sendto(("在%s接收到你的消息"%time.ctime()).encode(),addr),encode()是讓回發的信息轉為bytes類型。

5.close最後關閉套接字

sockfd.close(),關閉函數比較簡單,功能關閉套接字,無參數,無返回值。

伺服器端程序見下圖

這樣就把服務端寫完了,那我們能否用telnet測一下呢,不行,因為telnet是tcp連

接用的測試工具,所以我們來寫客戶端的程序

二 客戶端的程序 #udp_client.py

客戶端無論是tcp還是udp都不需要綁定,埠隨機分配就可以。

因為是客戶端,所以通常是先發後收。

具體步驟非常簡單,主要函數之前都講過,這裡不再細講:

1.創建套接字

2.消息收發

3.關閉套接字

客戶端套接字和伺服器端類型要相同,伺服器是循環接收,客戶端也一樣要加入while True,,不過客戶端要設置退出死循環的條件。伺服器端可以不用設置退出循環的語句(用ctrl+c就可以),客戶端退出後,服務端並不會隨之退出,因為沒有連接,服務端還可以等其他客戶端再發送消息。

發送的消息可以選擇從終端輸入,終端接收的字元串類型,因此需要用encode()轉化為bytes類型。

recvfrom()函數返回兩個值,data和addr,其中addr和發送sendto()的地址是一樣的,都是伺服器的那個地址。

運行一下,可以正常接收回發。按enter鍵,即輸入空時,客戶端退出循環,伺服器端還在等待。

現在我們實現了單個客戶端和伺服器的連接,但是如果伺服器啟動之後同一時刻多個客戶端發消息要怎麼收發呢。

我們之前在上一篇SOCKET套接字中講過tcp需要先建立連接,同一時刻只能先處理一個客戶端,如果要和多個客戶端通信,要不斷的建立連接再斷開。

那麼在udp下是可以的,我們可以試一下,在一個區域網內再找其他幾個機器,可以將自己的主機地址設為伺服器地址,將自己的伺服器端程序開啟,其他機器就可以連接上進行通信。因為伺服器端的recvfrom是誰發都行,所以這種循環服務UDP更合適,而tcp不合適。

客戶端程序見下圖。

三 tcp粘包現象的處理

什麼是tcp粘包,為什麼會出現粘包?

使用tcp傳輸,兩個網路進程在通信時,網路可能會有延遲,比如客戶端第一次send用戶名lilei,第二次send密碼1234,伺服器端需要接收這兩次信息,在系統中查看有無這個用戶名,密碼是否正確。如果發送端特別快發送了2次,服務端只執行了一次接收操作,假設目前緩衝區的大小是超過發送的所有內容之和的,因為消息流沒有邊界,很可能一次接收把兩次發送的內容都接收了lilei1234。第二次recv時取得是空,這樣在對比時發現沒有lilei1234這個用戶名,判斷登錄不了。然而實際發送是沒有問題的,也就是tcp傳輸中,套接字的方法recv會不斷得取得緩衝區中的內容,一次如果沒有拿完,那麼下次會繼續取沒拿完的東西。這個問題的產生是發送端發送若干次數據的時候,因為發送太快,消息以數據流傳輸,接收端一次全接收了,導致信息混在一起,這種現象就是tcp粘包現象,粘包也是tcp傳輸特有的現象。

發生粘包一定是壞事嗎?不一定。在上述情況下,粘包會影響操作,但是發送文件這種情況下粘包發生對實際操作不會產生影響,因為文件內容本身就是連續的,是個整體,所以發送粘包也沒有影響。

下面來演示一下這個粘包現象

模擬網路延遲,讓伺服器端在接收前sleep 5秒,在這5秒之內讓客戶端發送至少2次消息,即send 2次,看一下是否會出現粘包。如果把sleep去掉呢,試一下還是會粘包,加入sleep是為了讓粘包的現象必然發生,為了演示效果。

客戶端這邊也修改一下,第一次send的內容是從終端輸入的,第二次我們寫死,send(「this is second message」),運行看一下結果。

可以看到兩次send的內容連在一起了,具體細節大家可以自己試一下。

如何處理粘包?

1.將消息格式化。

因為send與recv對應,二者可以約定一個規則,比如用戶名必須佔多少個位元組,密碼必須是多少是位元組,比如規定一次完整消息就是64位元組,recv這邊的buffersize大小設置為64位元組,多了不接收。

2如果消息無法事先確定格式,發送消息的同時,發送一個消息長度標誌,客戶端能知道發送的消息大小,在消息之前加一個數字,對消息進行描述,服務端接收,解析消息,約定前多少個位元組表示消息長度。

3.讓消息的發送延遲,比如0.1秒等,這樣最簡單,給接收端足夠的時間的接收。如果網路不好,設置的發送延遲時間可能還不夠,不能完全避免粘包。而且發送很多次的內容時,就會影響效率了。

UDP傳輸的信息是數據報形式,套接字中的recvfrom一次只能接收一個數據包,即使設置的BUFFERSIZE很大,也不會多接收。如果設置的BUFFERSIZE比一個數據包的位元組數小呢,比如5,recvfrom就只會接收5個位元組,發送的數據包的其他位元組就丟失了。下次發送再接收時就沒有以前的消息。因此也就不存在粘包現象。而之前講的流式套接字不是,一次接收不完,會馬上從緩衝區中再繼續接收剩餘的,直到接收完。

為什麼會這樣呢?其實很好理解,因為使用UDP傳輸,兩端沒有建立連接,緩衝區中很有可能是別的客戶端發來的信息。所以不能再去緩衝區中接收,如果接收不完只能丟掉。

推薦閱讀:

通信行業的前景在哪裡?何時才能回暖?
通信人需要考哪些「證書」?
SVC和視頻通信
通信設備&光器件市場發展趨勢簡析(1)
Y2T10 光譜寬度的幾種描述--RMS、FWHM、-20dB

TAG:網路協議 | 通信 | TCP |