1、楔子json
最近作了一個需求遇到一個坑,歸結成一個小問題,其實就是在socket的server端處理client端發來的數據的問題,現將這個問題總結一下,本文將數據在server端以字典的形式存儲。併發
另外,因爲我想作一個動圖的演示,因此本文沒有用MarkDown寫,但願本文的乾貨能彌補格式帶來的不適ε=(´ο`*)))app
2、基於原生UDP的實現socket
咱們這裏用「默認字典」去實現數據的構建,基於原生UDP的代碼以下:ide
# -*- coding:utf-8 -*- import socket from collections import defaultdict server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1',9002)) print('Listening......') client_msg = { '123':'Naruto', '456':'Sasuke', } dic_msg = defaultdict(list) while 1: try: # 接收 recv_msg,addr = server.recvfrom(1024) recv_msg = recv_msg.decode('utf-8') # 這裏只切割一次,避免後面的數據還有|符號 cid = recv_msg.strip().split('|',1)[0] msg = recv_msg.strip().split('|',1)[1] # 默認字典 dic_msg[client_msg[cid]] dic_msg[client_msg[cid]].append(msg) # 回覆 server.sendto('收到了'.encode('utf-8'),addr) print('消息字典:%s'%(dict(dic_msg))) except Exception as e: print(e)
# -*- coding:utf-8 -*- import socket client = socket.socket(type=socket.SOCK_DGRAM) server_addr = ('127.0.0.1',9002) cid = '123' while 1: try: msg = input('>>>:').strip() # 發送 cid跟消息用|分割開來——具體怎麼作看具體需求 send_msg= cid+'|'+msg client.sendto(send_msg.encode('utf-8'),server_addr) # 接收 content = client.recv(1024).decode('utf-8') print('server端回覆:',content) except Exception as e: print(e) break
實現效果以下:spa
3、基於併發的TCP實現設計
socketsever模塊爲咱們提供了併發的功能,咱們這裏直接用socketsever模塊作。3d
另外咱們考慮到TCP的粘包問題,以Client端給Server端發送數據爲例:咱們須要先給Server端發送一個帶有」信息「的字典。這裏的信息能夠是咱們將要發送的數據的大小,也能夠是其餘的必要的數據,這裏咱們給Server端發送Client端的標識cid以及將要發送的數據的大小。code
同時考慮到程序的解耦,咱們把包含重要信息字典的處理放在pro_trance.py文件中,兩端在收發信息的時候直接import便可。裏面的代碼爲:server
# -*- coding:utf-8 -*- import json import struct def pro_send(sk,dic,pro=True): str_dic = json.dumps(dic) bytes_dic = str_dic.encode('utf-8') # 默認選擇協議發送 if pro: num_dic = struct.pack('i',len(bytes_dic)) sk.sendall(num_dic) sk.sendall(bytes_dic) def pro_recv(sk,pro=True): # 依據協議先收到的是字典的長度 if pro: num_dic = sk.recv(4) # 注意unpack獲得的是一個元組,須要加上0索引取值 num = struct.unpack('i',num_dic)[0] str_dic = sk.recv(num).decode('utf-8') dic = json.loads(str_dic) # 不根據協議接收直接用1024大小的長度來接收 else: dic = json.loads(sk.recv(1024).decode('utf-8')) return dic
接下來是Client端與Sever端的代碼,這裏模擬不一樣標識的客戶端可以同時訪問Server端的狀況:
# -*- coding:utf-8 -*- import socket from pro_trance import pro_send,pro_recv cid = '123' client = socket.socket() client.connect(('127.0.0.1',9003)) while 1: # 這裏用msg模擬長數據,因此選擇避免粘包的發送方式 msg = input('>>>:').strip() bytes_msg = msg.encode('utf-8') len_by_msg = len(bytes_msg) dic = {'cid':cid,'data_size':len_by_msg} # 協議發送攜帶信息的字典 pro_send(client,dic) # 再發送數據 client.sendall(bytes_msg) # 接收 —— 直接收 實際是否考慮粘包視具體設計而定 str_recv = client.recv(1024).decode('utf-8') print('server reply:',str_recv)
# -*- coding:utf-8 -*- import socket from pro_trance import pro_send,pro_recv cid = '456' client = socket.socket() client.connect(('127.0.0.1',9003)) while 1: # 這裏用msg模擬長數據,因此選擇避免粘包的發送方式 msg = input('>>>:').strip() bytes_msg = msg.encode('utf-8') len_by_msg = len(bytes_msg) dic = {'cid':cid,'data_size':len_by_msg} # 協議發送攜帶信息的字典 pro_send(client,dic) # 再發送數據 client.sendall(bytes_msg) # 接收 —— 直接收 實際是否考慮粘包視具體設計而定 str_recv = client.recv(1024).decode('utf-8') print('server reply:',str_recv)
# -*- coding:utf-8 -*- import socketserver from collections import defaultdict from pro_trance import pro_send,pro_recv # 通常這種數據都是放在settings文件中的 client_msg = { '123':'Naruto', '456':'Sasuke', } class Server(socketserver.BaseRequestHandler): def handle(self): dic_msg = defaultdict(list) while 1: # 根據協議接收信息字典 dic = pro_recv(self.request) # client端的id cid = dic['cid'] data_size = dic['data_size'] # 根據數據的長度接收 msg = self.request.recv(data_size).decode('utf-8') # 利用以前的默認字典構建數據 dic_msg[client_msg[cid]] dic_msg[client_msg[cid]].append(msg) print('client %s,message:%s' % (client_msg[cid], dict(dic_msg))) # 回覆 self.request.sendall('received successfully!'.encode('utf-8')) ## 注意socketserver這裏不用加close方法 ## BaseRequestHandler有一個finish的相關的方法 if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',9003),Server) server.serve_forever()
演示效果以下: