python网络编程——IO多路复用之epoll

来源:http://www.cnblogs.com/maociping/p/5132583.html


1、内核EPOLL模型讲解

    此部分参考http://blog.csdn.net/mango_song/article/details/42643971博文并整理

    首先我们来定义流的概念,一个流可以是文件,socket,pipe等可以进行I/O操作的内核对象。不管是文件,还是套接字(socket),还是管道(pipe),我们都可以把他们看作流。

    之后我们来讨论I/O操作,通过read,我们可以从流中读入数据;通过write,我们可以往流中写入数据。现在假定1种情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读数据,但是服务器端还没有把数据传回来),这时候该怎么办?

    阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你还不知道快递什么时候过来,而且你也没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打电话(假定一定能叫醒你)。

    非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他打个电话:“你到了没?”

    很明显一般人不会用第二种做法,不仅显得无脑,浪费话费不说,还占用了快递员大量的时间。

    大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片

    为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的)当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。

    假设有一个管道,进程A为管道的写入方,B为管道的读出方。假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”。也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。

    这四种情形涵盖了四个I/O事件,内核缓冲区满,内核缓冲区空,内核缓冲区非空,内核缓冲区非满。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。

    然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。于是再来考虑非阻塞忙轮询的I/O方式,我们发现可以同时处理多个流(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):

1 while true {  
2      for i in stream[]; {  
3            if i has data  
4            read until unavailable  
5         }  
6 }  

    我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。

    为了避免CPU空转,可以引进一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样:

复制代码
1 while true {  
2       select(streams[])  
3       for i in streams[] {  
4             if i has data  
5             read until unavailable  
6        }  
7 }  
复制代码

    于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。

    但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。再次说了这么多,终于能好好解释epoll了。
    epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的(复杂度降低到了O(1))。
    在讨论epoll的实现细节之前,先把epoll的相关操作列出:
复制代码
1 epoll_create创建一个epoll对象,一般epollfd = epoll_create()  
2 epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件  
3  比如  
4 epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入  
5 epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入  
6 epoll_wait(epollfd,...)等待直到注册的事件发生  
7 (注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。  
复制代码

 一个epoll模式的代码大概的样子是:

  1. while true {  
  2.     active_stream[] = epoll_wait(epollfd)  
  3.     for i in active_stream[] {  
  4.         read or write till  
  5.     }  
  6.  } 

2 python中的epoll

   从以上可知,epoll是对select、poll模型的改进,提高了网络编程的性能,广泛应用于大规模并发请求的C/S架构中。

  1、触发方式:

     边缘触发/水平触发,只适用于Unix/Linux操作系统

   2、原理图

  3、一般步骤

  1. Create an epoll object——创建1个epoll对象
  2. Tell the epoll object to monitor specific events on specific sockets——告诉epoll对象,在指定的socket上监听指定的事件
  3. Ask the epoll object which sockets may have had the specified event since the last query——询问epoll对象,从上次查询以来,哪些socket发生了哪些指定的事件
  4. Perform some action on those sockets——在这些socket上执行一些操作
  5. Tell the epoll object to modify the list of sockets and/or events to monitor——告诉epoll对象,修改socket列表和(或)事件,并监控
  6. Repeat steps 3 through 5 until finished——重复步骤3-5,直到完成
  7. Destroy the epoll object——销毁epoll对象

  4、相关用法

import select 导入select模块

epoll = select.epoll()创建一个epoll对象

epoll.register(文件句柄,事件类型)注册要监控的文件句柄和事件

事件类型:

  select.EPOLLIN    可读事件

  select.EPOLLOUT   可写事件

  select.EPOLLERR   错误事件

  select.EPOLLHUP   客户端断开事件

epoll.unregister(文件句柄)  销毁文件句柄

epoll.poll(timeout) 当文件句柄发生变化,则会以列表的形式主动报告给用户进程,timeout

                     为超时时间,默认为-1,即一直等待直到文件句柄发生变化,如果指定为1

                     那么epoll每1秒汇报一次当前文件句柄的变化情况,如果无变化则返回空

epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)

epoll.modfiy(fineno,event)fineno为文件描述符 event为事件类型  作用是修改文件描述符所对应的事件

epoll.fromfd(fileno)从1个指定的文件描述符创建1个epoll对象

epoll.close()   关闭epoll对象的控制文件描述符

   5 实例:客户端发送数据 服务端将接收的数据返回给客户端

复制代码
 1 #!/usr/bin/env python
 2 #-*- coding:utf-8 -*-
 3 
 4 import socket
 5 import select
 6 import Queue
 7 
 8 #创建socket对象
 9 serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 #设置IP地址复用
