在socket的server端处理client端发来的数据
一、楔子
最近做了一个需求遇到一个坑,归结成一个小问题,其实就是在socket的server端处理client端发来的数据的问题,现将这个问题总结一下,本文将数据在server端以字典的形式存储。
另外,由于我想做一个动图的演示,所以本文没有用MarkDown写,希望本文的干货能弥补格式带来的不适ε=(´ο`*)))
二、基于原生UDP的实现
我们这里用“默认字典”去实现数据的构建,基于原生UDP的代码如下:
# -*- 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
实现效果如下:
三、基于并发的TCP实现
socketsever模块为我们提供了并发的功能,我们这里直接用socketsever模块做。
另外我们考虑到TCP的粘包问题,以Client端给Server端发送数据为例:我们需要先给Server端发送一个带有”信息“的字典。这里的信息可以是我们将要发送的数据的大小,也可以是其他的必要的数据,这里我们给Server端发送Client端的标识cid以及将要发送的数据的大小。
同时考虑到程序的解耦,我们把包含重要信息字典的处理放在pro_trance.py文件中,两端在收发信息的时候直接import即可。里面的代码为:
# -*- 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()
演示效果如下: