python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码

Posted 胖虎是只mao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码相关的知识,希望对你有一定的参考价值。

一、文件上传/下载

1、文件上传/下载

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

	import socket
    import json

    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 = conn.recv(1024).decode('utf8')
            dic = json.loads(dic_json)
            print(dic)

            operate = dic.get('operate')
            file_name = dic.get('file_name')
            file_size = dic.get('file_size')

            conn.send(b'200')

            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)
                    print('接收完成,接收了%s,发送了%s' % (recv_len,file_size))
        print('接收完成')
        conn.close()

服务端代码

客户端代码

	import socket
    import os
    import json

    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')
        client.send(dic_json)
        # 确认服务端接收到了文件信息
        res = client.recv(1024).decode('utf8')
        print(res)
        if res == '200':
            with open(file_name,'rb') as f:
                for line in f:
                    client.send(line)

        else:
            print('服务器异常')

客户端代码

分析上边代码,我们发现,client发送上传文件相关信息的字典序列化之后,server又给client发送了一个状态码,然后client才开始上传文件数据,思考一下如果不发送状态码,直接发送文件数据且避免黏包现象发生该怎么写?没错!可以用struct模块解决,如下示例:

服务端代码:

    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'\\xx\\xx\\xx\\xx{"":"","":"","":"","":""}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()

I表示4字节无符号整数

在这里插入图片描述

客户端代码:

	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.socket()(创建套接字对象)、bind()、acecept()等等,学习了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方法;
在这里插入图片描述
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方法。

附录:以下是各类的继承关系以及类中成员方法
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

以上是关于python 复习——网络编程——文件上传下载socketserver(并发)解读socketserver源码的主要内容,如果未能解决你的问题,请参考以下文章

期末复习——网络层

计算机网络:(终章)4万字长文,总复习

实战复习:感觉so easy

python复习提纲

Python 复习

python学习之复习整理