Python中的Socket編程,全掌握!
在如今的互聯(lián)網(wǎng)當中,Socket 協(xié)議是最重要的基礎(chǔ)之一。本文涵蓋了在 Python 中處理 Socket 編程的所有領(lǐng)域。
為什么使用 Sockets
Sockets 是組成當今網(wǎng)絡(luò)的各種通信協(xié)議,這些協(xié)議使得在兩個不同的程序或設(shè)備之間傳輸信息成為可能。例如,當我們打開瀏覽器時,我們作為客戶機就會創(chuàng)建與服務(wù)器的連接以傳輸信息。
在深入研究這個通信原理之前,讓我們先弄清楚 Sockets 到底是什么。
什么是 Sockets
一般來說,Socket 是為發(fā)送和接收數(shù)據(jù)而構(gòu)建的內(nèi)部應(yīng)用協(xié)議。單個網(wǎng)絡(luò)將有兩個 Sockets,每個 Sockets 用于通信設(shè)備或程序,這些 Sockets 是IP地址和端口的組合。根據(jù)使用的端口號,單個設(shè)備可以有“n”個 Sockets,不同的端口可用于不同類型的協(xié)議。
下圖展示了一些常見端口號和相關(guān)協(xié)議的信息:
協(xié)議 | 端口號 | Python 庫 | 應(yīng)用 |
HTTP | 80 | httplib,urllib,requests | 網(wǎng)頁,網(wǎng)站 |
FTP | 20 | ftplib | 文件傳輸 |
NNTP | 119 | nttplib | 新聞傳輸 |
SMTP | 25 | smtplib | 發(fā)送郵件 |
Telnet | 23 | telnetlib | 命令行 |
POP3 | 110 | poplib | 接收郵件 |
Gopher | 70 | gopherlib | 文檔傳輸 |
現(xiàn)在我們已經(jīng)了解了 Sockets 的概念,現(xiàn)在讓我們來看看 Python 的 Socket 模塊
如何在 Python 中實現(xiàn) Socket 編程
要在 Python 中實現(xiàn) Socket 編程,需要導(dǎo)入 socket 模塊。
該模塊的一些重要方法如下:
方法 | 描述 |
socket.socket() | 用于創(chuàng)建 socket(服務(wù)器端和客戶端都需要創(chuàng)建) |
socket.accept() | 用于接受連接。它返回一對值(conn,address),其中conn是用于發(fā)送或接收數(shù)據(jù)的新 socket 對象,address是連接另一端的 socket 地址 |
socket.bind() | 用于綁定到指定為參數(shù)的地址 |
socket.close() | 用于關(guān)閉 socket |
socket.connect() | 用于連接到指定為參數(shù)的遠程地址 |
socket.listen() | 使服務(wù)器能夠接受連接 |
現(xiàn)在我們已經(jīng)了解了 socket 模塊的重要性,接下來讓我們看看如何在 Python 中建服務(wù)器和客戶機。
什么是服務(wù)器
服務(wù)器或者是一個程序、一臺計算機,或者是一臺專門用于管理網(wǎng)絡(luò)資源的設(shè)備。服務(wù)器可以位于同一設(shè)備或計算機上,也可以本地連接到其他設(shè)備和計算機,甚至可以遠程連接。有各種類型的服務(wù)器,如數(shù)據(jù)庫服務(wù)器、網(wǎng)絡(luò)服務(wù)器、打印服務(wù)器等。
服務(wù)器通常使用socket.socket(),socket.bind(),socket.listen()等來建立連接并綁定到客戶端,現(xiàn)在讓我們編寫一個程序來創(chuàng)建服務(wù)器。
創(chuàng)建 socket 的第一個必要條件是導(dǎo)入相關(guān)模塊。之后是使用socket.socket()方法創(chuàng)建服務(wù)器端 socket。
AF_INET 是指來自 Internet 的地址,它需要一對(主機、端口),其中主機可以是某個特定網(wǎng)站的 URL 或其地址,端口號為整數(shù)。SOCK_STREAM 用于創(chuàng)建 TCP 協(xié)議。
bind()?方法接受兩個參數(shù)作為元組(主機、端口)。這里需要注意的是最好使用4位的端口號,因為較低的端口號通常被占用或者是系統(tǒng)預(yù)留的。listen()方法允許服務(wù)器接受連接,5是同時接受的多個連接的隊列。此處可以指定的最小值為0,如果未指定參數(shù),則采用默認的合適參數(shù)。
while?循環(huán)允許永遠接受連接,clt和adr?是客戶端對象和地址,print?語句只是打印出客戶端 socket 的地址和端口號,最后,clt.send用于以字節(jié)為單位發(fā)送數(shù)據(jù)。
現(xiàn)在我們的服務(wù)器已經(jīng)設(shè)置好了,讓我們繼續(xù)向客戶機前進。
什么是客戶端
客戶端是從服務(wù)器接收信息或服務(wù)的計算機或軟件。在客戶端-服務(wù)器模型中,客戶端從服務(wù)器請求服務(wù)。最好的例子是 Google Chrome、Firefox 等 Web 瀏覽器,這些 Web 瀏覽器根據(jù)用戶的指示請求 Web 服務(wù)器提供所需的網(wǎng)頁和服務(wù)。其他示例包括在線游戲、在線聊天等。
現(xiàn)在,讓我們看看如何用 Python 編程語言編寫客戶端程序:
首先依然是導(dǎo)入 socket 模塊,然后像創(chuàng)建服務(wù)器時那樣創(chuàng)建套接字。接著要在客戶端服務(wù)器之間創(chuàng)建連接,需要通過指定(host,port)使用 connect()方法。
注意:當客戶端和服務(wù)器位于同一臺計算機上時,需要使用gethostname。(LAN–localip/WAN–publicip)
在這里,客戶端希望從服務(wù)器接收一些信息,為此,我們需要使用recv()?方法,信息存儲在另一個變量msg中。需要注意的是正在傳遞的信息將以字節(jié)為單位,在上述程序的客戶端中,一次傳輸最多可以接收1024字節(jié)(緩沖區(qū)大?。?。根據(jù)傳輸?shù)男畔⒘?,可以將其指定為任意?shù)量。
最后,再解碼并打印正在傳輸?shù)南ⅰ?/p>
現(xiàn)在我們已經(jīng)了解了如何創(chuàng)建客戶端-服務(wù)器程序,接下來讓我們看看它們需要如何執(zhí)行。
客戶端服務(wù)器交互
要執(zhí)行這些程序,需要打開命令程序,進入創(chuàng)建客戶端和服務(wù)器程序的文件夾,然后鍵入:
不出意外服務(wù)器開始運行
要執(zhí)行客戶端,需要打開另一個cmd窗口,然后鍵入:
下面讓我們將緩沖區(qū)大小減少到7,來看看相同的程序會怎么樣
如圖所示,傳輸7個字節(jié)后,連接終止。
其實這是一個問題,因為我們尚未收到完整的信息,但是連接卻提前關(guān)閉了,下面讓我們來解決這個問題。
多重通信
為了在客戶端收到完整信息之前繼續(xù)連接,我們可以使用while循環(huán)
如此修改之后,每次傳輸將以7個字節(jié)接收完整消息。
但這又引來了另一個問題,連接永遠不會終止,你永遠不知道什么時候會終止。此外,如果我們實際上不知道客戶端將從服務(wù)器接收到的消息或信息有多大,該怎么辦。在這種情況下,我們需要繼續(xù)完善代碼
在服務(wù)器端,使用close()方法,如下所示:
輸出如下:
程序會檢查信息的大小,并將其打印到一次兩個字節(jié)的緩沖區(qū)中,然后在完成連接后關(guān)閉連接。
傳輸 Python 對象
目前為止我們僅僅掌握了傳遞字符串的方法,但是,Python 中的 Socket 編程也允許我們傳輸 Python 對象。這些對象可以是集合、元組、字典等。要實現(xiàn)這一點,需要用到 Python 的 pickle 模塊。
Python pickle模塊
當我們實際序列化或反序列化 Python 中的對象時,就會使用到 Python pickle 模塊。讓我們看一個小例子
Output:
在上面的程序中,mylist?是使用pickle模塊的dumps()?函數(shù)序列化的。還要注意,輸出以b開頭,表示它已轉(zhuǎn)換為字節(jié)。在 socket 編程中,可以實現(xiàn)此模塊以在客戶端和服務(wù)器之間傳輸 python 對象。
如何使用 pickle 模塊傳輸 Python 對象
當我們將 pickle 與 socket 一起使用時,完全可以通過網(wǎng)絡(luò)傳輸任何內(nèi)容。
先來看看服務(wù)端代碼
Server-Side:
這里,m?是一個字典,它基本上是一個需要從服務(wù)器發(fā)送到客戶端的 Python 對象。這是通過首先使用dumps()序列化對象,然后將其轉(zhuǎn)換為字節(jié)來完成的。
現(xiàn)在,讓我們記下客戶端:
Client-Side:
第一個while循環(huán)將幫助我們跟蹤完整的消息(complete_info)以及正在使用緩沖區(qū)接收的消息(rec_msg)。
然后,在接收消息時,我們所做的就是打印每一位消息,并將其放在大小為10的緩沖區(qū)中接收。此大小可以是任何大小,具體取決于個人選擇。
然后如果收到的消息等于完整消息,我們只會將消息打印為收到的完整信息,然后使用loads()反序列化消息。
輸出如下:
? ?