【01】什么是epool:

  当互联网的用户越来越多的时候,人们发现传统的网络io模型,扛不住用户的高并发请求的时候。各个操作系统给出了自己对应的答案,

而linux给出的答案是epool。epool是系统的一个接口,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready

队列的描述符集合就行。

【02】epool 是如何工作的

我们通过代码一步步去深入epool 在网络io中的工作.

epool是能够让文件描述状态发生变化的时候产生通知,这个也被成为事件events

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;

我们可以把链接产生的文件描述符放到epool中,然后让epool去监听事件,然后做对应的处理。

events 其实还包含两种信号

  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  这是说epoll事件产生触发机制。

----------------------------------------------------------------------------------------------------------------------------------------------------

[1] 注册文件描述符

  这里我们把文件描述符号分成两种:

    1 socket对象的文件描述符号

      这个文件描述符只有一个,就是socket本身,他的状态通常在 listen accept 之间转换。 当有链接过来的时候listen变成 accept ,然后又变成listen等待下一链接

    2链接文件描述符(这种说法并不完全准确):他是每当有一个新的链接过l来的时候,会新建一个文件描述符号,(一般从3号开始累加,012---标准输入输入系类,3--->socket)在代码中,它的状态

    是有accept read write process close 这五种 通常链接是从 accpet -->read -->write -->process-->close 这种变化的,但是根据实际情况会有不同,长链接从process之后会继续read,而当产生问题时就直接close

[2] 通过状态机执行状态处理:

  我们在代码中,处理问题的方式,如过要跟据上一次状态做出决定,那么你可以写个状态机,这里我们代码中,通过状态机,就是根据程序状态字当前状态,决定该如何处理。

严格来说我们的代码就做这两件事

