剖析epool
【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的更多相关文章
- 探索C#之6.0语法糖剖析
阅读目录: 自动属性默认初始化 自动只读属性默认初始化 表达式为主体的函数 表达式为主体的属性(赋值) 静态类导入 Null条件运算符 字符串格式化 索引初始化 异常过滤器when catch和fin ...
- jQuery之Deferred源码剖析
一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...
- [C#] 剖析 AssemblyInfo.cs - 了解常用的特性 Attribute
剖析 AssemblyInfo.cs - 了解常用的特性 Attribute [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5944391.html 序 ...
- Membership三步曲之进阶篇 - 深入剖析Provider Model
Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...
- 《AngularJS深度剖析与最佳实践》简介
由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...
- 探索c#之Async、Await剖析
阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...
- ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程
从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...
- [C#] 走进异步编程的世界 - 剖析异步方法(上)
走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...
- [C#] 走进异步编程的世界 - 剖析异步方法(下)
走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...
随机推荐
- 实例详解Django的 select_related
在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...
- hdu-2688 Rotate---树状数组+模拟
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2688 题目大意: 给你n数,(n<=3e6),有两个操作,Q为 当前有多少对数,满足严格递增, ...
- Prim算法求权数和,POJ(1258)
题目链接:http://poj.org/problem?id=1258 解题报告: #include <iostream> #include <stdio.h> #includ ...
- Math.random()随机生成x~y间的数字
JS如何随机产生数字呢?这就用到了Math.random()方法,它能够随机产生0~1间的数字,这个数可能为0,但会小于1. 那么,如果我想要大于等于1小于10之间的随机整数呢?需要分为以下几步: 1 ...
- 2017.9.22 HTML学习总结--JavaScript脚本语言
接上: 1.JavaScript脚本语言 定义:javascript是一种简单的脚本语言,可以在浏览器中直接运行, 是一种在浏览器端实现网页与客户交互的技术javascript代码可 以直接运行在ht ...
- Git配置和常用命令
Git配置 git config --global user.name "hunng" git config --global user.email "huangthin ...
- 内置函数SQLCODE和SQLERRM的使用
由于ORACLE的错信息最大长度是512字节,为了得到完整的错误提示信息,我们可用 SQLERRM 和 SUBSTR 函数一起得到错误提示信息,方便进行错误,特别是如果WHEN OTHERS异常处理器 ...
- Python容器--list, tuple, dict, set
## Python 中有四种用于存放数据的序列--list, tuple, dict, set ## list 列表 - 可以存放任意类型数据的有序序列 - 列表可以由零个或多个元素组成,元素之间用逗 ...
- php扩展开发-全局变量
//php_myext.hZEND_BEGIN_MODULE_GLOBALS(myext) unsigned long counter;//在这里定义需要的全局变量,可以多个,每个变量一行, ZEND ...
- python之微信自动发送消息
代码如下: from __future__ import unicode_literals from threading import Timer from wxpy import * import ...