python学习之-- IO多路复用 select模块

python I/O多路复用包括3个模块,上一篇已经说过概念,这里我使用的是select模块实现一个ftp并发

服务器端核心代码:

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

客户端核心代码

  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('文件下载完毕')
View Code

 

关于selectors 模块

举例:

 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)
View Code

 

posted @ 2017-06-20 10:42  十年如一..bj  阅读(185)  评论(0编辑  收藏  举报