python-tcp实现文件上传下载

tcp实现上传下载文件

代码存在的问题:

1、没有实现用户输入的路径的检测(太懒了),用户输入的路径不存在会导致程序中断。

2、代码的复用率不高,还可以进行简化。

3、上传文件的路径需要手动输入。

4、本来不应该设置注册功能,应该只设置登录功能。

代码放在一个文件夹内:

1、download_file : 保存用户下载的文件

2、save_up_file: 保存用户上传的文件。

3、应该还有一个文件夹,里面存放着要共享给用户的文件

4、login_file:保存用户和加密的密码,格式:用户|密码(hmac加密后)

5、server.py 和client.py

 

服务端:

提供:登录/注册,上传文件,下载文件(指定目录下的文件)

使用的模块:

import struct  将数字转成四字节的字节码
import socketserver 实现并发访问
import hmac 密码加密
import os
import sys
import json

代码的实现:主要是验证区,实现用户登录注册功能;功能区,提供上传、下载文件的功能。

服务端的代码:


import struct
import socketserver
import hmac
import os
import sys
import json
#用户密码加密
def md5(pwd):
   h = hmac.new(b'admin',pwd.encode('utf-8'))
   ret = h.digest()
   return ret
#读取login_file,返回字典
def read_login_file(path): #path=login_file
   dic = {}
   if os.path.exists(path):
       with open(path, 'r', encoding='utf-8') as fp:
           for line in fp:
               key, value = line.strip().split('|')
               dic[key] = value
       return dic
   else:
       return False
#将更新后的字典写到 login_file中
def write_login_file(dic,path):#path=login_file
   if os.path.exists(path):
       with open(path,'w',encoding='utf-8')as fp:
           for key in dic:
               msg = key+'|'+str(dic[key])+'\n'
               fp.write(msg)
       return True
   else:
       return False

#接收用户发送的用户名和密码
def recv_user(con):
   lis =[]
   user_len = con.recv(4)#接收用户的pack后的字节码
   leng = struct.unpack('i', user_len)[0]
   user = con.recv(leng).decode('utf-8')
   pwd_len = con.recv(4)
   leng = struct.unpack('i', pwd_len)[0]
   pwd = con.recv(leng).decode('utf-8')
   lis.append(user)
   lis.append(pwd)
   return lis
#用户登录验证密码
def login(user,pwd,path):#con就是accept 的con,path=login_file
   code_pwd = md5(pwd) #获取密码加密后的内容
   '''#在write_login_file是,将加密后密码转成str,这里的code_pwd是bytes类型
  所有要将code_pwd转成str类型,不然永远都不可能密码一样的。
  '''
   code_pwd=str(code_pwd)
   dic = read_login_file(path)
   # print(68,dic)
   if user in dic and dic[user]==code_pwd:
       return True #登录成功
   else:
       return False#登录失败
#用户注册函数
def register(user,pwd,path):# path=login_file
   dic = read_login_file(path)
   if user in dic:
       return False
   else:
       code_pwd = md5(pwd)
       dic[user]=code_pwd
       ret = write_login_file(dic,path)
       if ret ==True:
           return True
       else:
           return False

''' 添加的功能相应的函数: '''
#保存用户上传文件的文件夹(改成自己电脑的路径)
path_save = r'F:\installsotf\python file\python全栈\day30\作业\save_up_file'
#共享给客户的文件夹(改成自己电脑的路径)
PATH = r'F:\installsotf\python file\python全栈\day29'

'''客户端也要有相应的接收函数,在功能区中最先发送'''
#将功能列表发送给客户端
def send_function_list(con):
   msg = '1、上传文件\n2、下载文件'.encode('utf-8')
   msg_len = len(msg)
   msg_len = struct.pack('i', msg_len)
   con.send(msg_len)  # 将字符串长度发送过去。
   con.send(msg)  # 将字符串字节码发送过去。

