标签:
首先什么是I/O:
I/O(input/output),即输入/输出端口。每个设备都会有一个专用的I/O地址,用来处理自己的输入输出信息
I/O分为磁盘io和网络io,这里说的是网络io
IO多路复用:
I/O多路复用指:通过一种机制,可以监视多个描述符(socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
Linux
Linux中的 select,poll,epoll 都是IO多路复用的机制。
Linux下网络I/O使用socket套接字来通信,普通I/O模型只能监听一个socket,而I/O多路复用可同时监听多个socket.
I/O多路复用避免阻塞在io上,原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理.
Python
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
1 Windows Python: 2 3 提供: select 4 5 Mac Python: 6 7 提供: select 8 9 Linux Python: 10 11 提供: select、poll、epoll
对于select模块操作的方法:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 参数: 可接受四个参数(前三个必须) 返回值:三个列表 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 5、当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
利用select监听终端操作实例1:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import select 4 import sys 5 6 while True: 7 readable, writeable, error = select.select([sys.stdin,],[],[],1) 8 """ 9 select.select([sys.stdin,],[],[],1)就是I/O多路复用的例子,第一个参数是列表,这里放进去的是sys.stdin就是我输入 10 进去东西的描述符.第一个参数对应的是readable这个句柄,后面两个先不考虑. 11 12 """ 13 if sys.stdin in readable: 14 print ‘select get stdin‘,sys.stdin.readline() 15 16 #注意:[sys.stdin,] 这个参数不管是列表还是元组在最后的元素后面建议增加一个逗号,是一种写作习惯. 17 #这个代码可以在linux平台执行,windows不支持select方法的epoll方法.只支持select socket 方法.
利用select监听终端操作实例2:
1 #/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import socket 5 import select 6 #创建socket对象 7 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #setsockopt获得端口重用 9 #设置监听的IP与端口 10 sk.bind((‘127.0.0.1‘,8000)) 11 #设置client最大等待连接数 12 sk.listen(5) 13 sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错 14 while True: 15 readable_list, writeable_list, error_list = select.select([sk,],[],[],2) #监听第一个列表的文件描述符,如果里面有文件描述符发生改变既能捕获并放到readable_list中 16 for r in readable_list: #如果是空列表将不执行,如果是非空列表。将执行。 17 conn,addr = r.accept() 18 print addr 19 20 ################################################## 21 #执行程序并打开IE输入地址:127.0.0.1:8000 输出如下: 22 (‘127.0.0.1‘, 52155)
利用select实现伪同时处理多个Socket客户端请求:
1 #/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 import time 4 import socket 5 import select 6 #创建socket对象 7 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #setsockopt获得端口重用 9 #设置监听的IP与端口 10 sk.bind((‘127.0.0.1‘,8000)) 11 #设置client最大等待连接数 12 sk.listen(3) 13 sk.setblocking(False) #这里设置setblocking为False accept将不在阻塞,但是如果没有收到请求就会报错 14 inputs = [sk,] #将sk这个对象加入到列表中,并且赋值给inputs 15 #原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,还是服务端的socket 有关吗? 16 #是不是的把他改为动态的? 17 18 while True: 19 readable_list, writeable_list, error_list = select.select(inputs,[],[],1) #把第一个参数设为列表动态的添加 20 time.sleep(2) #暂停2秒,测试使用 21 print "inputs list :",inputs # 打印inputs列表,查看执行变化 22 print "file descriptor :",readable_list #打印readable_list ,查看执行变化 23 24 for r in readable_list: 25 if r == sk: #这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk 26 conn,address = r.accept() 27 inputs.append(conn) 28 print address 29 else: 30 #如果是客户端,接受和返回数据 31 client_data = r.recv(1024) 32 r.sendall(client_data)
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import socket 5 6 client = socket.socket() 7 client.connect((‘127.0.0.1‘,8000)) 8 client.settimeout(5) 9 10 while True: 11 client_input = raw_input(‘please input message:‘).strip() 12 client.sendall(client_input) 13 server_data = client.recv(1024) 14 print server_data
交互过程:
#1 默认,sk这个对象文件句柄就在inputs列表中select监听客户端的请求,当有客户端请求过来 client1 ---> server #用户捕获了变化readable_list = [sk,] 那么循环是有值得,判断r = sk 说明是一个新的请求链接,然后把client链接加入到inputs里 inputs = [sk,conn1,] #如果现在什么都不做,那么select无法捕获到变化:readable_list = [] #执行看下: inputs list : [<socket._socketobject object at 0x0000000002C66798>] #默认inputs list 就有一个server socket sk 对象 file descriptor : [<socket._socketobject object at 0x0000000002C66798>] #当有客户端请求过来时候,sk发生了变化,select捕获到了 (‘127.0.0.1‘, 62495) inputs list : [<socket._socketobject object at 0x0000000002C66798>, <socket._socketobject object at 0x0000000002C66800>] #第二次循环的时候,inputs = [sk,conn1,] file descriptor : [] #第二次循环的时候readable_list = [] 因为客户端没有做任何操作,没有捕获到变化所以为空 #2 又有一个新的链接过来了,谁变化了? sk 他变化了,有人向他发起了一个请求链接,那么现在inputs = [sk,conn1,conn2] readable_list = [sk] #本次循环完成之后再循环的时候 inputs = [sk,conn1,conn2,] readable_list = [] 因为我们没有继续做操作 #第一个链接 inputs list : [<socket._socketobject object at 0x0000000002C56798>] #默认只有一个对象 file descriptor : [] inputs list : [<socket._socketobject object at 0x0000000002C56798>] file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #当捕获到,判断是否是新链接,如果是加入到inputs列表中监控 (‘127.0.0.1‘, 62539) inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>] #inputs列表变更为了[sk,conn1] file descriptor : [] #因为没有后续的操作,这里没有捕获到异常所以列表为空 #第二个链接 inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>] #第一个链接没有做任何操作 file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #第二个链接过来了被捕获到,判断是否为新链接 (‘127.0.0.1‘, 62548) inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>] #加入到inputs列表中 file descriptor : [] inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>] file descriptor : [] inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>] file descriptor : []
优化:当client端退出后,在inputs列表中移除对象!
1 #/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 import time 4 import socket 5 import select 6 #创建socket对象 7 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 sk.setsockopt 9 #设置监听的IP与端口 10 sk.bind((‘127.0.0.1‘,6666)) 11 #设置client最大等待连接数 12 sk.listen(5) 13 sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错 14 inputs = [sk,] #将sk这个对象加入到列表中,并且赋值给inputs 15 #原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,连接上之后,还是服务端的socket 有关吗? 16 #是不是的把他改为动态的? 17 18 while True: 19 readable_list, writeable_list, error_list = select.select(inputs,[],[],1) #把第一个参数设为列表动态的添加 20 time.sleep(2) #测试使用 21 print "inputs list :",inputs #打印inputs列表,查看执行变化 22 print "file descriptor :",readable_list #打印readable_list ,查看执行变化 23 24 for r in readable_list: 25 if r == sk: #这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk 26 conn,address = r.accept() 27 inputs.append(conn) 28 print address 29 else: 30 #如果是客户端,接受和返回数据 31 client_data = r.recv(1024) 32 if client_data: 33 r.sendall(client_data) 34 else: 35 inputs.remove(r)#如果没有收到客户端端数据,则移除客户端句柄 因为,不管是正常关闭还是异常关闭,client端的系统底层都会发送一个消息 36 37 select socket server - server release client-connect
通过I/O多路复用让socket实现了处理多个客户端的方法,参数注解:
#第一个参数,监听的句柄序列,当有变动的时候就能捕获到把值赋值给readable_list #如果第二参数有参数,即只要不是空列表,select就能感知,然后writeabled_list就能获取值 #第三个参数监听描述符,select内部,检测列表里面的描述符在底层操作的时候有没有异常,如果异常了他也当成一个变化,把这个赋值给error_list 一般第三个参数和第一个参数相同 #第四个参数,阻塞时间,如 1秒(这个如果不写,select会阻塞住,直到监听的描述符发生变化才继续往下执行) readable_list, writeable_list, error_list = select.select(inputs,[],[],1)
对于I/O多路复用,咱们上面的例子就可以了,但是为了遵循select规范需要把读和写进行分离:
#rlist -- wait until ready for reading #等待直到有读的操作 #wlist -- wait until ready for writing #等待直到有写的操作 #xlist -- wait for an ``exceptional condition‘‘ #等待一个错误的情况
读和写他共享接收的数据,仅仅靠变量是完成不了的,还的需要借助外界的字典,字典里为每一个客户度维护了一个队列。收到的信息都放到队列了,然后返回的时候直接从队列里拿就可以了
Queue 队列
队列的特点:
1 import Queue 2 3 q = Queue.Queue() #调用队列生成对象 4 q.put(1) #存放第一个值到队列 5 q.put(2) #存放第二个值到队列 6 7 8 print ‘get frist one:‘,q.get() #获取队列的第一个值 9 print ‘get second on:‘,q.get() #获取队列的第二个值
先进先出原则第一次存放的是1,第二次存放的是2,那么我们在获取值得时候,第一次获取的就是1,第二次就是2
看下面的例子如果队列里没有值怎么办?他会等待直到有数据为止:
1 q = Queue.Queue() #调用队列生成对象 2 3 q.put(1) #存放第一个值到队列 4 q.put(2) #存放第二个值到队列 5 6 a = q.get() #获取队列的第一个值 7 print ‘get frist one:%s‘ % a 8 b = q.get() #获取队列的第二个值 9 print ‘get second one:%s‘ % b 10 c = q.get()#获取队列的第三个值 11 print ‘get third one:%s‘ % c 12 13 #结果: 14 ‘‘‘ 15 get frist one:1 16 get second one:2 17 #这里一直在等待着值进来~ 18 ‘‘‘
标签:
原文地址:http://www.cnblogs.com/saneri/p/5115048.html