- Python高效開發(fā)實戰(zhàn):Django、Tornado、Flask、Twisted(第3版)
- 劉長龍
- 2151字
- 2021-10-15 17:52:54
2.3 Socket編程
除了基于HTTP等標準協(xié)議的Web應(yīng)用,Internet上還有很大一部分應(yīng)用是基于非公有協(xié)議的。無論使用哪種語言進行非標準協(xié)議的開發(fā),都需要了解Socket編程的基本知識。本節(jié)學(xué)習(xí)Socket的概念及用Socket進行TCP、UDP開發(fā)的方法。
注意:本節(jié)介紹的Socket知識不僅可用于Python網(wǎng)絡(luò)編程,同樣適用于其他所有編程語言。
2.3.1 Socket基礎(chǔ)
Socket原指“孔”或“插座”,它最初作為BSD UNIX的進程通信機制,通常被稱作“套接字”。當然,Socket是一個通信鏈的句柄,如今已經(jīng)是Windows和macOS等其他操作系統(tǒng)所共同遵守的網(wǎng)絡(luò)編程標準,用于描述IP地址和端口,可以用來實現(xiàn)不同虛擬機或不同計算機之間的通信,當然也可以實現(xiàn)相同主機內(nèi)的不同進程間的通信。Internet上的主機一般運行了多個服務(wù)軟件,同時提供幾種服務(wù),每種服務(wù)都打開一個Socket,并綁定到一個端口上,不同的端口對應(yīng)不同的服務(wù)。
在操作系統(tǒng)結(jié)構(gòu)上,Socket為應(yīng)用程序屏蔽了TCP/IP網(wǎng)絡(luò)傳輸層及以下的網(wǎng)絡(luò)細節(jié),如圖2.6所示。Socket為操作系統(tǒng)的用戶空間提供網(wǎng)絡(luò)抽象,開發(fā)者編寫的網(wǎng)絡(luò)程序都會直接或間接地用到Socket抽象。通過Socket抽象可以控制傳輸層協(xié)議TCP和UDP,甚至包括部分網(wǎng)絡(luò)層協(xié)議,如IP和ICMP。

圖2.6 Socket抽象
注意:本書只涉及Socket的TCP和UDP編程。
Socket使用“IP地址+端口+協(xié)議”的三元組唯一標識一個通信鏈路。服務(wù)器端的一個通信鏈路可以對應(yīng)于多個客戶端,例如,一個Web服務(wù)器的80端口可以同時服務(wù)大量的客戶端。
2.3.2 實戰(zhàn)演練1:Socket TCP原語
用Socket進行網(wǎng)絡(luò)開發(fā)需了解服務(wù)器和客戶端的Socket原語,每個原語在不同的高級語言中都有相應(yīng)的實現(xiàn)方式。TCP的Socket原語如圖2.7所示。所有基于TCP的Socket通信都遵循如圖2.7所示的流程。下面解釋每個原語的含義。

圖2.7 TCP的Socket原語
? socket():建立Socket對象。Socket是以類似文件系統(tǒng)的“打開、讀寫、關(guān)閉”的模式設(shè)計的,socket()原語相當于“打開”。socket()原語的參數(shù)通常包括使用的傳輸層協(xié)議類型、網(wǎng)絡(luò)層地址類型等。
? bind():綁定。在參數(shù)中需要傳入要綁定的IP地址和端口。IP地址必須是主機上的一個可用的地址(除用0.0.0.0指定綁定所有的本機IP外)。端口必須是一個該Socket協(xié)議未被占用的端口,例如,當一個主機上的兩個程序試圖同時綁定到80端口時,只有一個程序能夠成功。服務(wù)器端程序在listen()之前必須進行bind()操作,而客戶端程序如果在connect()原語之前沒有調(diào)用bind(),則系統(tǒng)會自動為該Socket分配一個未被占用的地址和端口。
技巧:當主機上存在多個IP地址時,綁定地址0.0.0.0可以監(jiān)聽所有這些可用的IP地址。
? listen():監(jiān)聽。只在服務(wù)器端有用,告訴操作系統(tǒng)開始監(jiān)聽之前綁定的IP地址和端口,可以在參數(shù)中指定允許排隊的最大連接數(shù)量。
? connect():在客戶端連接服務(wù)器。參數(shù)中需要指定服務(wù)器的地址和端口。調(diào)用connect()可能有兩種結(jié)果,即與服務(wù)器端完成TCP 3次握手并建立連接或者連接服務(wù)器失敗。
? accept():接收連接。只在服務(wù)器端有用,從監(jiān)聽到的連接中取出一個,并將其包裝成一個新的Socket對象。這個新的Socket對象可被用于和相應(yīng)的客戶端進行通信。完成accept()標志著Socket已經(jīng)完成了TCP鏈路建立階段的3次握手。如果當前沒有客戶端連接請求,則accept()調(diào)用會進入阻塞等待狀態(tài)。
? send():發(fā)送數(shù)據(jù)。服務(wù)器和客戶端均可調(diào)用send()向?qū)Ψ桨l(fā)送數(shù)據(jù),在send()的參數(shù)中傳入要發(fā)送的數(shù)據(jù),通過send()的返回值判斷數(shù)據(jù)是否發(fā)送成功。
? recv():接收數(shù)據(jù)。服務(wù)器和客戶端均可調(diào)用recv()從對方接收數(shù)據(jù)。如果Socket中沒有消息可以讀取,則在默認情況下recv()調(diào)用會被阻塞直到有消息到達;開發(fā)者也可以將Socket設(shè)置為非阻塞模式,使recv()以失敗形式返回。
? close():關(guān)閉連接。通信中的任何一方都可以調(diào)用close()發(fā)起關(guān)閉連接請求,另一方收到后也調(diào)用close()關(guān)閉連接。
【示例2-1】下面通過Python代碼演示Socket編程方法,TCP服務(wù)器端的代碼如下:

