day 27
day 27 粘包、subprocess、struct、qq聊天室、Socketserver
01.粘包问题
- 服务端第一次发送的数据大于网络上传送的最大数据包,客户端无法精确一次性接受完毕,导致下一次发送的喝上一次未接受的的连在一起了
- 借助struct模块将将要发送的长度转换成固定的长度字节,在发送真实文件之前将这个固定长度的字节提前发往接收端,让接收端按照这个长度来循环接收接下来要传输的所有数据
02.subprocess模块;创建子进程并将错误和结果返回
-
可用帮你通过代码执行操作系统的终端命令,并返回只想命令的结果
-
import subprocess cmd=input('cmd;') res=subprocess.Popen(cmd,shell=True, # 必须将其设定为True stdout=subprocess.PIPE, # 设定结果输出 stderr=subprocess.PIPE) # 设定错误输出 result=res.stdout.read()+res.stderr.read() # 将输出结果进行拼接,无论正确执行还是执行错误都能打印出结果,数据结果为二进制流 print(result.decode('utf-8')) # Mac默认utf-8,Windows默认gbk,对二进制流解码
03.struct模块 将字节长度转化为固定长度
- 是一个可以将很长的数据长度,压缩成固定的长度的一个标记(数据报头)
import struct
headers = struct.pack('i',20000) # i 模式会将数据称长度压缩成4个bytes
data_len=struct.unpack('i',headers) # 接收并按照 i 模式将 headers 解压,获取到真实数据长度(以元组形式)
print(data_len[0])
# 要想发送文件 ,必须先定义报头,再发送真实数据
- 既想发送文件长度,又想发送文件的描述信息,循环上传大文件
## 发送端(客户端) ##
# 利用json功能将装有描述信息和文件长度的字典序列化后进行传送
import socket
import struct
import json
client = socket.socket()
client = connect(('127.0.0.1',9608)) # 设定服务端ip地址和端号,必须以元组的形式传入
with open (r'文件路径','rb')as f:
movie_bytes = f.read() # 通过rb模式将文件打开获取到二进制流
send_dic={
'file_name':'文件名',
'file_size':len(movie_bytes) # 获取文件的二进制长度
}
json_data=json.dumps(send_dic) # 将send_dic字典序列化为bytes字节格式
bytes_data=json_data.encode('utf-8') # 用utf-8格式进行编码
headers=struct.pack('i',len(bytes_data)) # 用i模式将bytes_data字符串的长度转化为4个字节标准长度
client.send(headers) # 向接收端发送转化后的字典长度
client.send(bytes_data) # 向接收端发送字典 # 注意只能发送字节形式的内容,字典等数据类型无法直接发送
init_data=0 # 对发送字节长度进行计数
num=1 # 计算该文件一共分为几次发送
with open(r'文件路径','rb') as f:
while init_data<len(move_bytes): # 设定循环中止条件
send_data=f.read(1024) # 一次从文件中获取1024字节的内容
client.send(send_data) # 将本次获取的内容发往接收端
num+=1
init_data+=len(send_data) # 因为最后一次有可能获取不到1024字节的内容
## 接收端(服务端) ##
import socket
import json
import struct
server = socket.socket()
server.bind(
('127.0.0.1', 9527)
)
server.listen(5)
while True:
conn, addr = server.accept()
try:
# 先接收字典报头
headers = conn.recv(4)
# 解包获取字典真实数据长度
data_len = struct.unpack('i', headers)[0]
# 获取字典真实数据
bytes_data = conn.recv(data_len)
# 反序列得到字典
back_dic = json.loads(bytes_data.decode('utf-8'))
print(back_dic)
# 拿到字典的文件名,文件大小
file_name = back_dic.get('file_name')
file_size = back_dic.get('file_size')
init_data = 0
# 1.以文件名打开文件,准备写入
with open(file_name, 'wb') as f:
# 一点一点接收文件,并写入
while init_data < file_size:
data = conn.recv(1024)
# 2.开始写入视频文件
f.write(data)
init_data += len(data)
print(f'{file_name}接收完毕!')
except Exception as e:
print(e)
break
conn.close()
04.UDP传输协议
- 不需要建立双向管道、不会粘包、无反馈机制
- 数据容易丢失
- 基于UDP建立QQ聊天室
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # 创建UDP传输方式的应用时必须选择为
server.bind(('127.0.0.1',9608))
while True:
msg,addr=server.recvfrom(1024) # recv改成recvfrom同样是接收来自客户端的数据
send_msg=input().encode('utf_8')
server.sendto(send_msg,addr) # send改成sendto同样是将将消息传给客户端,但要将客户端的IP地址和端口号一并输入
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_ip_port = ('127.0.0.1', 9608)
while True:
send_msg = input('客户端1: ').encode('utf-8')
# 发送消息必须要加上对方地址
client.sendto(send_msg, server_ip_port)
# 能接收任何人的消息
msg = client.recv(1024)
print(msg.decode('utf-8'))
05.socketserver模块;用来简化socket套接字服务端的代码
# 必须要创建一个类
import socketserver
## TCP 定义类必须继承socketserver.BaseRequestHandler类
class MycpServer(socketserver.BaseRequestHandler):
def handle(self): # 必须重写父类的handle属性,当客户端连接时就会自动调用该方法
print(self.client_address) # 打印当前连接到这里的客户端ip地址和端口号
while True:
try: # 防止内部出现错误时强制终止程序
data = self.request.recv(1024).decode('utf-8') # 从客户端收到数据,用utf-8解码
send_msg=data.upper() # 将接收到的消息变为大写
self.request.send(send_msg.encode('utf-8')) # 将修改后的消息返回给该客户端
except Exception as e:
print(e) # 打印错误类型
break # 当出错时执行跳出当前循环
if __name__ == '__main__': # socketserver.TCPServer 只能连接一个客户端
server = socketserver.ThreadingTCPServer( # 让服务端可以与多个客户端连接
('127.0.0.1',9608),MyTcpServer
)
server.serve_forever() # 让服务端永远执行服务