网络编程(2)

文件传输

基础文件传输(小文件)

    # server端负责接收
    import socket
    import json
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 5002))
    sk.listen()
    conn, addr = sk.accept()
    msg_f = conn.recv(1024).decode('UTF-8')# 接收client端的文件信息
    dic_f = json.loads(msg_f)# json.loads()用于将字符串形式的数据转化为字典
    
    with open(dic_f['filename'], mode='wb') as f:
        file = conn.recv(dic_f['filesize'])# 以发送过来的文件大小接收文件
        f.write(file)
    
    conn.close()
    sk.close()
    ##############################################
    # client端负责发送
    import socket
    import os
    import json
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 5002))
    
    abs_path = r'D:\1.jpg'
    filename = os.path.basename(abs_path)
    filesize = os.path.getsize(abs_path)
    dic = {'filename': filename, 'filesize': filesize}
    str_dic = json.dumps(dic)# json.dumps()用于将字典形式的数据转化为字符串
    sk.send(str_dic.encode('UTF-8'))# 将文件信息传递给server端
    
    with open(abs_path, mode='rb') as f:
        file = f.read()
        sk.send(file)
    
    sk.close()
  • 以上server端负责接收,client端负责发送,运行后在当前目录下出现1.jpg文件。
  • 以上代码只能传输较小的文件,且隐患较多,例如当传输过程中的str_dic大于1024字节时,server端接收到的文件信息会有误;且当传输大文件时会出现更多问题。

大文件传输

    # 负责接收
    import socket
    import json
    import struct
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 5002))
    sk.listen()
    conn, addr = sk.accept()
    lens = conn.recv(4)
    lens = struct.unpack('i', lens)[0]
    msg_f = conn.recv(lens).decode('UTF-8')# 接收client端的文件信息
    dic_f = json.loads(msg_f)# json.loads()用于将字符串形式的数据转化为字典
    
    with open(dic_f['filename'], mode='wb') as f:
        while dic_f['filesize'] > 0:
            file = conn.recv(1024)
            dic_f['filesize'] -= len(file)
            f.write(file)
    
    conn.close()
    sk.close()
    ############################################################
    # 负责发送
    import socket
    import os
    import json
    import struct
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 5002))
    
    abs_path = r'C:\Users\LENOVO\Pictures\Camera Roll\WIN_20200301_14_36_34_Pro.mp4'
    filename = os.path.basename(abs_path)
    filesize = os.path.getsize(abs_path)
    dic = {'filename': filename, 'filesize': filesize}
    str_dic = json.dumps(dic)# json.dumps()用于将字典形式的数据转化为字符串
    # 为了避免粘包现象
    b_dic = str_dic.encode('UTF-8')
    lens = struct.pack('i', len(b_dic))# 将要传输的文件信息的长度转化为4字节的形式
    sk.send(lens)
    sk.send(b_dic)# 将文件信息传递给server端
    
    with open(abs_path, mode='rb') as f:
        # 传输大文件用循环读取
        while filesize > 0:
            file = f.read(1024)# 每次读取1024字节
            filesize -= 1024
            sk.send(file)
    
    sk.close()
  • 运行后在当前目录下出现相应mp4文件,且为144M较大的文件。
  • 文件发送比接收快,且发送过程中会将发送的文件进行拆解,所以当发送1024字节时,接收端不一定会接收到1024字节,会出现数据丢失现象,为了避免这种现象,server端接收数据进行循环时每次减去的数据长度应为每次接收到的数据长度。
  • 在传输文件信息时,可能会出现粘包现象,为了避免出现这种现象,需要先将文件信息的长度以4字节的形式发送给server端,再以相应的长度接收文件。

客户端合法性

  • 若客户端要连接服务端要经过合法认证,通过约定的密钥验证客户端合法性。
  • 服务端随机发送信息给请求连接的客户端,客户端将接收到的信息加上约定的密钥经过哈希算法得到一个结果,将此结果发送给服务端。
  • 而服务端同时将发送的随机信息和密钥经过哈希算法得到一个结果,将此结果和接收到客户端的结果进行比较,若结果一致则通过合法验证。
  • 注意:不能将密钥通过网络直接发送,这样可能被拦截。

哈希算法:通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。即通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

    # server端
    import socket
    import os
    import hashlib
    import hmac
    
    key = b'password'
    msg_rand = os.urandom(16)# 生成16位类型bytes的随机信息
    sk = socket.socket()
    sk.bind(('127.0.0.1', 5002))
    sk.listen()
    conn, addr = sk.accept()
    conn.send(msg_rand)# 发送16位随机信息
    # 根据随机信息和密钥进行摘要
    '''
    # 采用hashlib模块中的md5,也可以用sha1
    md5 = hashlib.md5(key)
    md5.update(msg_rand)
    res = md5.hexdigest().encode('UTF-8')# 摘要结果为str类型
    '''
    # 采用hmac模块
    hm = hmac.new(key, msg_rand)
    res = hm.digest()# 此时摘要结果为bytes类型
    msg_res = conn.recv(32)
    if res == msg_res:
        print('合法客户端')
        conn.send(b'hello')
    else:
        conn.close()
    sk.close()
    ########################################################  
    # client端
    import socket
    import hashlib
    import hmac
    
    key = b'password'
    sk = socket.socket()
    sk.connect(('127.0.0.1', 5002))
    msg_rand = sk.recv(16)# 接收随机信息
    '''
    md5 = hashlib.md5(key)
    md5.update(msg_rand)
    res = md5.hexdigest().encode('UTF-8')
    '''
    hm = hmac.new(key, msg_rand)
    res = hm.digest()
    sk.send(res)
    msg = sk.recv(1024)
    print(msg)
    sk.close()

sockesever模块

  • socketsever模块基于socket模块。

  • 常用于tcp协议的sever端处理并发的客户请求。

  • 此时多个client可与sever端通信。

      # sever端
      import time
      import socketserver
      class Mysever(socketserver.BaseRequestHandler):
          def handle(self):
              conn = self.request
              while True:
                  # 小循环可发多次消息
                  try:
                      msg_r = conn.recv(1024).decode('UTF-8')
                      conn.send(msg_r.upper().encode('UTF-8'))
                      time.sleep(0.5)
                  except ConnectionResetError:
                      break
      sever = socketserver.ThreadingTCPServer(('127.0.0.1',5002), Mysever)
      sever.serve_forever()# sever端不主动退出
      ######################################################################
      # client-n端
      import time
      import socket
      sk = socket.socket()
      sk.connect(('127.0.0.1', 5002))      
      while True:
          sk.send(b'hello')
          msg = sk.recv(1024).decode('UTF-8')
          print(msg)
    
posted @ 2020-03-22 17:37  搁浅Lee  阅读(188)  评论(0编辑  收藏  举报