python学习之-- IO多路复用 select模块
python I/O多路复用包括3个模块,上一篇已经说过概念,这里我使用的是select模块实现一个ftp并发
服务器端核心代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import socket,select 2 import queue,os 3 from conf import setting 4 5 6 class select_server(object): 7 ''' ftp服务器核心程序''' 8 9 def __init__(self,ip,port): 10 self.server = socket.socket() 11 self.server.bind((ip,port)) 12 self.inputs = [self.server,] # 收 13 self.outputs = [] # 发 14 self.msg = {} # 保存客户端的socket信息及对应的登录账号,密码,登录状态和客户端消息队列 15 self.client_sock_cmd = {} # 以客户端socket为健名,值为对应客户端的所有信息 16 17 def listen(self): 18 ''' 启动监听并设定服务器为非阻塞模式''' 19 self.server.listen(100) 20 self.server.setblocking(False) 21 22 def login_auth(self,name,pwd,auth_file): 23 ''' 进行账户验证''' 24 if name in auth_file: 25 if pwd == auth_file[name]['passwd']: 26 return '200' 27 return '400' 28 return '400' 29 30 def sele(self): 31 ''' 非阻塞模式操作''' 32 while True: 33 # 监听客户端的连接,返回3个列表 34 readable, writeable, exceptionable = select.select(self.inputs, self.outputs, self.inputs) 35 # 客户端的连接 36 for s in readable: 37 if s is self.server: # 如果客户端为新连入的 38 conn,addr = self.server.accept() 39 print('已新建立一个客户端的连接',conn) 40 self.inputs.append(conn) # 添加客户端的socket到inputs列表 41 conn.setblocking(False) 42 self.msg[conn] = {} # 初始化客户端的值为一个队列 43 self.msg[conn]['queue'] = queue.Queue() 44 self.msg[conn]['account'] = [] 45 self.msg[conn]['status'] = False 46 conn.send('请输入登录账号:'.encode('utf-8')) 47 conn.send('请输入登录密码:'.encode('utf-8')) 48 else: 49 try: 50 data = s.recv(1024) 51 except ConnectionResetError as e: # 客户端中断捕捉的异常 52 exceptionable.append(s) 53 break 54 if data == b'exit': 55 exceptionable.append(s) 56 break 57 self.msg[s]['queue'].put(data) # 将客户端发来的数据保存到队列中 58 self.outputs.append(s) # 将客户端的socket添加到outputs列表 59 60 for w in writeable: # 客户端写列表 61 data = self.msg[w]['queue'].get() 62 # print(data) 63 # 判断账号是否登录,未登录执行如下 64 if self.msg[w]['status'] == False: 65 self.msg[w]['account'].append(data.decode()) 66 if len(self.msg[w]['account']) == 2: 67 # 进行账号密码验证操作 68 out = self.login_auth(self.msg[w]['account'][0],self.msg[w]['account'][1],setting.account) 69 if out == '200': 70 self.msg[w]['status'] = True 71 w.send('login'.encode('utf-8')) 72 else: 73 w.send('err'.encode('utf-8')) 74 exceptionable.append(w) 75 else: 76 # 已登录执行如下 77 # 根据客户端socket进行get和put操作 78 if w in self.client_sock_cmd: 79 if self.client_sock_cmd[w]['cmd'] == b'put': 80 # 进行put上传操作 81 self.client_sock_cmd[w]['file_io'].write(data) 82 self.client_sock_cmd[w]['file_io'].flush() 83 if os.path.getsize(self.client_sock_cmd[w]['filename']) == self.client_sock_cmd[w]['filesize']: 84 self.client_sock_cmd[w]['file_io'].close() 85 del self.client_sock_cmd[w] 86 print('文件保存完毕') 87 else: 88 # 进行get下载操作 89 if data.decode() == self.client_sock_cmd[w]['status']: 90 # 进行数据get操作在Linux必须要用send指定字节大小,否则报错,在windows下可以一次性发送sendall 91 # 由于这里使用了select多路复用模式,所以需要每次收发,都需要和客户端进行一次交互动作 92 if self.client_sock_cmd[w]['filesize'] - self.client_sock_cmd[w]['initsize'] >= 1024: 93 size = 1024 94 else: 95 size = self.client_sock_cmd[w]['filesize'] - self.client_sock_cmd[w]['initsize'] 96 data = self.client_sock_cmd[w]['file_io'].read(size) 97 self.client_sock_cmd[w]['initsize'] += len(data) 98 w.send(data) 99 if self.client_sock_cmd[w]['filesize'] == self.client_sock_cmd[w]['initsize']: 100 self.client_sock_cmd[w]['file_io'].close() 101 del self.client_sock_cmd[w] 102 print('文件发送完毕') 103 else: 104 # 根据客户端的socket进行字典初始化 105 self.client_sock_cmd[w] = {} 106 self.client_sock_cmd[w]['cmd'] = data.split()[0] 107 if self.client_sock_cmd[w]['cmd'] == b'put': 108 # 进行put的初始化操作 109 self.client_sock_cmd[w]['filename'], self.client_sock_cmd[w]['filesize'] = data.split()[1], int(data.split()[2]) 110 self.client_sock_cmd[w]['filename'] = os.path.join(setting.save_dir,self.client_sock_cmd[w]['filename']) 111 f = open(self.client_sock_cmd[w]['filename'], 'wb') 112 self.client_sock_cmd[w]['file_io'] = f 113 w.send('200'.encode('utf-8')) 114 else: 115 # 进行get的初始化操作 116 self.client_sock_cmd[w]['filename'] = data.split()[1] 117 self.client_sock_cmd[w]['filename'] = os.path.join(setting.save_dir,self.client_sock_cmd[w]['filename']) 118 if os.path.isfile(self.client_sock_cmd[w]['filename']): 119 self.client_sock_cmd[w]['status'] = '200' 120 self.client_sock_cmd[w]['initsize'] = 0 121 self.client_sock_cmd[w]['filesize'] = os.path.getsize(self.client_sock_cmd[w]['filename']) 122 status_msg = '%s %s' % (self.client_sock_cmd[w]['status'], str(self.client_sock_cmd[w]['filesize'])) 123 f = open(self.client_sock_cmd[w]['filename'], 'rb') 124 self.client_sock_cmd[w]['file_io'] = f 125 else: 126 status_msg = '%s 0' % ('404') 127 exceptionable.append(w) 128 w.send(status_msg.encode('utf-8')) 129 self.outputs.remove(w) 130 # 客户端异常或者退出清空对应的客户端socket信息并关闭连接 131 for e in exceptionable: 132 print('客户端退出:',e) 133 if e in self.msg: 134 del self.msg[e] 135 if e in self.outputs: 136 self.outputs.remove(e) 137 self.inputs.remove(e) 138 e.close()
客户端核心代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/bin/env python 2 #Author: zhaoyong 3 4 import socket,os 5 6 7 class client_sock(object): 8 ''' 9 ftp客户端主程序 10 ''' 11 12 def __init__(self): 13 ''' socket 实例化''' 14 self.client = socket.socket() 15 16 def conn(self,ip,port): 17 ''' 连接服务器''' 18 self.client.connect((ip,port),) 19 20 def help(self): 21 ''' 帮助信息''' 22 print(''' 23 上传下载命令如下: 24 put filename 上传文件 25 get filename 下载文件 26 ''') 27 28 def interactive(self): 29 ''' 交互模式''' 30 while True: 31 msg = self.client.recv(22).decode() 32 if msg == 'login': 33 print('登录成功') 34 break 35 if msg == 'err': 36 exit('登录失败') 37 info = input(msg).strip() 38 if not info: 39 exit('输入为空,退出') 40 self.client.send(info.encode('utf-8')) 41 while True: 42 cmd = input('请输入上传下载命令<exit 退出系统>:').strip() 43 if not cmd: 44 print('输入为空,请重新输入') 45 continue 46 if cmd == 'exit': 47 print('客户端退出完成') 48 self.client.send(b'exit') 49 break 50 if len(cmd.split()) == 2: 51 # 反射调用类方法 52 if hasattr(self,cmd.split()[0]): 53 fun = getattr(self,cmd.split()[0]) 54 fun(cmd) 55 else: 56 self.help() 57 else: 58 self.help() 59 60 def put(self,cmd): 61 ''' 客户端执行的put功能''' 62 # 提取文件名 63 file_name = cmd.split()[1].strip() 64 if os.path.isfile(file_name): 65 # 提取上传的文件大小 66 file_size = os.path.getsize(file_name) 67 file_mess = '%s %s' % (cmd, str(file_size)) 68 # 将文件命令,文件名,文件大小发送到服务器 69 self.client.send(file_mess.encode('utf-8')) 70 # 打开文件读取并发送 71 mess = self.client.recv(1024).decode() 72 if mess == '200': 73 f = open(file_name, 'rb') 74 self.client.sendall(f.read()) 75 f.close() 76 print('文件上传完毕') 77 else: 78 print('文件不存在') 79 80 def get(self,cmd): 81 ''' 客户端执行的get功能''' 82 self.client.send(cmd.encode('utf-8')) 83 # 以下为接收到服务器发来的文件名和文件大小 84 file_name = cmd.split()[1] 85 file_mess = self.client.recv(1024).decode() 86 file_mess = file_mess.split() 87 file_status, file_size = file_mess[0], int(file_mess[1]) 88 if file_status == '404': 89 print('文件不存在') 90 return 0 91 # 接收到服务器端的状态码后,返回给服务器端确认 92 self.client.send(file_status.encode('utf-8')) 93 # 打开写文件 94 init_size = 0 95 f = open(file_name, 'wb') 96 while init_size < file_size: 97 if file_size - init_size >= 1024: 98 size = 1024 99 else: 100 size = file_size - init_size 101 data = self.client.recv(size) 102 init_size += len(data) 103 f.write(data) 104 f.flush() 105 # 如果文件没有下载完成,就重复通知服务器 106 if init_size < file_size: 107 self.client.send(file_status.encode('utf-8')) 108 if os.path.getsize(file_name) == file_size: 109 f.close() 110 print('文件下载完毕')
关于selectors 模块
举例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import socket 2 import selectors 3 4 sel = selectors.DefaultSelector() 5 6 def accept(sock,mask): 7 conn,addr = sock.accept() 8 print('fount client',conn) 9 conn.setblocking(False) 10 sel.register(conn,selectors.EVENT_READ,read) 11 12 def read(conn,mask): 13 data = conn.recv(1024) 14 print(data) 15 if data: 16 conn.send(data) 17 else: 18 sel.unregister(conn) 19 conn.close() 20 21 server = socket.socket() 22 server.bind(('0.0.0.0',9999)) 23 server.listen(100) 24 server.setblocking(False) 25 sel.register(server,selectors.EVENT_READ,accept) 26 while True: 27 events = sel.select() 28 for key,mask in events: 29 callback = key.data 30 callback(key.fileobj, mask)