Python Socket編程詳解
背景
關於Python Socket編程,首先需要瞭解幾個計算機網絡的知識,通過以下的幾個問題,有助於更好的理解Socket編程的意義,以及整個框架方面的知識:
TCP和UDP協議本質上的區別?
TCP協議,面向連接,可靠,基於字節流的傳輸層通信協議;UDP協議無連接,不可靠,基於數據包的傳輸層協議。
TCP協議在建立連接的過程需要經歷三次握手,斷開連接則需要經歷四次揮手,而這建立連接的過程增加瞭傳輸過程中的安全性。
而建立連接的過程則會消耗系統的資源,消耗更多的時間,而相比較UDP協議傳輸過程則不會出現這種問題。
總結來講,基於TCP協議傳輸,需要不斷的確認對方是否收到信息,從而建立連接(確認過程次數有限制,即三次握手),UDP協議傳輸則
不需要確認接收端是否收到信息,隻需要將信息發給對方。
TCP/IP協議棧、HTTP協議、Socket之間的區別和聯系?
TCP/IP協議棧就是一系列網絡協議,可以分為四層模型來分析:應用層、傳輸層、網絡層、鏈路層;
HTTP協議(超文本傳輸協議)就是在這一協議棧中的應用層協議;HTTP協議簡單來說,它的作用就是規范數據的格式,讓程序能夠方便的識別,並且收發雙方都需要遵循同樣的協議格式進行數據傳輸。(應用層的協議也和HTTP協議的作用類似,不一樣的是定義不同的數據格式。)
Socket可以理解為TCP/IP協議棧提供的對外的操作接口,即應用層通過網絡協議進行通信的接口。Socket可以使用不同的網絡協議進行端對端的通信;
TCP Socket服務器的通信過程?
Server端:
建立連接(socket()函數創建socket描述符、bind()函數綁定特定的監聽地址(ip+port)、listen()函數監聽socket、accept()阻塞等待客戶端連接)
數據交互(read()函數阻塞等待客戶端發送數據、write()函數發送給客戶端數據)
Client端:
建立連接(socket()函數創建socket描述符、connect()函數向指定的監聽地址發送連接請求)
數據交互(wirte()函數發送服務端數據、read()函數足阻塞等待接受服務端發送的數據)
socket和websocket之間的聯系?
webosocket是一種通信協議,不同於HTTP請求,客戶端請求服務端資源,服務端響應的通信過程;websocket允許服務端主動
向客戶端推送消息,同時做到客戶端和服務端雙向通訊的協議。(具體底層原理有待後面實踐,暫時未接觸)
HTTP,WSGI協議的聯系和區別?
HTTP協議(超文本傳輸協議),屬於TCP/IP協議棧中應用層的協議。用於規范傳輸數據的格式,是一種客戶端和服務端傳輸的規則。
WSGI協議則是Python定義的Web服務器和框架程序通信的接口規則。兩者聯系不大,強行說的話,Python框架程序主要處理的是HTTP請求。
(後期可以實現一個WSGI協議的Python框架,用於處理HTTP請求的實驗。)
主流Web框架,異步Web框架?
主流Web框架:Django、Flask
異步Web框架:Tornado(內置異步模塊)、Snaic(Python自帶asyncio)、FastAPI(基於Starlette庫) 、aiohttp(基於asyncio)
asyncio,aiohttp之間的聯系?(異步編程)
asyncio是一個異步IO庫,aiohttp就是基於asyncio的異步HTTP框架(支持客戶端/服務端)
代碼設計
Python提供瞭基本的socket模塊:
- socket模塊;提供瞭標準的BSD Sockets API;
- socketserver模塊:提供瞭服務器中心類,簡化服務器的開發;
TCP Socket服務端
socket模塊:
# -*- coding: utf-8 -*- from socket import socket, AF_INET, SOCK_STREAM def echo_handler(sock ,address): print("Get Connection from address:", address) while True: response = sock.recv(8192) if not response: break print(f"Got {response}") sock.sendall(response) def echo_server(address, back_log=5): sock = socket(AF_INET, SOCK_STREAM) sock.bind(address) sock.listen(back_log) while True: sock_client, address = sock.accept() echo_handler(sock_client, address) if __name__ == "__main__": echo_server(('localhost', 5000))
代碼詳解:
- 創建一個基於IPV4和TCP協議的Socket,這裡AF_INET指的是使用IPV4協議,SOCK_STREAM指定使用面向流的TCP協議,綁定監聽端口,設置等待連接的最大數量
- 創建一個永久循環,獲取客戶端請求的連接,accept()會等待並返回一個客戶端的連接;
- 連接建立後,等待客戶端數據,接受完客戶端數據,然後返回數據給客戶端,最後關閉連接
存在的問題:當出現多個客戶端請求時,由於是單個線程會發生阻塞的情況,所以如果需要多線程處理多個客戶端請求,可以這樣改;
from threading import Thread while True: client_sock, address = sock.accept() thread = Thread(target=echo_handler, args=(client_sock, address)) thread.start()
這樣的話,就會在每個客戶端請求的時候,生成一個子線程然後處理請求;
(但是存在一個問題:當突然大量請求連接,消耗系統資源達到上限後,很可能造成程序無法處理後續請求。)
socketserver模塊:
from socketserver import BaseRequestHandler, TCPServer class EchoHandler(BaseRequestHandler): def handle(self): print("Got Connection From: %s" % str(self.client_address)) while True: msg = self.request.recv(8192) if not msg: break self.request.send(msg) if __name__ == "__main__": server = TCPServer(("", 5000), EchoHandler) server.serve_forever()
from socketserver import StreamRequestHandler, TCPServer, ThreadingTCPServer import time class EchoHandler(StreamRequestHandler): def handle(self): print("Got Connection Address: %s" % str(self.client_address)) for line in self.rfile: print(line) self.wfile.write(bytes("hello {}".format(line.decode('utf-8')).encode('utf-8'))) if __name__ == "__main__": serv = ThreadingTCPServer(("", 5000), EchoHandler) serv.serve_forever()
代碼詳解:
- 處理多個客戶端,初始化一個ThreadingTCPServer實例;
- 設置綁定的IP地址和端口,以及處理類;
- 使用StreamRequestHandler(使用流的請求處理程序類,類似file-like對象,提供標準文件接口簡化通信過程),重寫裡面的handle方法,獲取請求數據,返回數據給客戶端;
TCP Socket客戶端
socket模塊:
# -*- coding: utf-8 -*- from socket import socket, AF_INET, SOCK_STREAM import time def request_handler(): start_time = time.time() sock_client = socket(AF_INET, SOCK_STREAM) sock_client.connect(('localhost', 5000)) book_content = "" with open("send_books.txt", "r") as f: book_content = f.read() content_list = book_content.split("\n") for content in content_list: if content: sock_client.send((content).encode()) time.sleep(2) response = sock_client.recv(8192) print(response) end_time = time.time() print("總共耗時:", end_time-start_time) if __name__ == "__main__": request_handler()
UDP Socket
Socket模塊:
from socket import socket, AF_INET, SOCK_DGRAM import time def time_server(address): sock = socket(AF_INET, SOCK_DGRAM) sock.bind(address) while True: msg, addr = sock.recvfrom(8192) print('Get message from', addr) resp = time.ctime() sock.sendto(resp.encode('ascii'), addr) if __name__ == "__main__": time_server(('', 5000))
代碼不詳解,和之前的差不多,註意不同的協議就完事瞭
客戶端測試:
from socket import socket, AF_INET, SOCK_DGRAM if __name__ == "__main__": s = socket(AF_INET, SOCK_DGRAM) s.sendto(b'hello', ('localhost', 5000)) text = s.recvfrom(8192) print(text)
socketserver模塊:
from socketserver import BaseRequestHandler, UDPServer import time class TimeHandler(BaseRequestHandler): def handle(self): print("Got Connection %s".format(str(self.client_address))) data = self.request[0] print(data) msg, sock = self.request print(msg) data = time.ctime() sock.sendto(data.encode('ascii'), self.client_address) if __name__ == "__main__": u = UDPServer(("localhost", 9999), TimeHandler) u.serve_forever()
代碼不在贅述,如果需要多線程處理並發操作可以使用ThreadingUDPServer
總結
關於本篇介紹Python Socket編程,大都是皮毛,隻是談到瞭Python實際處理socket的幾個模塊,
關於socket底層方面的知識並未提及,先瞭解個大概,從實際使用方面出發,在實際使用過程中結合
計算機網絡知識,能夠對socket在整個TCP/IP協議棧中的作用。
socket和socketserver模塊都可以用來編寫網絡程序,不同的是socketserver省事很多,你可以專註
業務邏輯,不用去理會socket的各種細節,包括不限於多線程/多進程,接收數據,發送數據,通信過程。
以上就是Python Socket編程詳解的詳細內容,更多關於Python Socket編程的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Python網絡編程之socket與socketserver
- 用PHP實現的服務端socket具體實例
- Python 基於TCP 傳輸協議的網絡通信實現方法
- Python中的socket網絡模塊介紹
- Python 實現一個簡單的web服務器