#!/usr/bin/env python
# coding: utf-8 from daemon import Daemon
import socket
import select
import time
import pdb __all__ = ["nbNet", "sendData_mh"]
#DEBUG = True from nbNetUtils import * class nbNetBase:
'''
基本类,定义了单一功能的多个方法 ''' def setFd(self, sock):
"""sock is class object of socket"""
#dbgPrint("\n -- setFd start!")
tmp_state = STATE()
tmp_state.sock_obj = sock
self.conn_state[sock.fileno()] = tmp_state
#self.conn_state[sock.fileno()].printState()
#dbgPrint("\n -- setFd end!") def accept(self, fd):
"""fd is fileno() of socket"""
print "FD ",fd,"-------- aceept and setblocking",'state:',self.conn_state[fd].state
#dbgPrint("\n -- accept start!")
'''
获取conn_state中存的socket对象,并且设置accept,等待客户端链接
'''
sock_state = self.conn_state[fd]
sock = sock_state.sock_obj
conn, addr = sock.accept()
# set to non-blocking: 0
#设置为非阻塞
conn.setblocking(0)
#返回要链接的客户端
print conn.fileno(),"conn fileno"
return conn def close(self, fd):
"""fd is fileno() of socket"""
try:
# cancel of listen to event
#j获取链接对象
sock = self.conn_state[fd].sock_obj
#从epool关注中删除,防止引用计数的问题
self.epoll_sock.unregister(fd)
#关闭socket
sock.close()
#从字典中去除这个fd的记录
self.conn_state.pop(fd)
#tmp_pipe = self.popen_pipe
#self.popen_pipe = 0
#tmp_pipe.close()
except:
#dbgPrint("Close fd: %s abnormal" % fd)
pass
#@profile
def read(self, fd):
"""fd is fileno() of socket"""
#pdb.set_trace()
try:
#获取要读取的套接字字典信息
sock_state = self.conn_state[fd]
#获取socket对象
conn = sock_state.sock_obj
'''这里是对客户端数据的检测,因为协议是在数据前面加上协议头,0000000003 10个字节告诉客户端协议体有3个字节
而needread刚刚开始的时候是10然后读完协议头改成3,然后在读3个字节,如该客户端正常发送数据,是不可能为0的 '''
if sock_state.need_read <= 0:
raise socket.error
#读取一次需要need_read的大小的数据
one_read = conn.recv(sock_state.need_read)
#dbgPrint("\tread func fd: %d, one_read: %s, need_read: %d" % (fd, one_read, sock_state.need_read))
#检验数据长度
if len(one_read) == 0:
raise socket.error
# process received data
#把读取到到的数据存在buff_read中
sock_state.buff_read += one_read
#存好已经读取的长度
sock_state.have_read += len(one_read)
#需要读取的则减去这次读取的数据长度
sock_state.need_read -= len(one_read) # read protocol header
if sock_state.have_read == 10:
#当havaread == 10的时候我们可以理解为头都读完了 我们把协议头解析成数字然后将needread改成这个数字,也就是要读多少字节的协议体
header_said_need_read = int(sock_state.buff_read)
if header_said_need_read <= 0:
raise socket.error
sock_state.need_read += header_said_need_read
#清除缓冲区
sock_state.buff_read = ''
#返回已经读取协议头,继续读取协议体
return "readcontent"
elif sock_state.need_read == 0:
# recv complete, change state to process it
#当needread==0的时候意味着协议体和协议头都读取完成了,返回process
return "process"
else:
#这里的意思是要读取协议体,我们假设我们一个正常包是这样的 0000000002AB 一共十二个字节
return "readmore"
except (socket.error, ValueError), msg:
try:
if msg.errno == 11:
#dbgPrint("11 " + msg)
return "retry"
except:
pass
return 'closing' #@profile
def write(self, fd):
#获取链接数据
sock_state = self.conn_state[fd]
#获取链接对象
conn = sock_state.sock_obj
#pdb.set_trace()
print "poen_pipe",sock_state.popen_pipe,"========="
if isinstance(sock_state.popen_pipe, file):
try:
output = sock_state.popen_pipe.read()
print output
except (IOError, ValueError), msg:
pass
#have_send = conn.send("%010d%s" % (len(output), output))
#todo else:
#
last_have_send = sock_state.have_write
try:
print "last_have_send===>",last_have_send
print "nerd_write===>",sock_state.need_write # to send some Bytes, but have_send is the return num of .send()
#获取内容,发送数据
have_send = conn.send(sock_state.buff_write[last_have_send:])
#hava_write 加上 已经发送的长度
sock_state.have_write += have_send
#need_write 减去已经读取的长度
sock_state.need_write -= have_send
#如歌需要写入的长度为零和已经写入的长度不为零,我们认为写入完成了
if sock_state.need_write == 0 and sock_state.have_write != 0:
#send complete, re init status, and listen re-read
#sock_state.printState()
#dbgPrint('\n write data completed!')
return "writecomplete"
else:
return "writemore"
except socket.error, msg:
return "closing" def run(self):
'''
主流程方法
'''
while 1:
#这里会阻塞,一直到监控的文件描述符有事件产生
epoll_list = self.epoll_sock.poll()
#如果事件产生epoll_list中就会包含产生事件的fd号
for fd, events in epoll_list:
print fd,events,"#########"
#获取在setFd中定义好的字典中key为当前fd的值
sock_state = self.conn_state[fd]
#print 'EPOLLHUP:',select.EPOLLHUP,'EPOLLERR:',select.EPOLLERR,'events',events
#如果长生EPOLLHUP或者EPOLLERR 将这个文件描述在self.conn_state的状态字段设置为close
if select.EPOLLHUP & events:
#dbgPrint("EPOLLHUP") sock_state.state = "closing"
elif select.EPOLLERR & events:
#dbgPrint("EPOLLERR") sock_state.state = "closing"
print fd,"to state_machine state is ",sock_state.state
#将fd号放到状态机中
self.state_machine(fd) def state_machine(self, fd): #time.sleep(0.1)
#dbgPrint("\n - state machine: fd: %d, status: %s" % (fd, self.conn_state[fd].state))
#获取self.conn_state字典key为fd的值根据fd的状态在sm这个字典中找到对应的方法执行
sock_state = self.conn_state[fd]
print sock_state.state,fd,'sm msg'
self.sm[sock_state.state](fd) class nbNet(nbNetBase):
def __init__(self, addr, port, logic):
#dbgPrint('\n__init__: start!')
#定义一个字典,以fileno为key记录链接信息
self.conn_state = {}
'''
定义一个socket对象,监听端口,并且添加到epool事件监听里面,设置为ET模式的触发 '''
self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listen_sock.bind((addr, port))
self.listen_sock.listen(10)
self.setFd(self.listen_sock)
self.epoll_sock = select.epoll()
# LT for default, ET add ' | select.EPOLLET '
#把socket文件描述符添加到epool事件监听,监听事件为EPOLLIN,也就是表示对应的文件描述符可以读
self.epoll_sock.register(self.listen_sock.fileno(), select.EPOLLIN)
#实例化业务代码
self.logic = logic
#定义好字典,然后让状态机知道什么状态,调用什么代码
self.sm = {
"accept" : self.accept2read,
"read" : self.read2process,
"write" : self.write2read,
"process": self.process,
"closing": self.close,
}
#dbgPrint('\n__init__: end, register no: %s' % self.listen_sock.fileno() ) #@profile
def process(self, fd):
#获取链接套接字的数据
sock_state = self.conn_state[fd]
#传递给我们业务方法
response = self.logic( sock_state.buff_read)
#pdb.set_trace()
#如果在执行业务代码的时候,没有得到返回值,重新恢复这个套接字在字典里面信息
if response == None:
conn = sock_state.sock_obj
self.setFd(conn)
print "==============> modify fd ",conn.fileno()
self.conn_state[fd].state = "read"
self.epoll_sock.modify(fd, select.EPOLLIN)
else:
#获取数据并加上协议头
sock_state.buff_write = "%010d%s" % (len(response), response)
#获取数据长度并写到need_write
sock_state.need_write = len(sock_state.buff_write)
#修改状态为write
sock_state.state = "write"
#修改监控状态为EPOLLOUT
self.epoll_sock.modify(fd, select.EPOLLOUT) #@profile
def accept2read(self, fd):
#默认socketfd刚刚开始为accept状态
#获取链接对象
conn = self.accept(fd) #将链接对象的描述符添加到epool关注中,并且关注的事件是EPOLLIN
self.epoll_sock.register(conn.fileno(), select.EPOLLIN)
#在全局字典上面注册
self.setFd(conn)
#设置字典key为fd打链接描述符的状态为read 以便下次发生事件的时候,让状态机知道如何处理
self.conn_state[conn.fileno()].state = "read" #@profilenbNet
def read2process(self, fd):
"""fd is fileno() of socket"""
#pdb.set_trace()
#print fd,'go to raed'
read_ret = ""
try:
#当之前状态是read的链接文件描述符监听EPOOLIN事件产生的时候,我们需要读取客户端发
read_ret = self.read(fd)
print "read_ret ========>",read_ret '''
根据read方法返回的值决定该如何处理
'''
except (Exception), msg:
#dbgPrint(msg)
read_ret = "closing"
if read_ret == "process":
'''
当返回值为process表示一次协议包读取完成,我们可以执行业务代码
'''
self.process(fd) elif read_ret == "readcontent":
pass
elif read_ret == "readmore":
pass
elif read_ret == "retry":
pass
elif read_ret == "closing":
self.conn_state[fd].state = 'closing'
# closing directly when error.
self.state_machine(fd)
else:
raise Exception("impossible state returned by self.read") #@profile
def write2read(self, fd):
try:
#当事件发生,而且之前状态是write的时候,那么执行这个方法
#对这个fd执行write方法
write_ret = self.write(fd)
except socket.error, msg:
#产生以上 将状态设置为closing 下次关闭
write_ret = "closing" if write_ret == "writemore": pass
elif write_ret == "writecomplete":
'''
writecomplete,代表写入完毕
重新设置下链接的状态,state = read 为了支持长链接,客户端下次再发送数据可以接着读写
'''
sock_state = self.conn_state[fd]
conn = sock_state.sock_obj
self.setFd(conn)
self.conn_state[fd].state = "read"
self.epoll_sock.modify(fd, select.EPOLLIN)
elif write_ret == "closing":
#关闭
#dbgPrint(msg)
self.conn_state[fd].state = 'closing'
# closing directly when error.
self.state_machine(fd) counter = 0
if __name__ == '__main__': def logic(d_in):
print d_in
print "no return val"
return "ok"
reverseD = nbNet('0.0.0.0', 9099, logic)
reverseD.run()
#!/usr/bin/env python
# coding: utf-8 from daemon import Daemon
import socket
import select
import time
import pdb
import sys, os
import fcntl DEBUG = True
#DEBUG = False from inspect import currentframe def get_linenumber():
cf = currentframe()
return str(cf.f_back.f_back.f_lineno) def dbgPrint(msg):
if DEBUG:
print get_linenumber(), msg def nonblocking(fd):
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) import signal,functools
class TimeoutError(Exception): pass
def timeout(seconds, error_message = 'Function call timed out'):
def decorated(func):
def _handle_timeout(signum, frame):
raise TimeoutError(error_message)
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0)
return result
return functools.wraps(func)(wrapper)
return decorated @timeout(5)
def connect_timeout(socket, host_port):
return socket.connect(host_port) def sendData_mh(sock_l, host_l, data, single_host_retry = 3):
"""
saver_l = ["localhost:50001","127.0.0.1:50001"]
sock_l = [some_socket]
sendData_mh(sock_l,saver_l,"this is data to send")
"""
done = False
for host_port in host_l:
if done:
break
host,port =host_port.split(':')
port = int(port)
print "iter", host, port
print "sock_l", sock_l
retry = 0
while retry < single_host_retry:
try:
if sock_l[0] == None:
sock_l[0] = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "connecting", host, port
#connect_timeout(sock_l[0], (host, port))
sock_l[0].settimeout(5)
sock_l[0].connect((host, port))
d = data
sock_l[0].sendall("%010d%s"%(len(d), d))
print "%010d%s"%(len(d), d)
count = sock_l[0].recv(10)
if not count:
print 'count', count
raise Exception("recv error", "recv error")
count = int(count)
buf = sock_l[0].recv(count)
print buf
if buf[:2] == "OK":
retry = 0
done = True
return True
except (Exception), msg:
try:
print msg.errno
except:
pass
sock_l[0].close()
sock_l[0] = None
time.sleep(1)
retry += 1 def sendData(sock_l, host, port, data):
retry = 0
while retry < 3:
try:
if sock_l[0] == None:
sock_l[0] = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_l[0].connect((host, port))
print "connecting"
d = data
sock_l[0].sendall("%010d%s"%(len(d), d))
print "%010d%s"%(len(d), d)
count = sock_l[0].recv(10)
if not count:
raise Exception("recv error", "recv error")
count = int(count)
buf = sock_l[0].recv(count)
print buf
if buf[:2] == "OK":
retry = 0
break
except:
sock_l[0].close()
sock_l[0] = None
retry += 1 # initial status for state machine
class STATE:
def __init__(self):
self.state = "accept"
self.have_read = 0
self.need_read = 10
self.have_write = 0
self.need_write = 0
self.buff_write = ""
self.buff_read = ""
# sock_obj is a object
self.sock_obj = ""
self.popen_pipe = 0 def printState(self):
if DEBUG:
dbgPrint('\n - current state of fd: %d' % self.sock_obj.fileno())
dbgPrint(" - - state: %s" % self.state)
dbgPrint(" - - have_read: %s" % self.have_read)
dbgPrint(" - - need_read: %s" % self.need_read)
dbgPrint(" - - have_write: %s" % self.have_write)
dbgPrint(" - - need_write: %s" % self.need_write)
dbgPrint(" - - buff_write: %s" % self.buff_write)
dbgPrint(" - - buff_read: %s" % self.buff_read)
dbgPrint(" - - sock_obj: %s" % self.sock_obj)

  

