Python 提供了兩個級別訪問的網路服務,:
- 低級別的網路服務支持基本的 socket,,可以訪問底層作業系統Socket介面的方法,
- 高級別的網路服務模塊 socketserver, 可以簡化網路服務器的開發,
socket
查看socket類的幫助如下
import socket # 匯入socket模塊
>>> help(socket.socket)
重點關注初始化函式:
__init__(self, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=0, fileno=None)
- family:網路協議簇,默認值為AF_INET
- type:套接字的型別,根據是面向連接的還是非連接分為
SOCK_STREAM或SOCK_DGRAM - proto:套接字協議,一般默認為0,表示
- fileno:套接字的int型的檔案描述符
下面實作一個TCP聊天室和一個UDP聊天室
TCP聊天室
概要設計
獲取多個連接的處理
開啟accept執行緒,執行accept操作開始阻塞,有客戶端連接時,再開啟一個執行緒recv進行資料接收的處理,然后accept執行緒繼續阻塞,等待后續客戶端的連接,
阻塞的處理
服務端處理客戶端的連接時,有兩處存在阻塞,分別是:
- 獲取連接時,socket.accept()會阻塞
- 每一個建立成功的連接在獲取資料時,socket.recv(1024)
因此這兩處都需要開啟執行緒單獨處理,否則會阻塞主執行緒,
客戶端主動斷開的處理
客戶端主動斷開時,如果不通知服務端,那么服務端上保存的客戶端連接不會被清理,這是不合理的,因此客戶端主動斷開時,我們在應用層約定,客戶端推出前需要發送/quit指令到服務端上,然后有服務端關閉socket,
TCP聊天室-server
聊天室的server端主要是監聽埠,處理來自client端的連接,并且分發資料到所有的client端
代碼
import socket
import threading
class TcpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.ip = ip
self.port = port
self.clients = {}
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self, so, ip ,port):
while not self.event.is_set():
data = https://www.cnblogs.com/CHLL55/p/so.recv(1024).decode() # 將接受到的位元組資料bytes轉化為utf-8格式的字串
if data.strip() =='/quit': # 客戶端主動斷開時的處理
so.close()
self.clients.pop((ip, port))
return
for s in self.clients.values(): # 廣播發送
s.send('{}:{}\n{}'.format(ip, port, data).encode())
def accept(self):
while not self.event.is_set():
so, (ip, port) = self.sock.accept()
self.clients[(ip, port)] = so
# 因為so.recv會產生阻塞,因此單獨開一個執行緒處理資料的接受部分,這樣accept可以繼續接受來自其他客戶端的鏈接
threading.Thread(target=self.recv, args=(so, ip, port), name='client-{}:{}'.format(ip, port)).start()
def start(self):
self.sock.bind((self.ip, self.port))
self.sock.listen()
t = threading.Thread(target=self.accept, daemon=True) # 為了不阻塞主執行緒,單獨開啟一個執行緒處理accept(accept會阻塞執行緒)
try:
t.start()
t.join() # 阻塞直到獲取到KeyboardInterrupt
except KeyboardInterrupt:
self.stop()
def stop(self):
for s in self.clients.values():
s.close()
self.sock.close()
self.event.set() # 停止所有的回圈
if __name__ == '__main__':
tcp_chat_server = TcpChatServer()
tcp_chat_server.start()
TCP聊天室-client
聊天室的client端主要是發起連接,連接到server端,并且要接受來自服務端廣播分發的訊息,
代碼
import socket
import threading
class TcpChatClient:
def __init__(self):
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self): # 客戶端需要一直接收服務端廣播分發的訊息
while not self.event.is_set():
data = https://www.cnblogs.com/CHLL55/p/self.sock.recv(1024).decode()
data = data.strip()
print(data)
def send(self): # 輸入訊息就發送
while not self.event.is_set():
data = input()
self.sock.send(data.encode())
if data.strip() =='/quit': # 發送/quit的時候自身關閉
self.stop()
def start(self, ip, port):
self.sock.connect((ip, port))
s = threading.Thread(target=self.send, daemon=False)
r = threading.Thread(target=self.recv, daemon=False)
s.start()
r.start()
def stop(self):
self.sock.close()
self.event.set()
if __name__ == '__main__':
tcp_chat_client = TcpChatClient()
tcp_chat_client.start('192.168.110.13', 9001)
UDP聊天室
概要設計
阻塞的處理
在UDP服務端接收客戶端的訊息時,采用socket.recvfrom(1024)這個方法以便保存客戶端的地址資訊,這個方法會阻塞當前執行緒,因此需要開啟執行緒單獨處理,
客戶端主動斷開的處理
UDP客戶端主動關閉之后,服務端是無法檢測到客戶端已經關閉的,我們可以采用以下兩種方法:
- 如果類似于TCP采用約定退出指令的方法,那么客戶端發送退出指令后就呼叫close方法,然后服務端根據得到的指令剔除客戶端字典中對應的客戶端,
- 還可以通過客戶端定時發送心跳給服務端,服務端通過心跳來判斷客戶端行程是否存活,
UDP聊天室-server
UDP服務端程式開啟執行緒等待接收客戶端的資料,然后廣播給其他的客戶端,并且檢查所有連接的心跳是否超時,
代碼
import socket
import datetime
import threading
class UdpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.addr = (ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.clients = {}
self.event = threading.Event()
def recv(self):
while not self.event.is_set():
data, addr = self.sock.recvfrom(1024)
data = https://www.cnblogs.com/CHLL55/p/data.decode().strip()
now = datetime.datetime.now()
if data =='#ping#': # 判斷是否收到心跳
self.clients[addr] = now # 收到心跳則保存客戶端地址,并且更新時間戳
continue
disconnected = set() # 沒收到一次資料就判斷所有的失效鏈接
for addr, timestamp in self.clients.items():
if (now - timestamp).total_seconds() > 10: # 失效條件:2次(即10s)沒收到心跳就判斷客戶端關閉
disconnected.add(addr)
else:
self.sock.sendto('{}:{}\n{}'.format(addr[0], addr[1], data).encode(), addr)
for addr in disconnected:
self.clients.pop(addr)
def start(self):
self.sock.bind(self.addr) # 系結埠之后就開啟執行緒一直接受客戶端的資料
t = threading.Thread(target=self.recv(), daemon=True)
try:
t.start()
t.join()
except KeyboardInterrupt:
self.stop()
def stop(self):
self.event.set()
self.sock.close()
if __name__ == '__main__':
udp_chat_server = UdpChatServer()
udp_chat_server.start()
UDP聊天室-client
UDP的客戶端的主執行緒一直在等待用戶輸入資料然后將資料發送到服務端,同時開啟了一個心跳行程和一個接受服務端廣播資料的執行緒,
代碼
import socket
import threading
import time
class UdpChatClient:
def __init__(self, ip, port):
self.addr = (ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.event = threading.Event()
def heartbeat(self): # 心跳執行緒函式:每5s發一次心跳
while not self.event.wait(5):
self.sock.sendto(b'#ping#', self.addr)
def recv(self): # 一直等待接受udp服務器廣播的資料
while not self.event.is_set():
data = https://www.cnblogs.com/CHLL55/p/self.sock.recv(1024)
print(data.decode())
def start(self):
threading.Thread(target=self.heartbeat, name='heartbeat', daemon=True).start()
threading.Thread(target=self.recv, name='recv', daemon=True).start()
print('請在5s后發言')
time.sleep(5) # 因為服務端必須收到一個心跳之后才會保存次客戶端,因此需要等待5s
print('請開始發言')
while not self.event.is_set():
data = https://www.cnblogs.com/CHLL55/p/input('')
data = https://www.cnblogs.com/CHLL55/p/data.strip()
if data =='/quit':
self.event.set()
self.sock.close()
return
self.sock.sendto(data.encode(), self.addr)
if __name__ == '__main__':
udp_chat_client = UdpChatClient('192.168.110.13', 9001)
udp_chat_client.start()
SocketServer
TODO(Flowsnow):改寫聊天室程式的TcpChatServer和UdpChatServer
附一:TCP和UDP的本質區別
-
udp:所有的客戶端發來的資料報都堆積在佇列上,然后服務端一個一個的處理
-
tcp:每一個客戶端和服務端都有一個連接通道,只處理對應客戶端的資料流
附二:參考資料
- socketserver — A framework for network servers
記得幫我點贊哦!
精心整理了計算機各個方向的從入門、進階、實戰的視頻課程和電子書,按照目錄合理分類,總能找到你需要的學習資料,還在等什么?快去關注下載吧!!!

念念不忘,必有回響,小伙伴們幫我點個贊吧,非常感謝,
我是職場亮哥,YY高級軟體工程師、四年作業經驗,拒絕咸魚爭當龍頭的斜杠程式員,
聽我說,進步多,程式人生一把梭
如果有幸能幫到你,請幫我點個【贊】,給個關注,如果能順帶評論給個鼓勵,將不勝感激,
職場亮哥文章串列:更多文章

本人所有文章、回答都與著作權保護平臺有合作,著作權歸職場亮哥所有,未經授權,轉載必究!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/159838.html
標籤:Python
