zoukankan      html  css  js  c++  java
  • 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() # 让服务端永远执行服务
              	
    
  • 相关阅读:
    [mybatis] expression: in.showLayers==true [org.apache.ibatis.ognl.ParseException: Encountered “ “in“
    IDEA pom中引用本地lib下的jar包
    IDEA Error:java xxxx 程序包不存在
    Spring注解 @NotBlank,@NotNull,@NotEmpty三者之间的区别
    [Vue warn]: Invalid prop: type check failed for prop “disabled“. Expected Boolean, got String
    centos7设置nginx开机自启
    centos7 源码安装redis设置开机自启
    MybatisPlus java.lang.NoClassDefFoundError: org/apache/velocity/context/Context
    /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)
    No Identifier specified for entity的解决办法《转载》
  • 原文地址:https://www.cnblogs.com/luocongyu/p/11708230.html
Copyright © 2011-2022 走看看