#用户是上传文件,server是下载用户上传的文件。
def down_file(path,con):
    # 收到用户的选择。
   leng = con.recv(4) #接收文件名和大小组成的字典
   leng = struct.unpack('i', leng)[0]
   dic = con.recv(leng).decode('utf-8')
   dic = json.loads(dic) #获取到{‘file’:file,'size':size}
   # print(dic)
   path = os.path.join(path, dic['file'])
   size = dic['size']
   with open(path, 'wb')as fp:
       while size > 0:
           cont = con.recv(1024)
           fp.write(cont)
           size -= len(cont)

#对于用户是从服务端下载文件,对于服务端是传输给用户
def get_file(path):
   file=os.path.basename(path)
   size=os.path.getsize(path)
   dic = {'file':file,'size':size}
   return dic
def up_file(path,size,con):
   with open(path, 'rb')as fp:
       while size > 0:
           msg = fp.read(1024)
           con.send(msg)
           size -= len(msg)
def upto_file(path,con): #只是调用这个函数就能实现文件上传。
   # sc.send('1'.encode('utf-8')) # 告诉服务器要进行的操作
   # path=input('输入文件的绝对路径:').strip()
   if os.path.exists(path):
       dic = get_file(path)  # 获取字典【如果将path改成输入,就可以随意上传文件了】
       size = dic['size']
       dic_byte = json.dumps(dic).encode('utf-8')  # 先转成json,再转成bytes
       dic_leng = len(dic_byte)  # 获取bytes的长度
       # print(71,dic_leng)
       leng = struct.pack('i', dic_leng)
       # print(73,leng)
       con.send(leng)  # 发送字典dic的长度
       con.send(dic_byte)  # 发送字典的内容
       # 打开要操作的文件,用rb模式
       up_file(path, size,con)  # 调用上传文件
       return True
   else:
       return False
#将可以共享给客户端的文件列表发送给客户端选择:
def send_file_list(path,con):
   file_list = os.listdir(path)
   file_path = []  # 保存可下载的文件的路径
   file_name = []  # 保存文件的名字,供用户选择
   for name in file_list:
       file = os.path.join(path, name)
       if os.path.isfile(file):
           file_path.append(file)
           file_name.append(name)
   # 将文件名的列表发送给客户端。
   name_list = json.dumps(file_name).encode('utf-8')
   leng = len(name_list)
   leng = struct.pack('i', leng)
   con.send(leng)  # 将文件名列表长度发送过去
   con.send(name_list)  # 将编码后的文件名列表发送过去
   return file_path

class MySocket(socketserver.BaseRequestHandler):
   def handle(self):
       con = self.request
       path ='login_file'
       while 1:
           num=con.recv(1).decode('utf-8')
           if num=='Q':#用户发送Q过来就退出循环。
               break
           lis = recv_user(con)  # 接收用户名密码,密码加密后,返回一个字典。
           user = lis[0]
           pwd = lis[1]
           # print(93,user,pwd) 查看发送用户名和密码是否正确
           fun_list = ['login','register']
           num = int(num)
           # print(96, num) 查看发送的选择是否正确,1 login,2 register
           #利用反射减少代码量
           if hasattr(sys.modules['__main__'],fun_list[num-1]):
               fun = getattr(sys.modules[__name__],fun_list[num-1])
               # print(fun.__name__)查看是哪个函数在起作用。
               ret = fun(user,pwd,path)
               if ret ==True:#只有登录成功后才能提供上传下载功能给用户
                   con.send('yes'.encode('utf-8'))

                   '''服务端功能添加区域: '''
                   if fun.__name__=='login':#代表用户登录成功,可以提供上传下载功能给用户使用。
                       while 1:
                           send_function_list(con)#将功能列表发送给客户端
                           choice = con.recv(1).decode('utf-8')
                           if choice == '1':
                               while 1:
                                   down_file(path_save,con)
                                   num = con.recv(4).decode('utf-8')

                                   if num == 'stop':  # 客户端发送0代表要终止上传文件服务。
                                       break
                           elif choice == '2':
                               # 1、发送文件列表的长度和内容
                               file_path = send_file_list(PATH,con)  # 传输文件名列表的长度和列表的内容。
                               while 1:
                                   # 2、接收文件序号的长度和内容
                                   num_len = con.recv(4)  # 接收用户要选择哪个文件的序号的长度
                                   num_len = struct.unpack('i', num_len)[0]
                                   file_index = con.recv(num_len).decode('utf-8')  # 获取到文件的序号
                                   index = int(file_index) - 1  # 索引=序号-1
                                   if index + 1 == 0:
                                       continue  # 当客户端输入的选择文件序号不在范围内时,要重新开始
                                   path = file_path[index]  # 获取待传输文件路径
                                   size = os.path.getsize(path)  # 获取文件的大小
                                   # 下面三个函数实现文件的所有传输
                                   # get_file(path)#获取文件的大小和文件名,打包成dic
                                   # up_file(path,size)#将将dic传输给client
                                   ret = upto_file(path,con)  # 将文件内容传输给client
                                   yes_not = con.recv(4).decode('utf-8')

                                   if yes_not == 'stop':#只有接收到用户方法的stop,才会退出下载文件的功能
                                       break
                           elif choice.upper() == 'E':
                               break
                           else:
                               pass

               elif ret==False:#注册失败或登录失败
                   con.send('not'.encode('utf-8'))

           ''' 下面这么长的代码跟上面 if hasattr() 下的代码是一样功能的。 '''
