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()