11 serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
12 #ip地址和端口号
13 server_address = ("127.0.0.1", 8888)
14 #绑定IP地址
15 serversocket.bind(server_address)
16 #监听,并设置最大连接数
17 serversocket.listen(10)
18 print  "服务器启动成功,监听IP:" , server_address
19 #服务端设置非阻塞
20 serversocket.setblocking(False)  
21 #超时时间
22 timeout = 10
23 #创建epoll事件对象,后续要监控的事件添加到其中
24 epoll = select.epoll()
25 #注册服务器监听fd到等待读事件集合
26 epoll.register(serversocket.fileno(), select.EPOLLIN)
27 #保存连接客户端消息的字典,格式为{}
28 message_queues = {}
29 #文件句柄到所对应对象的字典,格式为{句柄:对象}
30 fd_to_socket = {serversocket.fileno():serversocket,}
31 
32 while True:
33   print "等待活动连接......"
34   #轮询注册的事件集合,返回值为[(文件句柄,对应的事件),(...),....]
35   events = epoll.poll(timeout)
36   if not events:
37      print "epoll超时无活动连接,重新轮询......"
38      continue
39   print "" , len(events), "个新事件,开始处理......"
40   
41   for fd, event in events:
42      socket = fd_to_socket[fd]
43      #如果活动socket为当前服务器socket,表示有新连接
44      if socket == serversocket:
45             connection, address = serversocket.accept()
46             print "新连接:" , address
47             #新连接socket设置为非阻塞
48             connection.setblocking(False)
49             #注册新连接fd到待读事件集合
50             epoll.register(connection.fileno(), select.EPOLLIN)
51             #把新连接的文件句柄以及对象保存到字典
52             fd_to_socket[connection.fileno()] = connection
53             #以新连接的对象为键值,值存储在队列中,保存每个连接的信息
54             message_queues[connection]  = Queue.Queue()
55      #关闭事件
56      elif event & select.EPOLLHUP:
57         print 'client close'
58         #在epoll中注销客户端的文件句柄
59         epoll.unregister(fd)
60         #关闭客户端的文件句柄
61         fd_to_socket[fd].close()
62         #在字典中删除与已关闭客户端相关的信息
63         del fd_to_socket[fd]
64      #可读事件
65      elif event & select.EPOLLIN:
66         #接收数据
67         data = socket.recv(1024)
68         if data:
69            print "收到数据:" , data , "客户端:" , socket.getpeername()
70            #将数据放入对应客户端的字典
71            message_queues[socket].put(data)
72            #修改读取到消息的连接到等待写事件集合(即对应客户端收到消息后,再将其fd修改并加入写事件集合)
73            epoll.modify(fd, select.EPOLLOUT)
74      #可写事件
75      elif event & select.EPOLLOUT:
76         try:
77            #从字典中获取对应客户端的信息
78            msg = message_queues[socket].get_nowait()
79         except Queue.Empty:
80            print socket.getpeername() , " queue empty"
81            #修改文件句柄为读事件
82            epoll.modify(fd, select.EPOLLIN)
83         else :
84            print "发送数据:" , data , "客户端:" , socket.getpeername()
85            #发送数据
86            socket.send(msg)
87 
88 #在epoll中注销服务端文件句柄
89 epoll.unregister(serversocket.fileno())
90 #关闭epoll
91 epoll.close()
92 #关闭服务器socket
93 serversocket.close()
复制代码
复制代码
 1 #!/usr/bin/env python
 2 #-*- coding:utf-8 -*-
 3 
 4 import socket
 5 
 6 #创建客户端socket对象
 7 clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 8 #服务端IP地址和端口号元组
 9 server_address = ('127.0.0.1',8888)
10 #客户端连接指定的IP地址和端口号
11 clientsocket.connect(server_address)
12 
13 while True:
14     #输入数据
15     data = raw_input('please input:')
16     #客户端发送数据
17     clientsocket.sendall(data)
18     #客户端接收数据
19     server_data = clientsocket.recv(1024)
20     print '客户端收到的数据:'server_data
21     #关闭客户端socket
22     clientsocket.close() 
复制代码

 

参考资料:

      http://blog.csdn.net/mango_song/article/details/42643971

      http://www.cnblogs.com/Alanpy/articles/5125986.html

      http://scotdoyle.com/python-epoll-howto.html

      http://www.haiyun.me/archives/1056.html


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/497121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高精度定位!“天地一体”基础设施助中国北斗在商用领域“弯道超车”

来源:上观概要:从2007年第一颗北斗导航卫星成功发射,到2018年2月12日第二十八、二十九颗卫星顺利升空进入预定轨道,北斗导航卫星系统的产业化步伐也日益加快。从2007年第一颗北斗导航卫星成功发射,到2018年2月12日第二…

2018全球技术展望报告

来源:199IT互联网数据中心概要:随着人工智能(AI)和其他技术的飞速发展,智能企业层出不穷,并正融入人们的生活。埃森哲(纽交所代码:ACN)最新发布的《埃森哲技术展望2018》…

