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