老男孩Day11作业:selectors版socket
一、作业需求:
使用SELECT或SELECTORS模块实现并发简单版FTP
允许多用户并发上传下载文件
二、readme
一、作业需求: 使用SELECT或SELECTORS模块实现并发简单版FTP 允许多用户并发上传下载文件 二、博客地址:http://www.cnblogs.com/catepython/p/8973372.html 三、运行环境 操作系统:Win10 Python:3.6.4rcl Pycharm:2017.3.4 四、功能实现 1)实现所有基本需求 2)充分利用了面向对象式编程 3)实现了单线程多并发上传/下载文件(多路复用IO)模式 五、测试 1)文件名为空判断 2)指令格式化判断 3)文件名/用户目录有效判断 六、备注 1、服务端put()函数中一遇到 data = conn.recv(size) 逻辑 就会出现“BlockingIOError:无法立即完成一个非阻止性套接字操作”报错 注:尝试过异常处理但效果不明显 完美解决办法:客户端直接把所需上传文件路径与操作字典{'action':'put','file':'e:\xx\xx\'} 一并发送至服务端。然后服务端直接读取路径并写入server目录中,这样就避免了“BlockingIOError”异常报错
三、流程图
四、目录结构图
五、核心代码
bin目录程序运行文件
# -*- coding:utf-8 -*- # Author:D.Gray from core import socket_client start = socket_client.MyClient()
# -*- coding:utf-8 -*- # Author:D.Gray from core import socket_server start = socket_server.MyServer() start.start()
conf配置文件目录
# -*- coding:utf-8 -*- # Author:D.Gray import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOCAL_HOST = ('localhost',6969) HOME_PATH = os.path.join(BASE_DIR,'File','home_file') SERVER_PATH = os.path.join(BASE_DIR,'File','server_file')
core主逻辑程序目录
# -*- coding:utf-8 -*- # Author:D.Gray import selectors import socket,json,os,hashlib,sys,time from conf import setting class MyClient(object): def __init__(self): self.client = socket.socket() self.client.connect(setting.LOCAL_HOST) self.run = self.run() def run(self): ''' 启动函数 :return: ''' while True: help = '''\033[33;1m "get":"用于下载文件,例如:get readme.txt 即 get 文件名" "put":"用于上传文件,例如:put readme.txt 即 put 文件名" \033[0m''' print(help) meg = input('root@selectors_client>>>:').strip().split() if len(meg) == 0:continue if len(meg) >= 2: dic = { 'action':meg[0], 'filename':meg[1], 'filesize':0 } if hasattr(self,str(dic['action'])): action = getattr(self,str(dic['action'])) action(dic) else: print('\033[31;1m请输入有效操作指令\033[0m') else: print('\033[31;1m请输入有效操作指令\033[0m') def put(self,*args): ''' 上传文件至服务端函数 :param args: :return: ''' args = args[0] # args = {'action':put,'filename':xxx} #print('in the put:',args) file_path = os.path.join(setting.HOME_PATH,args['filename']) if os.path.isfile(file_path): file_totle_size = os.stat(file_path).st_size #获取文件大小 args['filesize'] = file_totle_size #在字典中增加 文件大小键值对 self.client.send(json.dumps(args).encode()) #字典序列化上传 print('\033[34;1m发送文件相关信息至服务端:\n%s\033[0m'%args) recv_server = self.client.recv(1024) #接收服务端回调:允许客户端上传文件参数 if recv_server.decode() == '100': print('\033[35;1m收到服务回调信息:%s\033[0m'%recv_server.decode()) print('开始上传文件...') i = 0 file_totle_size = int(file_totle_size) send_size = 0 self.m = hashlib.md5() with open(file_path,'rb') as f: while send_size < file_totle_size: if file_totle_size - send_size < 1024: size = file_totle_size - send_size data = f.read(size) send_size += len(data) else: size = 1024 data = f.read(size) send_size += len(data) self.client.send(data) ''' 进度条 ''' str1 = '已上传 %s Bytes'%send_size str2 = '%s%s'%(round((send_size/file_totle_size)*100,2),'%') str3 = '[%s%s]'%('*'*i,str2) sys.stdout.write('\033[32;1m\r%s%s\033[0m'%(str1,str3)) sys.stdout.flush() while i<=50: i += 2 break time.sleep(1) ''' 加密认证 ''' self.m.update(data) self.encryption() else: print('\033[31;1m收到服务端异常回调信息\033[0m',recv_server.decode()) else: print('\033[31;1m未找到该文件\033[0m') def get(self,*args): dic = args[0] self.client.send(json.dumps(dic).encode()) server_recv = self.client.recv(1024) #获取服务端回调参数或文件大小 if server_recv.decode() != '204': print('收到服务端发送过来的文件大小[%s bytes]'%server_recv.decode()) file_path = os.path.join(setting.HOME_PATH,dic['filename']) print('开始接受文件') file_totle_size = int(server_recv.decode()) self.m = hashlib.md5() recv_size = 0 i = 0 with open(file_path,'wb') as f: while recv_size < file_totle_size: if file_totle_size - recv_size < 1024: size = file_totle_size - recv_size else: size = 1024 data = self.client.recv(size) self.m.update(data) f.write(data) recv_size += len(data) ''' 进度条 ''' str1 = '已下载 %s Bytes' % recv_size str2 = '%s%s' % (round((recv_size / file_totle_size) * 100, 2), '%') str3 = '[%s%s]' % ('*' * i, str2) sys.stdout.write('\033[32;1m\r%s%s\033[0m' % (str1, str3)) sys.stdout.flush() i += 2 time.sleep(1) ''' 加密认证 ''' self.encryption() else: print('服务端未找到该文件') def encryption(self): ''' 文件加密函数 :return: ''' enc = input('\n文件已上传是否需要加密(n取消加密)>>>:') if enc == 'n': self.client.recv(1024) #因服务端无论客户端是否选择加密都会发送加密消息过来,所以这里也必须接受下防止粘包 print('已取消加密文件上传成功') else: file_md5 = self.m.hexdigest() server_md5 = self.client.recv(1024) #接受服务端文件加密信息 print("\033[32;1m本地文件加密:%s\n服务端文件加密:%s\033[0m" % (file_md5 ,server_md5.decode())) if file_md5 == server_md5.decode(): print("\033[32;1m加密认证成功\033[0m") else: print("加密认证失败")
# -*- coding:utf-8 -*- # Author:D.Gray import selectors import socket,os,json,errno,hashlib,time from conf import setting sel = selectors.DefaultSelector() class MyServer(object): def __init__(self): self.server = socket.socket() def start(self): ''' 启动函数 :return: ''' print('等待链接...') self.server.bind(setting.LOCAL_HOST) self.server.listen(100) self.server.setblocking(False) self.register() def register(self): ''' 注册函数 :return: ''' sel.register(self.server,selectors.EVENT_READ,self.accept) while True: events = sel.select() for k,mask in events: callback = k.data callback(k.fileobj,mask) def accept(self,server,mask): ''' 服务器监听函数 :param server: :param mask: :return: ''' conn,self.addr = server.accept() print('\033[32;1m已和客户端[%s]建立了链接\033[0m'%(conn)) conn.setblocking(False) sel.register(conn,selectors.EVENT_READ,self.read) def read(self,conn,mask): ''' 接收客户端信息函数 :param conn: :param mask: :return: ''' data = conn.recv(1024) #print('in the read conn:',conn) if data: action_dic = json.loads(data) #序列化data={'action':xxx} print('\033[35;1m收到客户端操作指令:%s\033[0m'%action_dic['action']) if hasattr(self,str(action_dic['action'])): action = getattr(self,str(action_dic['action'])) action(action_dic,conn) #此时传参一定要用conn,千万不能传self.conn 多并发时会出现异常 else: print('\033[31;1m客户端已断开\033[0m') conn.unregister(conn) #关闭客户端链接 conn.close() def put(self,*args): ''' 服务端接收客户端文件函数 :param args: :return: ''' conn = args[1] #客户端链接地址 dic = args[0] #操作字典 file_path = os.path.join(setting.SERVER_PATH,dic['filename']) print('\033[34;1m已收到客户端传来的文件相关信息:\n%s\033[0m'%dic) conn.send(b'100') print('\033[35;1m发送回调给客户端:100\033[0m') print('开始接收客户端文件') self.m = hashlib.md5() with open(file_path,'wb') as f: with open(dic['file'],'rb')as fr: line = fr.read() f.write(line) self.m.update(line) self.encryption(conn) def get(self,*args): ''' 服务端上传文件至客户端函数 :param args: :return: ''' conn = args[1] dic = args[0] print('in the get:',dic) file_path = os.path.join(setting.SERVER_PATH,dic['filename']) if os.path.isfile(file_path): file_totle_size = os.stat(file_path).st_size conn.send(str(file_totle_size).encode()) print('开始发送文件给客户端') self.m = hashlib.md5() with open(file_path,'rb') as f: for line in f: self.m.update(line) conn.send(line) self.encryption(conn) else: conn.send(b'204') print('未找到该文件') def encryption(self,*args): ''' 加密函数 :param args: :return: ''' conn = args[0] server_md5 = self.m.hexdigest() conn.send(str(server_md5).encode()) print('文件操作完成并发送加密信息【%s】至客户端' % server_md5)