Loguru:Python 日志终极解决方案

From:https://blog.csdn.net/kdl_csdn/article/details/121146354 1、日志的重要性 日志的作用非常重要,日志可以记录用户的操作、程序的异常,还可以为数据分析提供依据,日志的存在意义就是为了能够在程序在运行过程中记录错误&am…

全球机器换人排行榜!这八个国家遥遥领先

来源: 机器人创新生态概要:如今,世界各国都在进行机器换人,希望把人力劳动从低端工作岗位释放出来,制造业自动化水平越来越高,工厂利用工业机器人获得了更低的成本、更高的效率和更快的生产速度。人力成本的…

视频监控成AI芯片主战场,海康威视和大华股份占据半壁江山

来源:MEMS概要:图像和视频的人工智能处理,是目前AI芯片商业化前景最乐观的赛道,也是玩家们弯道超车的最佳机会。 图像和视频的人工智能处理,是目前AI芯片商业化前景最乐观的赛道,也是玩家们弯道超车的最佳机…

python 命令行 解析模块 optparse、argparse

optparse:https://docs.python.org/zh-cn/3/library/optparse.htmlargparse :https://docs.python.org/zh-cn/3/library/argparse.html3.2 版后已移除 optparse 模块,并且将不再继续开发;开发转至 argparse 模块进行。 ​1、argpa…

构建插件式的应用程序框架(六)----通讯机制(ZT)

前天发了构建插件式的应用程序框架(五)----管理插件这篇文章,有几个朋友在回复中希望了解插件之间是如何通讯的。这个系列的文章写到这里,也该谈谈这个问题了,毕竟已经有了插件管理。不知道大家…

人工智能产业2018年待解的三大难题

来源:人民邮电报概要:2017年,人工智能领域在算法、政策、资金等方面已经出现了三大突破,业界欢欣鼓舞的情形很像1999年年底网络泡沫泛滥时的情形。2017年,人工智能领域在算法、政策、资金等方面已经出现了三大突破&…

矩阵连乘问题(c++)

矩阵连乘问题 问题描述: 给定n个矩阵:A1,A2,…,An,其中Ai与Ai1是可乘的,i1,2…,n-1。确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。输入数据为矩阵个数和每个矩…

QuestMobile 2017年中国移动互联网年度报告

来源:QuestMobile2017年,科技的风口兜兜转转,从直播、VR到AI再到区块链、短视频泛娱乐IP,最终在2017年底定格在了知识付费上,然而这并没有结束,紧随知识付费而来的就是撒币、大撒币……这就是中国移动互联网…

Python 读写配置文件模块: configobj 和 configParser

参考:http://www.voidspace.org.uk/python/configobj.html Python模块之ConfigParser - 读写配置文件:http://www.cnblogs.com/victorwu/p/5762931.html Python 官网 configparser 文档:https://docs.python.org/3.7/library/configparser.…

快速排序(c++)

1、快速排序的思想 快速排序就是给基准数据找在数组中正确位置的过程,一旦基准位置的正确位置找到,那基准位置左右两边经过同样的步骤递归也可以有序,最终整体数组有序。 整体可以理解为三个步骤: 1、先从队尾开始向前扫描且当l …

设计模式之禅--思维导图

原图ProcessOn里搜索:设计模式之禅

有BRT,为啥还建公交港湾

原来快速公交和普通公交要一块儿跑历山路公交港湾示意图(制图:赵国陆)   “历山路上既然跑快速公交车,有BRT站台,还要公交港湾干吗?”21日,本报报道了新公交港湾将在历山路亮相的消息后,不少市…

2018展望| AI:巨头生态开始站队,深入垂直行业才能赚钱

来源:36氪“AI改变世界”这件事,在2018年会更值得人期待。不只是BAT,京东在谈智能仓储配送,滴滴在谈智慧交通……BAT,以及滴滴、京东这样的小巨头,手中攥着大量数据、也有直接服务消费者的场景,…

归并排序(c++)

归并排序 归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列&#x…

五大风口产业全景手绘图(新能源汽车、人工智能等)

来源:一览众车概要:五大风口产业全景手绘图(新能源汽车、人工智能等)一、新能源汽车二、人工智能三、住房租赁住房租赁产业蕴含着哪些发展机会?各参与方的竞争格局如何?未来人们租房会更便利吗?…

冒泡排序(c++)

冒泡排序(Bubble Sort) 是一种计算机科学领域的较简单的排序算法。 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复…

原型模式详解

PrototypeClass.java public class PrototypeClass implements Cloneable {Overridepublic PrototypeClass clone() {try {final PrototypeClass instance (PrototypeClass) super.clone();return instance;} catch (CloneNotSupportedException e) {return null;}} } 1、构…

选择排序(c++)

选择排序 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找…