IO多路复用
I/O多路复用概念
通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
Python中有一个select模块,其中提供了:select 、poll 、epoll三个方法,分别调用系统的select、poll、epoll从而实现IO多路复用
Windows Python:
提供:select
Mac Python:
提供:select
Linux Python:
提供:select poll epoll
注意:网络操作、文件操作、终端操作等均属于IO操作
select实例一:终端操作
select监听文件句柄,当文件句柄序列发生变化,select调用系统接口得到变化
import select import sys while True: # 当用户输入内容时,stdin会发生改变,select循环检测,检测到改变后会将内容放到readable(读列表中)中 # readable 读列表 writeable写列表 error 错误列表 # timeout == 1 # 当用户没有输入的时候,列表就是空的一直 readable,writeable,error = select.select([sys.stdin,],[],[],1) if sys.stdin in readable: print "select get stdin",sys.stdin.readline()
select实例二:服务端操作
#!/usr/bin/env python # coding:utf-8 import socket import select ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind((ip_port)) sk.listen(5) # 设置不阻塞,服务端不需要一直在等待客户端的输入 sk.setblocking(False) sk1 = socket.socket() sk1.bind(('127.0.0.1',9999)) sk1.listen(5) # 是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。 sk1.setblocking(False) while True: R_list,w_list,E_list = select.select([sk,sk1],[],[],2) for r in R_list: # conn是客户端的句柄,addr是客户端的地址
conn,addr = r.accept() print addr
select实例三:通过select实现处理多请求
server端代码:
#!/usr/bin/env python # coding:utf-8 import socket import select import time ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) sk.setblocking(False) inputs = [sk] while True: R_list,w_list,E_list = select.select(inputs,[],[],2) time.sleep(2) print "input:",inputs print "result",R_list for r in R_list: if r == sk: conn,addr = r.accept() inputs.append(conn) print addr else: client_data = r.recv(1024) r.sendall(client_data)
client端代码:
#!/usr/bin/env python # coding:utf-8 import socket ip_port= ('127.0.0.1',8888) sk = socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: inp = raw_input('please input:') sk.sendall(inp) print sk.recv(1024) sk.close()
执行结果
# 没有客户端连接的时候,只监听着服务端 input: [<socket._socketobject object at 0x020E2B20>] result [] input: [<socket._socketobject object at 0x020E2B20>] # 当有客户端接入的时候 result [<socket._socketobject object at 0x020E2B20>] ('127.0.0.1', 62692) # 多了client的文件句柄 input: [<socket._socketobject object at 0x020E2B20>, <socket._socketobject object at 0x020E2B58>] result [] input: [<socket._socketobject object at 0x020E2B20>, <socket._socketobject object at 0x020E2B58>] result []
select实例四:使用后面三个参数,同时当client端失去连接的时候,从连接中删除
#!/usr/bin/env python # coding:utf-8 import socket import select import time ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) sk.setblocking(False) inputs = [sk] output = [] while True: # inputs:只有变化了,R_list才接收到,不变化不接收 # output:只要存在,w_list就一直接收 # inputs:如果有报错,会接收到E_list中 # 2: timeout 超时时间,超过两秒就不阻塞,执行下面的内容;如果不加时间,没有client端连接进来,他就一直堵塞 R_list,w_list,E_list = select.select(inputs,output,inputs,2) time.sleep(2) print "input:",inputs for r in R_list: if r == sk: conn,addr = r.accept() inputs.append(conn) print addr else: # 判断客户端是否存活,如果不存活就剔除 client_data = r.recv(1024) if client_data: r.sendall(client_data) else: inputs.remove(r)
select实例六:output讲解,与客户端接收发送数据
#!/usr/bin/env python # coding:utf-8 import socket import select import time ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) inputs = [sk] output = [] while True: # inputs:只有变化了,R_list才接收到,不变化不接收 # output:只要存在,w_list就一直接收 # inputs:如果有报错,会接收到E_list中 # 2: timeout 超时时间,超过两秒就不阻塞,执行下面的内容 R_list,W_list,E_list = select.select(inputs,output,inputs,2) print "output:",output for r in R_list: if r == sk: conn,addr = r.accept() inputs.append(conn) else: # 判断客户端是否存活,如果不存活就剔除 client_data = r.recv(1024) if client_data: # 获取数据 output.append(r) for w in W_list: w.sendall('123456') output.remove(w)
Queue
简单操作
#!/usr/bin/env python # coding:utf-8 import Queue # 定义一个对象 obj = Queue.Queue() # 打印对象队列的大小 print obj.qsize() # 向队列中添加数据 obj.put(1) print obj.qsize() obj.put(2) print obj.qsize() # 获取值(FIFO) print obj.get() # 定义队列的长度 obj = Queue.Queue(3) obj.put(1) obj.put(2) obj.put(3) print obj.qsize() # 队列满了后,不等待直接添加会报异常 obj.put_nowait(123) print obj.qsize() obj = Queue.Queue() # 队列中没有数据,会一直等待 #obj.get() # 不等待 # 队列中没有数据,会触发异常, obj.get_nowait() # 使用异常处理 try: obj.get_nowait() except Queue.Empty: print 'error'
使用队列来实现
#!/usr/bin/env python # coding:utf-8 import socket import select import Queue ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) inputs = [sk] output = [] message = {} # message = { # 'c1':队列, # 'c2':队列,【b,bb,bbb】 # } while True: # inputs:只有变化了,R_list才接收到,不变化不接收 # output:只要存在,w_list就一直接收 # inputs:如果有报错,会接收到E_list中 # 2: timeout 超时时间,超过两秒就不阻塞,执行下面的内容 R_list,W_list,E_list = select.select(inputs,output,inputs,2) print "output:",output for r in R_list: if r == sk: conn,addr = r.accept() inputs.append(conn) message[conn] = Queue.Queue() else: # 判断客户端是否存活,如果不存活就剔除 client_data = r.recv(1024) if client_data: # 获取数据 output.append(r) # 在指定的队列中插入数据 message[r].put(client_data) else: inputs.remove(r) del message[r] for w in W_list: # 去队列中去数据 try: data = message[w].get_nowait() w.sendall(data) except Queue.Empty: pass output.remove(w)
此处的Socket服务器端相比原生的Socket,他支持当某一个请求不再发送数据时,服务器端不会等待而是可以去处理其他的请求数据,但是,如果每个请求的耗时比较长时,select版本的服务器端也无法完成同时操作
SockertServer
SocketServer内部使用IO多路复用,以及多线程和多进程,从而实现并发处理多个客户端请求的Socket服务端,即:每个客户端请求连接到服务器时,Socket服务器端都会在服务器上创建一个线程或者进程,专门负责当前客户端的所有请求
计算密集型用进程,IO密集型用线程
ThreadingTCPServer进程
ThreadingTCPServer实现的Socket服务器内部会为每个client端创建一个线程,该线程用来和客户端进行交互
1: ThreadingTCPServer 基础
使用ThreadingTCPServer :
- 创建一个继承自SocketServer.BaseRequestHandler的类
- 类中必须定义一个名称为handler的方法
- 启动ThreadingTCPServer
服务器端:
#!/usr/bin/env python # coding:utf-8 import SocketServer class MyServer(SocketServer.BaseRequestHandler): # 必须有一个handle方法 def handle(self): print self.request,self.client_address,self.server conn = self.request conn.sendall('wlecome ........') Flag = True while Flag: data = conn.recv(1024) if data == 'exit': Flag = False elif data == '0': conn.sendall('2345678') else: conn.sendall('input:') if __name__ == '__main__': server = SocketServer.ThreadingTCPServer(('127.0.0.1'),MyServer) server.serve_forever()
客户端:
#!/usr/bin/env python # coding:utf-8 import socket ip_port = ('127.0.0.1',8009) sk = socket.socket sk.connect(ip_port) sk.settimeout(5) while True: data = sk.recv(1024) print 'receive:',data inp = raw_input('please input') sk.sendall(inp) if inp == 'exit': break sk.close()
twisted 事件驱动
Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如:网络协议、线程、数据库管理、网络操作、电子邮件等。
事件驱动:分为两部分,第一是注册事件,第二是触发事件
自定义一个事件驱动框架
#!/usr/bin/env python # -*- coding:utf-8 -*- # event_drive.py event_list = [] def run(): for event in event_list: obj = event() obj.execute() class BaseHandler(object): """ 用户必须继承该类,从而规范所有类的方法(类似于接口的功能) """ def execute(self): raise Exception('you must overwrite execute')
创建一个python Package,
将这个文件夹放在C:\Python27\Lib\site-packages
使用上面的事件驱动框架
#!/usr/bin/env python # -*- coding:utf-8 -*- from source import event_drive class MyHandler(event_drive.BaseHandler): def execute(self): print 'event-drive execute MyHandler' # 注册一个事件,把MyHandler这个类当作一个事件,注册到event_list列表中 event_drive.event_list.append(MyHandler) event_drive.run()
Twisted-15.5.0的安装
- 解压文件
- 进入到解压的文件的目录中
- 执行编译:python setup.py build
- 执行安装:python setup.py install
python源码安装都是上面的流程