Python基礎---網絡編程3
1.粘包現象
每個 socket 被創建後,都會分配兩個緩沖區,輸入緩沖區和輸出緩沖區。write()/send() 並不會立即向網絡中傳輸數據,而是先將數據寫入緩沖區中,再由TCP協議將數據從緩沖區發送到目標機器。一旦將數據寫入到緩沖區,函數就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被發送到網絡,這些都是TCP協議負責的事情,tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。兩種情況下會發生粘包現象:
現象1:第一次客戶端send數據,總長度>1024字節,發送到服務端recv(),服務端send()至客戶端recv緩沖區,但因數據總長度>1024,多出的那部分數據停留在輸入緩沖區第二次客戶單send數據,一觸發send,就執行recv,此時能拿到上次剩余的數據
現象2:客戶端輸入的數據過短,緩沖區會等到較滿時才會發送至服務端,此時拿到的數據可能是多次send較短數據的拼接結果
代碼查看緩沖區的大小
import socket server.bind((‘127.0.0.1‘,8010)) server.listen(3) print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF)) # 輸出緩沖區大小 print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)) # 輸入緩沖區大小
2.粘包現象的解決
struct模塊,可以把一個類型,如數字,轉成固定長度的bytes
import struct
# 將一個數字轉化成等長度的bytes類型。
ret = struct.pack(‘i‘, 183346)
print(ret, type(ret), len(ret))
# 通過unpack反解回來
ret1 = struct.unpack(‘i‘,ret)[0]
print(ret1, type(ret1), len(ret1))
01.low版
由於接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總數按照固定字節發送給接收端後面跟上總數據,然後接收端先接收固定字節的總字節流,再循環接收完所有數據。
server端:
import socket
import subprocess
import struct
server = socket.socket()
server.bind((‘127.0.0.1‘,8848))
server.listen(5)
while True:
con, addr = server.accept()
while 1:
try:
cmd = con.recv(1024).decode(‘utf-8‘)
obj = subprocess.Popen(cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
right_msg = obj.stdout.read()
error_msg = obj.stderr.read()
total_data_len = len(right_msg + error_msg)
# 把數據總長度轉化為固定長度的bytes類型
total_datasize_bytes = struct.pack(‘i‘,total_data_len)
# 發送固定長度的字節
con.send(total_datasize_bytes)
con.send(right_msg + error_msg) # 發送所有數據
except Exception:
break
con.close()
server.close()
client端:
import socket import struct client = socket.socket() client.connect((‘127.0.0.1‘,8848)) while True: cmd = input(‘>>>‘).strip() client.send(cmd.encode(‘utf-8‘)) # 接收固定長度的字節 head_bytes = client.recv(4) # b‘x20\x00\x10\x00‘ # 將固定長度的字節還原成原int類型 total_data_len = struct.unpack(‘i‘,head_bytes)[0] print(‘總長度 ---> ‘,total_data_len) # 循環接收數據 total_data = b‘‘ while len(total_data) < total_data_len: data = client.recv(1024) total_data += data print(total_data.decode(‘gbk‘)) client.close()
02.自定制報頭版
可以把報頭做成字典,字典裏包含將要發送的真實數據的描述信息,然後json序列化,然後用struck將序列化後的數據長度打包成4個字節。
我們在網絡上傳輸的所有數據 都叫做數據包,數據包裏的所有數據都叫做報文,報文裏面不止有你的數據,還有ip地址、mac地址、端口號等等,其實所有的報文都有報頭,這個報頭是協議規定的
發送時:01.先發報頭長度
02.編碼報頭內容然後發送
03.最後發真實內容.
import socket
import subprocess
import json
import os
import struct
server = socket.socket()
server.bind((‘本地回環地址‘, 端口號))
server.listen(5)
while True:
con, addr = server.accept()
while 1:
try:
obj_dic = {
‘user_name‘: ‘文件名‘,
‘user_md5‘: md5值
‘user_path‘: 文件路徑,
‘user_size‘: 文件大小(os.path.getsize(路徑))
}
obj_dic_json = json.dumps(obj_dic)
obj_dic_json_bytes = obj_dic_json.encode(‘utf-8‘)
obj_dic_json_bytes_pack = struct.pack(‘i‘, len(obj_dic_json_bytes))
con.send(obj_dic_json_bytes_pack)
con.send(obj_dic_json_bytes)
# 發送具體數據
con.send()
except Exception:
break
con.close()
server.close()
接收時:01.先收報頭長度,用struct解包取出來
02.根據取出的長度收取報頭內容,然後解碼,反序列化
03.從反序列化的結果中取出待取數據的描述信息,然後去取真實的數據內容
import socket
import struct
import json
client = socket.socket()
client.connect((‘服務端ip‘,端口號))
while True:
data = input(‘>>>‘)
client.send(data.encode(‘utf-8‘))
obj_dic_json_bytes_pack = client.recv(4)
obj_dic_json_bytes_size = struct.unpack(‘i‘,obj_dic_json_bytes_pack)
obj_dic_json_bytes = client.recv(obj_dic_json_bytes_size)
obj_dic_json = obj_dic_json_bytes.decode(‘utf-8‘)
obj_dic = json.loads(obj_dic_json)
obj_data = b‘‘
while len(obj_data) < dic[‘user_size‘]:
obj_data += client.recv(1024)
print(obj_data.decode(‘gbk‘))
client.close()
3.socketserver實現並發
socketserver可以實現和多個客戶端通信。它是在socket的基礎上進行了一層封裝,也就是說底層還是調用的socket
server端:
import socketserver class Myserver(socketserver.BaseRequestHandler): # 類名不固定,必須繼承socketserver.BaseRequestHandler def handle(self): # handle方法名是固定的 while 1: # self.request相當於server的通道:con from_client_data = self.request.recv(1024) print(from_client_data.decode(‘utf-8‘)) to_client_data = input(‘回復:‘).strip().encode(‘utf-8‘) self.request.send(to_client_data) if __name__ == ‘__main__‘: ip_port = (‘本地回環地址‘,端口號) # 相當於bind,綁定ip和端口 # socketserver.TCPServer.allow_reuse_address = True # (會觸發: 可重復使用端口) server = socketserver.ThreadingTCPServer(ip_port,Myserver) # 固定寫法((IP地址,端口),Myserver) # 源碼上顯示: 創建了socket對象,綁定IP和端口,監聽 server.server_forever()
client端:
import socket
client = socket.socket()
client.connect((‘服務端ip‘,端口))
while 1:
to_server_data = input(‘>>>‘).strip().encode(‘utf-8‘)
client.send(to_server_data)
from_server_data = client.recv(1024).decode(‘utf-8‘)
print(from_server_data)
4.udp協議下的socket通信
udp協議: 效率很高,但數據不安全
udp-server端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind((‘本地回環地址‘, 端口號))
try:
while True:
client_data, client_addr = server.accept()
print(‘來自%s的消息:%s‘%(client_addr,client_data.decode(‘utf-8‘)))
to_client_data = input(‘回復:‘).strip().encode(‘utf-8‘)
server.send(to_client_data,client_addr)
finally:
server.close()
udp-client端:
import socket client = socket.socket(type=socket.SOCK_DGRAM) while True: to_server_data = input(‘給服務端發送:‘).strip().encode(‘utf-8‘) client.send(to_server_data,(‘服務端ip‘,端口號)) server_data, server_addr = server.recvfrom(1024) print(‘來自%s的信息:%s‘%(server_addr,server_data.decode(‘utf-8‘)))
Python基礎---網絡編程3