基本知識
- 計算機網(wǎng)絡(luò)知識,主要了解一下OSI的七層架構(gòu),以及TCP/IP的四層架構(gòu)。
- 網(wǎng)絡(luò)通信中最基本的概念:套接字,也叫通信端點、socket
- 網(wǎng)絡(luò)通信地址表示:一般是主機+端口號
- 最常用的兩種連接方式:面向連接方式(TCP,SOCK_STREAM)和面向無連接方式(UDP, SOCK_DGRAM)
這些基本的知識,網(wǎng)上有很多相關(guān)介紹,不是本章重點,有需要可以在網(wǎng)上查找相關(guān)資料。
這里學習一下python語言對底層網(wǎng)絡(luò)編程的支持,這里只學習最常用的socket編程相關(guān)知識,詳細說明可以參考官方文檔:底層網(wǎng)絡(luò)接口
socket模塊
python有一個專門的socket模塊來支持底層網(wǎng)絡(luò)通信。是對Unix系統(tǒng)調(diào)用和套接字庫接口的直譯。
socket編程基本套路
- TCP服務(wù)端
s = socket() # 創(chuàng)建一個服務(wù)端socket s.bind() # 綁定socket地址(ip+port) s.listen() # 監(jiān)聽此地址是否有連接 loop: cs = s.accept() # 阻塞等待客戶端連接,連接成功返回新的套接字對象,用于在此連接上收發(fā)數(shù)據(jù) comm_loop: cs.recv()/cs.send() # 通信:接收數(shù)據(jù)/發(fā)送數(shù)據(jù) cs.close() # 關(guān)閉通信套接字對象 s.close() # 關(guān)閉服務(wù)端套接字對象 - TCP客戶端
cs = socket() # 創(chuàng)建一個客戶端socket cs.connect() # 通過服務(wù)端地址(ip + port)嘗試連接服務(wù)端 comm_loop: cs.send()/cs.recv() # 通信:發(fā)送數(shù)據(jù)/接收數(shù)據(jù) cs.close() # 關(guān)閉客戶端套接字對象 - UDP服務(wù)端
ss = socket() # 創(chuàng)建一個服務(wù)端socket ss.bind() # 綁定服務(wù)端地址 comm_loop: cs.recvfrom()/cs.sendto() # 通信:接收數(shù)據(jù)/發(fā)送數(shù)據(jù) cs.close() # 關(guān)閉客戶端套接字對象
說明: UDP無連接,不需要監(jiān)聽。先被動接收數(shù)據(jù),會返回客戶端的地址,后面就可以根據(jù)客戶端地址主動發(fā)送消息。
- UDP客戶端
cs = socket() # 創(chuàng)建一個客戶端socket comm_loop: cs.sendto()/cs.recvfrom() # 通信:發(fā)送數(shù)據(jù)/接收數(shù)據(jù) cs.close() # 關(guān)閉客戶端套接字對象
socket通信實例
先從一個TCP實例開始學習,功能比較簡單:
- 一個客戶端,一個服務(wù)器端,兩個python程序可以放在兩臺電腦上運行
- 客戶端給服務(wù)端發(fā)送一個消息;服務(wù)器端接收到消息之后,加上一個時間戳再返回給客戶端
- 無消息則結(jié)束
-
服務(wù)端代碼
from socket import * from time import ctime HOST = '' PORT = 21567 BUFF = 1024 ADDR = (HOST, PORT) tcpSvrSocket = socket(AF_INET, SOCK_STREAM) tcpSvrSocket.bind(ADDR) tcpSvrSocket.listen(5) while True: print("Waiting for connect...") tcpCliSocket, addr = tcpSvrSocket.accept() print(f"...connected from {addr}") while True: data = tcpCliSocket.recv(BUFF) if not data: break; data = data.decode('utf-8') print(f'recive data from client: {data}') resMsg = f'[{ctime()}] {data}' tcpCliSocket.send(bytes(resMsg, 'utf-8')) tcpCliSocket.close() tcpSvrSocket.close() -
客戶端代碼
from socket import * HOST = '' PORT = 21567 BUFF = 1024 ADDR = (HOST, PORT) tcpCliSocket = socket(AF_INET, SOCK_STREAM) tcpCliSocket.connect(ADDR) while True: data = input(">>> ") if not data: break tcpCliSocket.send(bytes(data, 'utf-8')) data = tcpCliSocket.recv(BUFF) if not data: break print(data.decode('utf-8')) tcpCliSocket.close()
說明:socket類實現(xiàn)了
__enter__和__exit__,可以使用with語句自動實現(xiàn)close操作,也即使用with語句可以不用主動調(diào)用socket.close()接口
模塊說明
- 常量
socket模塊定義了很多常量,最常使用的如下表
| 常量名 | 說明 |
|---|---|
socket.AF_INET |
一種用于ipv6的地址族,一個四元組(host, port, flowinfo, scope_id)表示地址 |
socket.AF_INET6 |
一種socket地址族(用于ipv4,也是目前常用的)使用一個(ip, port)元組表示地址 |
socket.SOCK_STREAM |
面向連接(TCP)的socket類型 |
socket.SOCK_DGRAM |
面向無連接(UDP)的socket類型 |
- 接口
socket模塊中有一些函數(shù)可以使用,但是最常用的還是socket類生成socket對象,以及類中的接口。類原型如下:class socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
常用接口信息如下表
| 接口名 | 連接方式 | 說明 |
|---|---|---|
socket.bind(address) |
TCP/UDP | 服務(wù)端socket綁定到地址address |
socket.listen([backlog]) |
TCP | 啟動一個服務(wù)器用于接受連接。backlog表示系統(tǒng)允許暫未 accept 的連接數(shù),超過后將拒絕新連接。最低為 0(小于 0 會被置為 0),未指定則自動設(shè)為合理的默認值。 |
socket.accept() |
TCP | 接受一個連接。此 socket 必須綁定到一個地址上并且監(jiān)聽連接。返回一個 (conn, address) 對:其中 conn 是一個新的套接字對象,用于在此連接上收發(fā)數(shù)據(jù),address 是連接另一端的套接字所綁定的地址。 |
socket.connect(address) |
TCP/UDP | 連接到 address 處的遠程套接字。 |
socket.recv(bufsize[, flags]) |
TCP | 從套接字接收數(shù)據(jù)。返回值是一個字節(jié)對象,表示接收到的數(shù)據(jù)。bufsize 指定一次接收的最大數(shù)據(jù)量??蛇x參數(shù) flags 的含義請參閱 Unix 手冊頁 recv(2),它默認為零。 |
socket.send(bytes[, flags]) |
TCP | 發(fā)送數(shù)據(jù)給套接字。本套接字必須已連接到遠程套接字??蛇x參數(shù) flags 的含義與上述 recv() 中的相同。 |
socket.recvfrom(bufsize[, flags]) |
UDP | 從套接字接收數(shù)據(jù)。返回值是一對 (bytes, address),其中 bytes 是字節(jié)對象,表示接收到的數(shù)據(jù),address 是發(fā)送端套接字的地址??蛇x參數(shù) flags 的含義請參閱 Unix 手冊頁 recv(2),它默認為零。 |
socket.sendto(bytes, address) |
UDP | 發(fā)送數(shù)據(jù)給套接字。由 address 指定目標套接字??蛇x參數(shù) flags 的含義與上述 recv() 中的相同。 |
- 異常
| 異常名 | 說明 |
|---|---|
socket.error |
一個被棄用的 OSError 的別名。 |
socket.herror |
OSError的子類,本異常通常表示與地址相關(guān)的錯誤。 |
socket.gaierror |
OSError的子類,來自 getaddrinfo()和 getnameinfo(),表示與地址相關(guān)的錯誤。附帶的值是一對 (error, string),代表庫調(diào)用返回的錯誤。 |
socket.timeout |
OSError的子類,當套接字發(fā)生超時,且事先已調(diào)用過 settimeout()(或隱式地通過 setdefaulttimeout())啟用了超時,則會拋出此異常。 |
SocketServer模塊
socketserver模塊是一個用于網(wǎng)絡(luò)服務(wù)器編寫的框架,簡化了編寫網(wǎng)絡(luò)服務(wù)器的任務(wù)。例如上面TCP服務(wù)器端代碼可以使用此模塊改寫如下:
import socketserver
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 21567
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
模塊說明
常用實體服務(wù)器類:
- TCPServer類,該類使用互聯(lián)網(wǎng) TCP 協(xié)議,它可以提供客戶端與服務(wù)器之間的連續(xù)數(shù)據(jù)流。函數(shù)原型如下:
class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True) - UDPServer類,該類使用數(shù)據(jù)包,即一系列離散的信息分包,它們可能會無序地到達或在傳輸中丟失。函數(shù)原型如下:
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
請求處理句柄對象:
-
BaseRequestHandler類,這是所有請求處理句柄對象的超類。 它定義了下文列出的接口。 一個實體請求處理句柄子類必須定義新的
handle()方法,并可重載任何其他方法。 對于每個請求都會創(chuàng)建一個新的子類的實例。接口名 說明 setup()會在 handle()方法之前被調(diào)用以執(zhí)行任何必要的初始化操作。 默認實現(xiàn)不執(zhí)行任何操作。handle()此函數(shù)必須執(zhí)行為請求提供服務(wù)所需的全部操作。 默認實現(xiàn)不執(zhí)行任何操作。 它有幾個可用的實例屬性;請求為 self.request;客戶端地址為self.client_address;服務(wù)器實例為self.server,如果它需要訪問特定服務(wù)器信息的話。finish()在 handle()方法之后調(diào)用以執(zhí)行任何需要的清理操作。 默認實現(xiàn)不執(zhí)行任何操作。 StreamRequestHandler和DatagramRequestHandler類,是
BaseRequestHandler的子類,重載了setup()和finish()方法,并提供了self.rfile(讀取以獲取請求數(shù)據(jù)) 和self.wfile(寫入以將數(shù)據(jù)返回給客戶端) 屬性。
當然此框架還提供了更加強大的功能,具體可以參考官方文檔