server = socketserver.ThreadingTCPServer(('127.0.0.1',9997),MySocket)
server.serve_forever()






客户端:

代码实现:用户登录注册功能,用户使用上传,下载功能。

import socket
import struct
import json
import os

sc= socket.socket()
sc.connect(('127.0.0.1',9997))
''' 验证用户名密码的函数,验证区 '''
def send_user(user,pwd):#发送用户的账户和密码
  user_len = len(user.encode('utf-8'))
  leng = struct.pack('i',user_len)
  sc.send(leng)#用户的字节码长度
  sc.send(user.encode('utf-8'))#发送用户名的字节码

  pwd_len = len(pwd.encode('utf-8'))
  leng = struct.pack('i', pwd_len)
  sc.send(leng)#密码的字节码长度
  sc.send(pwd.encode('utf-8'))#密码的字节码



''' 接收服务端的功能的函数,功能区 '''
#上传文件的路径
#上传文件的路径
path = r'F:\installsotf\python file\python全栈\day30\作业\test_up'
#下载文件的保存位置
load_path=r'F:\installsotf\python file\python全栈\day30\作业\download_file'

#接收服务端提供的功能列表,功能循环中第一个调用
def recv_function_list(sc):
  leng = sc.recv(4)
  leng = struct.unpack('i', leng)[0]
  cont = sc.recv(leng).decode('utf-8')
  return cont

#将要发送的文件名和大小写到字典中,下面三个函数,实现文件的上传
def get_file(path):
  file=os.path.basename(path)
  size=os.path.getsize(path)
  dic = {'file':file,'size':size}
  return dic
def up_file(path,size):
  with open(path, 'rb')as fp:
      while size > 0:
          msg = fp.read(1024)
          sc.send(msg)
          size -= len(msg)
def upto_file(path):

  # path=input('输入文件的绝对路径:').strip()
  if os.path.exists(path):
      dic = get_file(path) # 获取字典【如果将path改成输入,就可以随意上传文件了】
      size = dic['size']
      dic_byte = json.dumps(dic).encode('utf-8') # 先转成json,再转成bytes
      dic_leng = len(dic_byte) # 获取bytes的长度
      leng = struct.pack('i', dic_leng)
      sc.send(leng) # 发送字典dic的长度
      sc.send(dic_byte) # 发送字典的内容
      # 打开要操作的文件,用rb模式
      up_file(path, size) # 调用上传文件
      return True
  else:
      return False

#下面这个函数实现文件的下载:
def down_file(path):#将文件下载到哪个位
  # 收到用户的选择。
  # print(58)
  leng = sc.recv(4) # 接收文件名和大小组成的字典
  # print(60,len(leng))
  leng = struct.unpack('i', leng)[0]
  dic = sc.recv(leng).decode('utf-8')
  dic = json.loads(dic) # 获取到{‘file’:file,'size':size}
  # print(dic)
  # print(65)
  path = os.path.join(path, dic['file'])
  size = dic['size']
  with open(path, 'wb')as fp:
      while size > 0:
          cont = sc.recv(1024)
          fp.write(cont)
          size -= len(cont)
