學習python,從入門到放棄(32)
阿新 • • 發佈:2022-04-15
學習python,從入門到放棄(32)
socket套接字
當我們需要編寫實現資料互動的程式時,需要編寫程式碼操作OSI七層,相當的複雜。但是由於操作OSI七層是所有cs架構的程式都需要經歷的過程,所以有固定的模組。
socket 模組提供了快捷方式,不需要自己處理每一層。
socket模組
cs架構的軟體無論是在編寫還是執行,都應該先考慮服務端,因為服務端是直接影響客戶端的各種功能的,沒有服務端客戶端就是一個空殼。
服務端程式碼
import socket server = socket.socket() # 產生一個socket物件 """ 通過檢視原始碼得知 括號內不寫引數預設就是基於網路的遵循TCP協議的套接字 """ server.bind(('127.0.0.1', 8080)) # 設定服務端地址方便客戶端進行連線 """ 服務端應該具備的特徵 固定的地址 ... 127.0.0.1是計算機的本地迴環地址 只有當前計算機本身可以訪問 """ server.listen(5) # 建立半連線池 sock, addr = server.accept() # 等待客戶端進行連線,否則程式將在此阻斷 """ listen和accept對應TCP三次握手服務端的兩個狀態 """ print(addr) # 輸出客戶端的地址 data = sock.recv(1024) # 接收客戶端傳送的內容 print(data.decode('utf8')) # 轉化為utf8格式輸出 sock.send('你好啊'.encode('utf8')) # 向客戶端傳送內容 """ recv和send接收和傳送的都是bytes型別的資料,所以要進行轉化 """ sock.close() # 關閉連線 server.close() # 關閉服務端
客戶端程式碼
import socket
client = socket.socket() # 產生一個socket物件
client.connect(('127.0.0.1', 8080)) # 根據服務端的地址連結
client.send(b'hello sweet heart!!!') # 給服務端傳送訊息
data = client.recv(1024) # 接收服務端回覆的訊息
print(data.decode('utf8'))
client.close() # 關閉客戶端
服務端與客戶端首次互動,一邊是 recv 那麼另一邊必須是 send ,否則無法連線。
通訊迴圈
- 利用 input 獲取使用者輸入用以訊息固定
- 將雙方用於資料互動的程式碼迴圈起來解決通訊迴圈的問題
客戶端程式碼:
import socket server = socket.socket() server.bind(('127.0.0.1', 8080)) server.listen(5) sock, addr = server.accept() while True: data = sock.recv(1024) print(data.decode('utf8')) msg = input('請回復訊息>>>:').strip() if msg == 'q': # 退出通訊 break sock.send(msg.encode('utf8')) sock.close() server.close()
服務端程式碼:
import socket
client = socket.socket() # 產生一個socket物件
client.connect(('127.0.0.1', 8080)) # 根據服務端的地址連結
while True:
msg = input('請輸入你需要傳送的訊息>>>:').strip()
if msg == 'q': # 退出通訊
break
client.send(msg.encode('utf8')) # 給服務端傳送訊息
data = client.recv(1024) # 接收服務端回覆的訊息
print(data.decode('utf8'))
client.close() # 關閉客戶端
程式碼優化及連結迴圈
-
統計長度並判斷,解決傳送訊息不能為空。
-
反覆重啟服務端可能會報錯:address in use,使用模組
from socket import SOL_SOCKET,SO_REUSEADDR
並在 bind 前加
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
-
客戶端如果異常斷開,服務端程式碼應該重新回到accept等待新的客人
服務端程式碼:
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept()
print(addr) # 客戶端的地址
while True:
try:
data = sock.recv(1024)
if len(data) == 0:
break
print(data.decode('utf8'))
msg = input('請回復訊息>>>:').strip()
if len(msg) == 0:
msg = '太忙 暫無訊息'
sock.send(msg.encode('utf8'))
except Exception:
break
客戶端程式碼:
import socket
client = socket.socket() # 產生一個socket物件
client.connect(('127.0.0.1', 8080)) # 根據服務端的地址連結
while True:
msg = input('請輸入你需要傳送的訊息>>>:').strip()
if len(msg) == 0: # 使用者如果什麼都不輸 直接回車 那麼不該往下走
continue
if msg == 'q':
break
client.send(msg.encode('utf8')) # 給服務端傳送訊息
data = client.recv(1024) # 接收服務端回覆的訊息
print(data.decode('utf8'))
client.close() # 關閉客戶端
黏包問題
服務端程式碼:
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
conn, addr = server.accept()
data1 = conn.recv(5)
print(data1)
data2 = conn.recv(5)
print(data2)
data3 = conn.recv(5)
print(data3)
客戶端程式碼
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
client.send(b'aaaaaasssssssssssd')
client.send(b'hello')
client.send(b'world')
輸出結果:
'''
b'aaaaa'
b'assss'
b'sssss'
'''
黏包指資料與資料之間沒有明確的分界線,導致不能正確的讀取資料。
這個問題的核心原因是因為TCP協議的特點,會將資料量比較小並且時間間隔比較短的資料整合到一起傳送,並且還會受制於 recv 括號內的數字大小。
問題產生的原因其實是因為 recv 括號內我們不知道即將要接收的資料到底多大,如果每次接收的資料我們都能夠精確的知道它的大小,那麼肯定不會出現黏包。
import struct
data1 = 'hello world!'
print(len(data1)) # 12
res1 = struct.pack('i', len(data1)) # 第一個引數是格式 寫i就可以了
print(len(res1)) # 4
ret1 = struct.unpack('i', res1)
print(ret1) # (12,)
pack 可以將任意長度的數字打包成固定長度。
unpack 可以將固定長度的數字解包成打包之前資料真實的長度。