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 }

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事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样:

while true {

select(streams[])

for i in streams[] {

if i has data

read until unavailable

}

}

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

但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。再次说了这么多,终于能好好解释epoll了。

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的(复杂度降低到了O(1))。

在讨论epoll的实现细节之前,先把epoll的相关操作列出:

epoll_create创建一个epoll对象,一般epollfd = epoll_create()

epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件

比如

epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入

epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入

epoll_wait(epollfd,...)等待直到注册的事件发生

(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而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模式的代码大概的样子是:

while true {

active_stream[] = epoll_wait(epollfd)

for i in active_stream[] {

read or write till

}

}

2 python中的epoll

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

1、触发方式:

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

2、原理图

3、一般步骤

Create an epoll object——创建1个epoll对象

Tell the epoll object to monitor specific events on specific sockets——告诉epoll对象,在指定的socket上监听指定的事件

Ask the epoll object which sockets may have had the specified event since the last query——询问epoll对象,从上次查询以来,哪些socket发生了哪些指定的事件

Perform some action on those sockets——在这些socket上执行一些操作

Tell the epoll object to modify the list of sockets and/or events to monitor——告诉epoll对象,修改socket列表和(或)事件,并监控

Repeat steps 3 through 5 until finished——重复步骤3-5,直到完成

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 实例:客户端发送数据 服务端将接收的数据返回给客户端

#!/usr/bin/env python#-*- coding:utf-8 -*-

importsocketimportselectimportQueue#创建socket对象

serversocket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)#设置IP地址复用

serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#ip地址和端口号

server_address = ("127.0.0.1", 8888)#绑定IP地址

serversocket.bind(server_address)#监听,并设置最大连接数

serversocket.listen(10)print "服务器启动成功,监听IP:", server_address#服务端设置非阻塞

serversocket.setblocking(False)#超时时间

timeout = 10

#创建epoll事件对象,后续要监控的事件添加到其中

epoll =select.epoll()#注册服务器监听fd到等待读事件集合

epoll.register(serversocket.fileno(), select.EPOLLIN)#保存连接客户端消息的字典,格式为{}

message_queues ={}#文件句柄到所对应对象的字典,格式为{句柄:对象}

fd_to_socket ={serversocket.fileno():serversocket,}whileTrue:print "等待活动连接......"

#轮询注册的事件集合,返回值为[(文件句柄,对应的事件),(...),....]

events =epoll.poll(timeout)if notevents:print "epoll超时无活动连接,重新轮询......"

continue

print "有" , len(events), "个新事件,开始处理......"

for fd, event inevents:

socket=fd_to_socket[fd]#如果活动socket为当前服务器socket,表示有新连接

if socket ==serversocket:

connection, address=serversocket.accept()print "新连接:", address#新连接socket设置为非阻塞

connection.setblocking(False)#注册新连接fd到待读事件集合

epoll.register(connection.fileno(), select.EPOLLIN)#把新连接的文件句柄以及对象保存到字典

fd_to_socket[connection.fileno()] =connection#以新连接的对象为键值,值存储在队列中,保存每个连接的信息

message_queues[connection] =Queue.Queue()#关闭事件

elif event &select.EPOLLHUP:print 'client close'

#在epoll中注销客户端的文件句柄

epoll.unregister(fd)#关闭客户端的文件句柄

fd_to_socket[fd].close()#在字典中删除与已关闭客户端相关的信息

delfd_to_socket[fd]#可读事件

elif event &select.EPOLLIN:#接收数据

data = socket.recv(1024)ifdata:print "收到数据:" , data , "客户端:", socket.getpeername()#将数据放入对应客户端的字典

message_queues[socket].put(data)#修改读取到消息的连接到等待写事件集合(即对应客户端收到消息后,再将其fd修改并加入写事件集合)

epoll.modify(fd, select.EPOLLOUT)#可写事件

elif event &select.EPOLLOUT:try:#从字典中获取对应客户端的信息

msg =message_queues[socket].get_nowait()exceptQueue.Empty:print socket.getpeername() , "queue empty"

#修改文件句柄为读事件

epoll.modify(fd, select.EPOLLIN)else:print "发送数据:" , data , "客户端:", socket.getpeername()#发送数据

socket.send(msg)#在epoll中注销服务端文件句柄

epoll.unregister(serversocket.fileno())#关闭epoll

epoll.close()#关闭服务器socket

serversocket.close()

服务端代码

#!/usr/bin/env python#-*- coding:utf-8 -*-

importsocket#创建客户端socket对象

clientsocket =socket.socket(socket.AF_INET,socket.SOCK_STREAM)#服务端IP地址和端口号元组

server_address = ('127.0.0.1',8888)#客户端连接指定的IP地址和端口号

clientsocket.connect(server_address)whileTrue:#输入数据