while 1:
  print('1、登录\n2、注册')
  chioce = input('输入的选择(q/Q退出):').strip()
  chioce_list=['登录','注册']
  if chioce=='1' or chioce=='2':
      user = input('输入用户名:').strip()
      pwd = input('输入密码:').strip()
      sc.send(chioce.encode('utf-8'))#发送选择给服务端
      send_user(user,pwd)
      ret = sc.recv(3).decode('utf-8')#yes 或者 not
      if ret =='yes':
          print(f'{chioce_list[int(chioce)-1]}成功。')

          ''' 接收服务端提供功能的代码 :功能区'''
          if chioce=='1':#代表用户是在登录,且登录成功了,那么就可以享受服务端提供的上传下载文件功能。

              while 1:
                  cont = recv_function_list(sc)
                  print(cont)
                  choice = input('输入你的选择(e/E退出):').strip()
                  if choice.isdecimal():
                      sc.send(choice.encode('utf-8')) # 告诉服务器要进行的操作
                      choice = int(choice)
                      if choice == 1: # 上传文件
                          while 1:
                              path = input('输入文件绝对路径:')
                              path = path.replace('\\', '/')
                              ret = upto_file(path)
                              if ret:
                                  print('上传成功')
                              num = input('回车继续上传,stop退出上传:').strip()
                              if num=='goto' or 'stop':
                                  sc.send(num.encode('utf-8'))
                                  if num == 'stop':
                                      break
                              else:
                                  sc.send('none'.encode('utf-8'))
                      elif choice == 2:
                          # 1、接收文件列表的长度和内容
                          len_list = sc.recv(4) # 接收列表的长度字节码
                          leng = struct.unpack('i', len_list)[0] # unpacke成数字
                          leng = int(leng)
                          file_list = sc.recv(leng).decode('utf-8')
                          file_list = json.loads(file_list)
                          while 1:
                              print('-*' * 20)
                              print()
                              for i, name in enumerate(file_list, 1):
                                  print(f'{i},{name}')
                              num = input('输入你选择:').strip()
                              if num.isdecimal():
                                  num = int(num)
                                  if num > 0 and num <= len(file_list):
                                      # 2、发送文件序号的长度和内容
                                      leng = len(str(num).encode('utf-8'))
                                      leng = struct.pack('i', leng)
                                      sc.send(leng) # 将文件序号的字节长度发送过去
                                      sc.send(str(num).encode('utf-8')) # 将文件序号发送过去。
                                      # print('调用down_file前')#走到这里了
                                      # 3、接收文件的内容
                                      down_file(load_path)
                                      print('下载文件成功。')
                                      # print('调用down_file后')
                                      yes_not = input('回车继续下载,输入 stop 退出:').strip()
                                      if yes_not=='stop' or yes_not=='goto':
                                          sc.send(yes_not.encode('utf-8'))
                                          if yes_not == 'stop':
                                              break
                                      else:
                                          sc.send("none".encode('utf-8'))
                                  else:
                                      print('无此选择。')
                                      sc.send('0'.encode('utf-8'))
                              else:
                                  print('输入有误。')
                      else:
                          print('无此选择。')
                  elif choice.upper() == 'E':
                      sc.send('E'.encode('utf-8'))
                      break
                  else:
                      print('输入有误。')
                  '''接收服务端提供功能的代码的尾部:功能区'''
      else:
          print(f'{chioce_list[int(chioce)-1]}失败。')
  elif chioce.upper()=='Q':
      #发送Q给服务端,说明要退出。
      sc.send('Q'.encode('utf-8'))
      print('退出系统。')
      break
  else:
      print('输入有误。')

sc.close()



posted @ 2021-08-15 12:56  提笔按住它  阅读(171)  评论(0)    收藏  举报