day 27

day 27 粘包、subprocess、struct、qq聊天室、Socketserver

01.粘包问题

  1. 服务端第一次发送的数据大于网络上传送的最大数据包,客户端无法精确一次性接受完毕,导致下一次发送的喝上一次未接受的的连在一起了
  2. 借助struct模块将将要发送的长度转换成固定的长度字节,在发送真实文件之前将这个固定长度的字节提前发往接收端,让接收端按照这个长度来循环接收接下来要传输的所有数据

使用struct模块解决粘包原理图

02.subprocess模块;创建子进程并将错误和结果返回

  1. 可用帮你通过代码执行操作系统的终端命令,并返回只想命令的结果

  2. 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模块 将字节长度转化为固定长度

  1. 是一个可以将很长的数据长度,压缩成固定的长度的一个标记(数据报头)
import struct
headers = struct.pack('i',20000) # i 模式会将数据称长度压缩成4个bytes
data_len=struct.unpack('i',headers) # 接收并按照 i 模式将 headers 解压,获取到真实数据长度(以元组形式)
print(data_len[0])
# 要想发送文件 ,必须先定义报头,再发送真实数据
  1. 既想发送文件长度,又想发送文件的描述信息,循环上传大文件
## 发送端(客户端) ##
# 利用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传输协议

  1. 不需要建立双向管道、不会粘包、无反馈机制
  2. 数据容易丢失
  3. 基于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() # 让服务端永远执行服务
          	
原文地址:https://www.cnblogs.com/luocongyu/p/11708230.html