在現今的互聯網當中,Socket 協定是最重要的基礎之一。本文涵蓋了在 Python 中處理 Socket 程式設計的所有領域。
Sockets 是組成當今網路的各種通訊協議,這些協議使得在兩個不同的程式或設備之間傳輸訊息成為可能。例如,當我們開啟瀏覽器時,我們作為客戶機就會建立與伺服器的連線以傳輸訊息。
在深入研究這個溝通原理之前,讓我們先弄清楚 Sockets 到底是什麼。
一般來說,Socket 是為發送和接收資料而建構的內部應用協定。單一網路將有兩個 Sockets,每個 Sockets 用於通訊設備或程序,這些 Sockets 是IP位址和連接埠的組合。根據使用的連接埠號,單一裝置可以有「n」個 Sockets,不同的連接埠可用於不同類型的協定。
下圖展示了一些常見連接埠號碼和相關協定的資訊:
協定 |
連接埠號碼 |
#Python 函式庫 | 應用程式 |
HTTP |
##80 |
httplib,urllib,requests |
#網頁,網站 |
|
|||
# | 119 |
nttplib |
訊息傳輸 |
SMTP |
#25 |
smtplib |
發送郵件 |
#Telnet |
23 |
telnetlib |
#命令列 |
##POP3 |
#110 |
#poplib |
接收郵件 |
Gopher | ##70 | gopherlib文件傳輸 |
現在我們已經了解了Sockets 的概念,現在讓我們來看看Python 的Socket 模組
方法 |
| #描述
#socket.socket() |
用於建立socket(伺服器端和客戶端都需要建立) |
#socket.accept() |
用於接受連線。它會傳回一對值(conn,address),其中conn是用於發送或接收資料的新socket 對象,address是連接另一端的socket 地址 |
socket.bind() |
用於綁定到指定為參數的位址 |
socket.close() |
用於關閉socket |
現在我們已經了解了 socket 模組的重要性,接下來讓我們來看看如何在 Python 中建立伺服器和客戶機。
伺服器或是一個程式、一台計算機,或是專門用來管理網路資源的設備。伺服器可以位於同一設備或計算機上,也可以本地連接到其他設備和計算機,甚至可以遠端連接。有各種類型的伺服器,如資料庫伺服器、網頁伺服器、列印伺服器等。
伺服器通常使用socket.socket(),socket.bind(),socket.listen()等來建立連線並綁定到客戶端,現在讓我們寫一個程式來建立伺服器。
import socket s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(),1234)) #port number can be anything between 0-65535(we usually specify non-previleged ports which are > 1023) s.listen(5) while True: clt,adr=s.accept() print(f"Connection to {adr}established") #f string is literal string prefixed with f which #contains python expressions inside braces clt.send(bytes("Socket Programming in Python","utf-8 ")) #to send info to clientsocket
建立 socket 的第一個必要條件是導入相關模組。之後是使用socket.socket()方法建立伺服器端 socket。
AF_INET 是指來自Internet 的位址,它需要一對(主機、連接埠),其中主機可以是某個特定網站的URL 或其位址,連接埠號碼為整數。 SOCK_STREAM 用於建立 TCP 協定。
bind()方法接受兩個參數作為元組(主機、連接埠)。這裡要注意的是最好使用4位的連接埠號,因為較低的連接埠號碼通常被佔用或是系統預留的。 listen()方法允許伺服器接受連接,5是同時接受的多個連接的佇列。此處可以指定的最小值為0,如果未指定參數,則採用預設的適當參數。
while循環允許永遠接受連接,clt和adr是客戶端物件和位址,print語句只是列印出客戶端socket 的位址和連接埠號,最後,clt.send用於以位元組為單位發送資料。
現在我們的伺服器已經設定好了,讓我們繼續往客戶端前進。
客戶端是從伺服器接收訊息或服務的電腦或軟體。在客戶端-伺服器模型中,客戶端從伺服器請求服務。最好的例子是 Google Chrome、Firefox 等 Web 瀏覽器,這些 Web 瀏覽器根據使用者的指示請求 Web 伺服器提供所需的網頁和服務。其他範例包括線上遊戲、線上聊天等。
現在,讓我們看看如何用 Python 程式語言編寫客戶端程式:
import socket s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 2346)) msg=s.recv(1024) print(msg.decode("utf-8"))
首先仍然是導入 socket 模組,然後像創建伺服器時那樣創建套接字。接著要在客戶端伺服器之間建立連接,需要透過指定(host,port)使用 connect()方法。
注意:當客戶端和伺服器位於同一台電腦上時,需要使用gethostname。 (LAN–localip/WAN–publicip)
在這裡,客戶端希望從伺服器接收一些訊息,為此,我們需要使用recv()方法,訊息儲存在另一個變數msg中。需要注意的是正在傳遞的訊息將以位元組為單位,在上述程式的客戶端中,一次傳輸最多可以接收1024位元組(緩衝區大小)。根據傳輸的資訊量,可以將其指定為任意數量。
最後,再解碼並列印正在傳輸的訊息。
現在我們已經了解如何建立客戶端-伺服器程序,接下來讓我們看看它們需要如何執行。
客戶端伺服器互動
要執行這些程序,需要開啟命令程序,進入建立客戶端和伺服器程式的資料夾,然後鍵入:
py server.py #这里,server.py 是服务器的文件名
不出意外伺服器開始執行
要執行客戶端,需要開啟另一個cmd窗口,然後鍵入:
pyclient.py
下面讓我們將緩衝區大小減少到7,來看看相同的程式會怎麼樣
#如圖所示,傳送7個位元組後,連線終止。
其實這是一個問題,因為我們尚未收到完整的訊息,但是連接卻提前關閉了,下面讓我們來解決這個問題。
多重通訊
為了在客戶端收到完整訊息之前繼續連接,我們可以使用while循環
import socket s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 2346)) while True: msg=s.recv(7) print(msg.decode("utf-8"))
如此修改之後,每次傳輸將以7個字節接收完整訊息。
但這又引來了另一個問題,連結永遠不會終止,你永遠不知道什麼時候會終止。此外,如果我們實際上不知道客戶端將從伺服器接收到的訊息或資訊有多大,該怎麼辦。在這種情況下,我們需要繼續完善程式碼
complete_info='' while True: msg = s.recv(7) if len(msg)<=0: break complete_info += msg.decode("utf-8") print(complete_info)
在伺服器端,使用close()方法,如下所示:
clt.close()
輸出如下:
程序会检查信息的大小,并将其打印到一次两个字节的缓冲区中,然后在完成连接后关闭连接。
目前为止我们仅仅掌握了传递字符串的方法,但是,Python 中的 Socket 编程也允许我们传输 Python 对象。这些对象可以是集合、元组、字典等。要实现这一点,需要用到 Python 的 pickle 模块。
Python pickle模块
当我们实际序列化或反序列化 Python 中的对象时,就会使用到 Python pickle 模块。让我们看一个小例子
import pickle mylist=[1,2,'abc'] mymsg = pickle.dumps(mylist) print(mymsg)
Output:
b’x80x03]qx00(Kx01Kx02Xx03x00x00x00abcqx01e.’
在上面的程序中,mylist是使用pickle模块的dumps()函数序列化的。还要注意,输出以b开头,表示它已转换为字节。在 socket 编程中,可以实现此模块以在客户端和服务器之间传输 python 对象。
如何使用 pickle 模块传输 Python 对象
当我们将 pickle 与 socket 一起使用时,完全可以通过网络传输任何内容。
先来看看服务端代码
Server-Side:
import socket import pickle a=10 s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 2133))#binding tuple s.listen(5) while True: clt , adr = s.accept() print(f"Connection to {adr}established") m={1:"Client", 2:"Server"} mymsg = pickle.dumps(m)#the msg we want to print later mymsg = {len(mymsg):{a}}"utf-8") + mymsg clt.send(mymsg)
这里,m是一个字典,它基本上是一个需要从服务器发送到客户端的 Python 对象。这是通过首先使用dumps()序列化对象,然后将其转换为字节来完成的。
现在,让我们记下客户端:
Client-Side:
import socket import pickle a=10 s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 2133)) while True: complete_info = b'' rec_msg = True while True: mymsg = s.recv(10) if rec_msg: print(f"The length of message = {mymsg[:a]}") x = int (mymsg[:a ] ) rec_msg = False complete_info += mymsg if len(complete_info)-a == x: print("Recieved the complete info") print(complete_info[a:]) m = pickle.loads(complete_info[a:]) print(m) rec_msg = True complete_info = b'' print(complete_info)
第一个while循环将帮助我们跟踪完整的消息(complete_info)以及正在使用缓冲区接收的消息(rec_msg)。
然后,在接收消息时,我们所做的就是打印每一位消息,并将其放在大小为10的缓冲区中接收。此大小可以是任何大小,具体取决于个人选择。
然后如果收到的消息等于完整消息,我们只会将消息打印为收到的完整信息,然后使用loads()反序列化消息。
输出如下:
以上是Python Socket程式設計詳解及實作指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!