data = raw_input('please input:')#客户端发送数据

clientsocket.sendall(data)#客户端接收数据

server_data = clientsocket.recv(1024)print '客户端收到的数据:'server_data#关闭客户端socket

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

python epoll多路复用技术_python网络编程——IO多路复用之epoll相关推荐

  1. unet网络python代码详解_python网络编程详解

    最近在看<UNIX网络编程 卷1>和<FREEBSD操作系统设计与实现>这两本书,我重点关注了TCP协议相关的内容,结合自己后台开发的经验,写下这篇文章,一方面是为了帮助有需要 ...

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

    什么是epoll epoll是什么?在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll.当然,这不是2.6内核才有的,它 ...

  3. L6网络编程--IO多路复用(day6)

    目录 一.I/O模型 二.阻塞I/O模式 : 1.读阻塞 2.写阻塞 三.非阻塞模式I/O 1.非阻塞模式的实现 fcntl()函数 四.多路复用I/O 基本常识: 多路复用服务器模型:​编辑 sel ...

  4. Linux网络编程(IO多路复用)

    网络编程 1 操作系统 1.1 用户空间与内核空间 1.2 进程切换 1.3 进程的阻塞 1.4 文件描述符fd 2 IO多路复用 2.1 阻塞IO 2.1.1 多线程/多进程 2.1.2 线程池 2 ...

  5. 网络编程—IO多路复用详解

    假如你想了解IO多路复用,那本文或许可以帮助你 本文的最大目的就是想要把select.epoll在执行过程中干了什么叙述出来,所以具体的代码不会涉及,毕竟不同语言的接口有所区别. 基础知识 IO多路复 ...

  6. python socket tcp客户端_python网络编程socketserver模块(实现TCP客户端/服务器)

    摘录python核心编程 socketserver(python3.x版本重新命名)是标准库中的网络编程的高级模块.通过将创建网络客户端和服务器所必须的代码封装起来,简化了模板,为你提供了各种各样的类 ...

  7. python socket 主动断开_Python网络编程tcp详解(基础篇十四)

    网络编程tcp 1 TCP详解 <1> tcp概述 TCP:英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的.可靠的.基于字节流的 ...

  8. Linux平台基于poll实现网络编程IO多路复用

    1.概念介绍 IO多路复用模型,也称为事件驱动IO模型,它的原理为通过一个函数(如poll)不断地轮询所负责的所有socket,当某个socket有数据到达时,就通知用户进程. #include< ...

  9. python异步多线程框架_Python网络编程中的服务器架构(负载均衡、单线程、多线程和同步、异步等)。...

    这篇文章主要介绍服务器架构. 网络服务需要面对两个挑战.第一个问题是核心挑战,要编写出能够正确处理请求并构造合适响应的代码. 第二个挑战是如何将网络代码部署到随系统自动启动的Windows服务或者是U ...

最新文章

  1. ai为什么要栅格化_三大优势告诉你,为什么一定要加盟AI定制家居
  2. 嵌入式系统自动使能alias
  3. HADOOP常见错误
  4. SAP-ABAP三种定义嵌套型结构的方法
  5. PWA 可用性检测工具
  6. android标题栏消失,安卓标题栏为什么没有显示
  7. html改元素怎么保存,是否可以在NW.js中保存html元素更改?
  8. 如把联想电脑计算机图标放在桌面上,thinkpad电脑图标没了怎么恢复
  9. Java虚拟机(二)—主流Java虚拟机分类及发展历程
  10. 力扣——罗马数字转整数
  11. Codeforces Round #413(Div. 1 + Div. 2, combined)——ABCD
  12. java 判断double是否为整数_java 中如何判断输入的是int还是double
  13. github java开源项目经验_GitHub 上最火的开源项目 —— Java 篇
  14. 改写TCPMP的界面
  15. 边缘计算(二)边缘计算与智能制造
  16. 【模电】0007 有源滤波器2(二阶有源滤低通波器)
  17. 关于起点中文网的一个我自认为是BUG的BUG(花了我一毛三分钱才实验出来的)...
  18. SV基础知识---功能覆盖率1 (概念理解)
  19. LincSNP:lncRNA相关SNP位点数据库
  20. fgets()函数的详解-使用技巧-C语言基础

热门文章

  1. MySQL事务学习总结
  2. 理解Android编译命令
  3. Anroid camera + mediacodec
  4. Wpf之无法添加wpf窗体
  5. VALSE学习(八):矿视-轻量级深度模型的研究与实践
  6. 如何让Vue在同一局域网内能访问?
  7. 阿里云服务器ECS和腾讯云服务器如何安装宝塔面板?
  8. python中库是什么意思_python库是什么意思
  9. vue上传图片文件到服务器,vue如何将quill图片上传到服务器
  10. 余弦距离和欧氏距离,知道原理和公式后真的很简单