包socket封裝了所有Python的原生Socket操作,代碼中通過socket()、bind()、listen()的一系列調(diào)用實現(xiàn)了對指定端口的監(jiān)聽,通過accept()接收客戶端的連接,當有客戶端連接成功后將當前系統(tǒng)時間發(fā)送給客戶端,并馬上關(guān)閉連接。因為代碼主體處于while循環(huán)中,所以程序?qū)⒉粩啾O(jiān)聽并一直運行。
注意:send()函數(shù)接收的參數(shù)為bytes類型,因此在調(diào)用該函數(shù)時需要將字符串參數(shù)通過.encode(‘utf-8’)方法轉(zhuǎn)換為bytes類型。
與該服務(wù)器端的代碼相對應(yīng)的客戶端的代碼如下:

客戶端通過connect()調(diào)用連接服務(wù)器,連接成功后接收從服務(wù)器發(fā)來的數(shù)據(jù),然后關(guān)閉連接、退出程序。
現(xiàn)在嘗試查看服務(wù)器端與客戶端通信的執(zhí)行效果,首先啟動服務(wù)器端程序:

服務(wù)器端程序?qū)⑦M入等待連接狀態(tài)。然后打開另外一個控制臺,執(zhí)行客戶端程序:

從以上輸出中已經(jīng)可以看到服務(wù)器端發(fā)送過來的當前時間,說明已經(jīng)成功進行通信。同時,在服務(wù)器窗口中可以看到如下輸出結(jié)果:

注意:客戶端的Socket端口號由系統(tǒng)自動分配。
2.3.3 實戰(zhàn)演練2:Socket UDP原語
UDP相對于TCP在傳輸層提供更少的控制,沒有建立連接、斷開連接等概念,所以基于UDP的Socket通信過程也比TCP稍微簡單一些。在UDP中可以直接通過指定IP:Port進行數(shù)據(jù)收發(fā)。UDP Socket可以復(fù)用TCP中的socket()和bind()原語,除此之外,UDP有如下兩個屬于自己的Socket原語。
? recvfrom ():從綁定的地址接收數(shù)據(jù)。
? sendto ():向指定的地址發(fā)送數(shù)據(jù),在調(diào)用的參數(shù)中應(yīng)該傳入通信對端的地址和端口。
【示例2-2】UDP的Python服務(wù)器端的代碼示例如下:

代碼通過socket()和bind()調(diào)用綁定了本地所有地址的3434端口,通過socket()中的SOCK_DGRAM指定Socket使用UDP,在一個循環(huán)中不斷地接收數(shù)據(jù)并打印。相應(yīng)的UDP客戶端的Python代碼如下:


客戶端直接調(diào)用sendto()向指定的地址發(fā)送數(shù)據(jù)。
與TCP類似,在啟動客戶端之前同樣需要先運行服務(wù)器端程序:

現(xiàn)在執(zhí)行客戶端程序,執(zhí)行結(jié)果如下:

相應(yīng)的服務(wù)器端執(zhí)行結(jié)果如下,其中的客戶端端口54525由客戶端程序在調(diào)用sendto()時自動生成:

- Learning Chef
- Hands-On Data Structures and Algorithms with JavaScript
- Visual Basic程序設(shè)計實驗指導(dǎo)(第二版)
- Go語言精進之路:從新手到高手的編程思想、方法和技巧(2)
- Java程序設(shè)計案例教程
- Web前端開發(fā)技術(shù):HTML、CSS、JavaScript
- Applied Deep Learning with Python
- Selenium WebDriver Practical Guide
- R語言數(shù)據(jù)分析從入門到實戰(zhàn)
- MonoTouch應(yīng)用開發(fā)實踐指南:使用C#和.NET開發(fā)iOS應(yīng)用
- 自己動手做智能產(chǎn)品:嵌入式JavaScript實現(xiàn)
- Hands-On GUI Application Development in Go
- 測試基地實訓(xùn)指導(dǎo)
- Nginx Troubleshooting
- 響應(yīng)式編程實戰(zhàn):構(gòu)建彈性、可伸縮、事件驅(qū)動的分布式系統(tǒng)