剖析epool的更多相关文章

  1. 探索C#之6.0语法糖剖析

    阅读目录: 自动属性默认初始化 自动只读属性默认初始化 表达式为主体的函数 表达式为主体的属性(赋值) 静态类导入 Null条件运算符 字符串格式化 索引初始化 异常过滤器when catch和fin ...

  2. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  3. [C#] 剖析 AssemblyInfo.cs - 了解常用的特性 Attribute

    剖析 AssemblyInfo.cs - 了解常用的特性 Attribute [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5944391.html 序 ...

  4. Membership三步曲之进阶篇 - 深入剖析Provider Model

    Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...

  5. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  6. 探索c#之Async、Await剖析

    阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...

  7. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  8. [C#] 走进异步编程的世界 - 剖析异步方法(上)

    走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...

  9. [C#] 走进异步编程的世界 - 剖析异步方法(下)

    走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...

随机推荐

  1. framework7 可以拉动右侧工具栏和点击当前item就可以出发事件的HTML结构

    <li class="swipeout"> <div class="swipeout-content item-content"> &l ...

  2. Entity Framework 6事务回滚

    使用EF6你有新的事务处理可以使用类似于: 复制代码 using (var context = new PostEntityContainer()) { using (var dbcxtransact ...

  3. robotframework实战一

    1.环境 1.Robotframework 安装环境,见以下帖子,只要保证python安装成功,安装了python的pip,就可以使用pip一路安装下去了.以下帖子也有基础的操作,帖子路径 http: ...

  4. Java解析Excel工具类(兼容xls和xlsx)

    依赖jar <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml&l ...

  5. Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple Task Points

    Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple ...

  6. Python类型转换+序列操作+基本概念辨析速查手册

     第一部分是Python语言中基础中的基础,根据网上资料,合并如下:       1.类型转换     int(x [,base])  将x转换为一个整数     long(x [,base]) 将x ...

  7. django模板层(templates)

    1.基础部分 通过使用模板,就可以在URL中直接调用HTML,它还是松耦合的,灵活性强,而且更容易维护 而且可以将变量通过一定的方式嵌入到HTML中,最终渲染到页面,总的来说基于模板完成了数据与用户之 ...

  8. HTML第二章:列表,表格,媒体元素

    第二章:列表,表格,媒体元素 列表:有三种,有序列表,无序列表,定义列表 1.有序列表:<ol></ol>            列表项:<li></li&g ...

  9. Linq to Entity 时间差作为筛选条件产生的问题

    前言 在使用 Linq to Entity 的時候,會把之前 Linq to SQL 的想法就帶進去,寫好之後編譯也都不會出錯,但是實際上在跑的時候就會出現錯誤訊息了,這點真的要注意了.這次我遇到問題 ...

  10. linux 开机自启动 Tomcat

    1.修改脚本文件rc.local:vim /etc/rc.d/rc.local 这个脚本是使用者自定的开机启动程序,可以在里面添加想在系统启动之后执行的脚本或者脚本执行命令 2.添加如下内容: exp ...