zoukankan      html  css  js  c++  java
  • 文件上传下载、socketserver(并发)、解读socketserver源码

    一.文件上传

    学习了socket套接字,我们现在可以写一个文件上传的程序,如下示例:

    import socket
    import struct
    
    server = socket.socket()  #创建socket
    server.bind(('127.0.0.1', 8000))  #绑定IP端口
    server.listen(5)   #开始监听,设置client最大的能等待接数
    while True:
        print("服务器等待连接...")
        conn, addr = server.accept()   #conn代表客户端socket对象;addr是客户端Ip地址
        while True:
            try:
                # 接收文件名长度:
                header_pack = conn.recv(4)  # 接收压缩的4个字节(文件名的长度)
                data_length = struct.unpack('i', header_pack)[0]  # 解压接收到的4个字节得到文件名的长度
                # print("文件名长度",data_length)
    
                # 取出文件名:
                ret = conn.recv(data_length)  # 根据文件名长度取文件名
                name = ret.decode('utf-8')
    
                # 接收文件大小:
                size = conn.recv(4)  # 接收压缩的4个字节(文件的大小)
                file_size = struct.unpack('i', size)[0]  # 解压接收到的4个字节得到文件的大小
                print("接收文件:%s,大小为%s字节" % (name, file_size))
    
                recv_data_length = 0  # 计数,初始为0    已接收数据的长度
                #循环接收
                while recv_data_length < file_size:
                    data = conn.recv(1024)
                    # with open(name,mode='a',encoding='utf-8') as f1:
                    #     f1.write(data.decode('utf-8'))
                    with open(name, mode='ab') as f1:
                        f1.write(data)
                    recv_data_length += len(data)  # 计数每次加1024
    
                print("接收完成")
            except ConnectionResetError as e:
                break
        conn.close()
    服务端代码
    import socket
    import os
    import struct
    client = socket.socket()
    client.connect(("127.0.0.1",8000))
    while 1:
        file_name = input("要上传的文件名")
        if file_name == "exit":
            break
        if os.path.exists(file_name):
            client.send(struct.pack('i',len(file_name)))  #发送文件名的长度
            client.send(file_name.encode('utf8'))    #发送文件名
    
            file_size = os.path.getsize(file_name)   #发送文件的大小
            client.send(struct.pack("i",file_size))
    
            with open(file_name,"rb") as f:   #发送文件
                for line in f:
                    client.send(line)
            print(client.recv(1024).decode('utf8'))
        else:
            print("文件不存在,请重新输入!")
            continue
        break
    client.close()
    客户端代码

    *直接发送文件数据且避免黏包现象发生该怎么写?(用socket模块解决)

      服务端代码

    import socket
      import json
      import struct
      import hashlib
    
      server = socket.socket()
      server.bind(('127.0.0.1',8899))
      server.listen(5)
    
      while 1:
          print("server is working....")
          conn,addr = server.accept()
          while 1:
              dic_json_len_pack = conn.recv(4)  # 接收字典字节码长度的pack值
              dic_json_len = struct.unpack('i',dic_json_len_pack)[0]
              dic_json = conn.recv(dic_json_len).decode('utf8')
              dic = json.loads(dic_json)
              # b'xxxxxxxx{"":"","":"","":"","":""}xxxxxxxxxxxxxxxxxxxxxxxxxx'
              operate = dic.get('operate')
              file_name = dic.get('file_name')
              file_size = dic.get('file_size')
    
              md5 = hashlib.md5()
              with open(file_name,'wb') as f:
                  recv_len = 0
                  while recv_len < file_size:
                      line = conn.recv(1024)
                      recv_len += len(line)
                      f.write(line)
                      md5.update(line)
                      print('接收完成,接收了%s,发送了%s' % (recv_len,file_size))
              print('接收完成')
              conn.send(b'ok')
              print(md5.hexdigest())
              client_md5_val = conn.recv(1024).decode('utf8')
              if client_md5_val == md5.hexdigest():
                  conn.send(b'200')
              else:
                  conn.send(b'400')
          conn.close()

      客户端代码

    import socket
      import os
      import json
      import struct
      import hashlib
    
      client = socket.socket()
      client.connect(('127.0.0.1',8899))
    
      while 1:
          cmd = input('请输入命令>>>')  # 'put a.txt'
          operate,file_name = cmd.strip().split()
          file_size = os.path.getsize(file_name)
          print('文件大小是---',file_size)
          dic = {
              'operate':operate,
              'file_name':file_name,
              'file_size':file_size
          }
          dic_json = json.dumps(dic).encode('utf-8')   # 字典序列化后转成字节码
          dic_json_len_pack = struct.pack('i',len(dic_json))  # 对字典序列化并转成字节码的长度进行pack
          client.send(dic_json_len_pack)
          client.send(dic_json)  # 发送字典序列化后的字节码
    
          md5 = hashlib.md5()
          with open(file_name,'rb') as f:
              for line in f:
                  client.send(line)
                  md5.update(line)
          print(md5.hexdigest())
    
          client.recv(1024).decode('utf8')
          md5_val = md5.hexdigest()
          client.send(md5_val.encode('utf8'))
          response = client.recv(1024).decode('utf8')
          if response == '200':
              print('文件完整')
          else:
              print('文件上传失败!')

      上面代码如何把避免黏包现象的?

        --client 先给server 发送字典的长度的pack包,再发送字典,之后发送文件数据,server先接收4个字节的pack包,惊醒unpack后得到字典长度,再根据字典长度结合接收字典,最后在循环接受文件数据.并且该例还惊醒了MD5算法来进行文件一致性校验

    二.socketserver(并发)

      在此我们发现在服务端和客户端的时候,在建立连接之前,总是要写一些固定的重复代码来,比如socket.socket()(:创建套接字对象),bind(),accpet()等等,学习了socketserver (并发)之后,就可以少些一些代码,并且实现并发,如下实例:

    import socketserver
      class Myserver(socketserver.BaseRequestHandler):
          def handle(self):
              """
              到此已经是等待跟客户端连接的状态
              所以从这里写代码正常逻辑代码
              conn 用self.request替换即可
              """
    
      # 1 创建socket对象   2 self.socket.bind()    3 self.socket.listen(5)
      server = socketserver.ThreadingTCPServer(('127.0.0.1',8800),Myserver)
    
      server.serve_forever()  # self.accept()

    三、解读python中socketserver源码

      结合下图中类的继承关系和类中的方法,分析socketserver代码的执行过程:

      a、初始化相关过程:server = socketserver.ThreadingTCPServer(('127.0.0.1',8800),Myserver)

        (1)TCPServer类中的__init__方法:

                         TCPServer类中主动执行BaseServer类中的__init__ 方法(把自己创建的Myserver类传参);

                         创建socket.socket()对象;

                         server_bind() -- 在TCPServer类中执行了socket.bind(self.server_address)

                         server_activate() -- 在TCPServer类中执行了socket.listen(5)

        (2)BaseServer类中的__init__ 方法:

          将参数server_address(ip地址和端口号)赋值给了self.server_address;

                         将形参RequestHandlerClass(实参是我们自己创建的Myserver类)赋值给了self.RequestHandlerClass;

     

      b、执行serve_forever的相关代码:

        (1)执行BaseServer类中的serve_forever()方法:

          注意看if ready后边的那句self._handle_request_noblock(),其他先忽略;

        (2)执行BaseServer中的_handle_request_noblock(self)方法:

          看第一个try中request,client_address = self.get_request(),

            TCPServer中有get_request()方法,方法中是return self.socket.accept(),即等待连接;

                         再看第二个try中的self.process_request(request,client_address)

         (3)连接成功之后拿到了request(即conn)和client_address(即addr)再去执行BaseServer类中的.process_request方法;

            创建线程,执行方法process_request_thread()

        (4)执行ThreadingMixIn 类中的process_request_thread(self, request, client_address)方法:

                         看try中self.finish_request(request,client_address)

        (5)执行BaseServer中的finish_request(request,client_address)方法:

          此时还记得RequestHandlerClass这个参数吗?正是我们执行BaseServer中__init__方法时传过来的自己创建的类Myserver,类() 即实例化一个Myserver对象,并且传了两个参数(conn,addr),但是我们自己写的Myserver类中没有__init__方法,所以执行Myserver父类BaseRequestHandler类中的__init__方法,并且带了每个连接的线程的conn和addr:

         (6)执行BaseRequestHandler中的__init__方法:

          此时self是Myserver类的对象,所以优先去执行Myserver类中handle方法。

     附录:以下是各类的继承关系以及类中成员方法:

       

     

     

  • 相关阅读:
    redis远程连接超时
    GNU LIBC源代码学习之strcmp
    计算最小生成树
    域名和空间的绑定问题
    Spring MVC 基于Method的映射规则(注解版)
    Spring MVC 基于URL的映射规则(注解版)
    手把手教你编写Logstash插件
    Ruby中如何识别13位的时间戳
    [logstash-input-http] 插件使用详解
    Java直接(堆外)内存使用详解
  • 原文地址:https://www.cnblogs.com/wxj1129549016/p/9596039.html
Copyright © 2011-2022 走看看