本质为:引入socket模块,json序列化与粘包的解决方法


概念
客户端
- 发送文件类型(以字典类型发送并json序列化),可附带文件的名称
发送文本
- 发送文件大小(经过字节转换)
- 发送内容((经过字节转换))
服务端
接收文件类型(以字典类型接收并json反序列化),其中获取文件名称
- 需要凑足四字节(避免因为网络传输丢包)
接收文本
接收文件大小(经过字节转换)
- 需要凑足四字节(避免因为网络传输丢包)
接收内容和((经过字节转换))
- 需要凑足四字节(避免因为网络传输丢包)
代码
客户端
#按需发送文字和文件
import socket
import json
import struct
import os
#定义发送文字函数
def send_data(conn,content):
#发送文件大小
#将字符串改字节再计算长度
body_bytes = content.encode('utf-8')
msg_size = struct.pack('i', len(body_bytes))
conn.sendall(msg_size)
conn.sendall(body_bytes)
#定义发送文件函数
def send_file(conn,file):
#发送文件大小
file_size = os.stat(file).st_size
msg_size = struct.pack('i', file_size)
conn.send(msg_size)
#发送文件
has_send_file_byte = 0
file_obj = open(file, 'rb')
while has_send_file_byte < file_size:
chunk = file_obj.read(2048)
conn.sendall(chunk)
has_send_file_byte += len(chunk)
file_obj.close()
#运行函数
def run():
#对象化连接
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client.connect(('127.0.0.1', 9999))
while True:
#设置发送类型
'''
msg | word
file | file path
'''
user_input = input('>>>').strip()
if user_input.upper() == 'Q':
send_data(client,'close')
break
#判断输入类型,并匹配方法
content = user_input.split('|',1)
if len(content) != 2:
print('输入不合法,请重试')
continue
#将输入格式分类
msg_type,info = content
if msg_type == 'msg':
#发送文件类型
send_data(client,json.dumps({'msg_type':'msg'}))
send_data(client,info)
else:
#获取当前文件名称
# file_name = info.rsplit(os.sep,1)[1]
if os.sep in info:
file_name = info.rsplit(os.sep, 1)[1]
else:
file_name = info
#发送文件类型
send_data(client,json.dumps({'msg_type':'file','file_name':file_name}))
#发送文件
send_file(client,info)
client.close()
if __name__ == '__main__':
run()
服务端
#按需接收文字和文件
import socket
import struct
import json
import os
#设置接收文本函数
def save_data(conn,chunk_stream = 1024):
#接收头部信息(大小)
#循环接收是因为需要从网卡缓冲区捞数据
has_receive_byte = 0
save_header_byte = []
while has_receive_byte < 4:
chunk = conn.recv(4-has_receive_byte)
has_receive_byte += len(chunk)
save_header_byte.append(chunk)
#字节合成由列表到字节
data = b''.join(save_header_byte)
#获取头部字节
data_length = struct.unpack('i', data)[0]
#开始接收文字数据
#开始从缓冲区捞
has_receive_data = []
#已保存数据大小
has_receive_data_byte = 0
while has_receive_data_byte < data_length:
#判断字节是否满足标准1024字节,不满足就获取不足的字节的数据并获取
size = chunk_stream if (data_length-has_receive_data_byte) > chunk_stream else data_length-has_receive_data_byte
chunk = conn.recv(size)
has_receive_data.append(chunk)
has_receive_data_byte += len(chunk)
#将获取的字节拼接
word_data = b''.join(has_receive_data)
return word_data
def save_file(conn,file_name,chunk_stream = 1024):
#接收头部信息(大小)
#循环接收是因为需要从网卡缓冲区捞数据
has_receive_byte_file = 0
save_header_byte = []
while has_receive_byte_file < 4:
chunk = conn.recv(4-has_receive_byte_file)
has_receive_byte_file += len(chunk)
save_header_byte.append(chunk)
#字节合成由列表到字节
data = b''.join(save_header_byte)
#获取头部字节
data_length = struct.unpack('i', data)[0]
#开始接收文字数据
#开始从缓冲区捞
#打开文件对象
#获取保存路径
save_path = os.path.join('files',file_name)
file_obj = open(save_path,'wb')
has_receive_file_byte = 0
while has_receive_file_byte < data_length:
#判断字节是否满足标准1024字节,不满足就获取不足的字节的数据并获取
size = chunk_stream if (data_length-has_receive_file_byte) > chunk_stream else data_length-has_receive_file_byte
chunk = conn.recv(size)
file_obj.write(chunk)
has_receive_file_byte += len(chunk)
file_obj.flush()
#关闭文件对象
file_obj.close()
#运行程序
def run():
#对象化
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#端口复用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 9999))
server.listen(5)
while True:
conn, addr = server.accept()
while True:
#获取文件类型
message_type = save_data(conn).decode()
if message_type == 'close':
print('客户端关闭链接')
break
#反序列化字符
message_type_info = json.loads(message_type)
#判断类型
if message_type_info['msg_type'] == 'msg':
data = save_data(conn)
print(f'接收到消息{data.decode()}!')
else:
file_name = message_type_info['file_name']
save_file(conn,file_name)
print(f'接收到文件,已经保存到{file_name}!')
conn.close()
if __name__ == '__main__':
run()