多路复用有select,poll为啥还要epoll呢?文章http://blog.csdn.net/songfreeman/article/details/51179213给出了详细讲解,这里不再赘述。我通过python pdb调试了《Python网络编程攻略》第34页的select.epoll多路复用web服务器程序后,有了较为粗浅的认识,哈哈!与大家分享!
select.epoll大概是一个能够监测在其上面注册过所有socket对象的东西,当socket的状态发生改变时能够及时的对其进行处理,从而实现多路复用。
服务器代码
'''
Created on 2017-3-8
@author: lenovo
'''
import socket
import select
import argparse
import pdb
SERVER_HOST='localhost'
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
SERVER_RESPONSE = b"""HTTP/1.1 200 OK\r\n Data: Mon, 1 Apr 2013 01:01:01\
GMT\r\nContent-Type: text/plain\r\nContent-Length: 25\r\n\r\n\
Hello from Epoll server!"""
class EpollServer(object):
""" A socket server using Epoll. """
def __init__(self,host=SERVER_HOST,port=0):
self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Explain the function of the above.
self.sock.bind((host,port))
self.sock.listen(1)
self.sock.setblocking(0)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# Explain the function of the above.
print "Start Epoll Server"
self.epoll = select.epoll()
self.epoll.register(self.sock.fileno(),select.EPOLLIN)
#Return the socket’s file descriptor (a small integer). This is useful with select.select().
# epoll.register(fd[, eventmask])
# Register a fd descriptor with the epoll object.
def run(self):
"""Executes epoll server operation"""
try:
connections = {}; requests = {}; responses = {}
while True:
events = self.epoll.poll(1)
# Wait for events. timeout in seconds (float)
for fileno,event in events:
if fileno == self.sock.fileno():
#events has the style like: events=[(3,1)].
#The first is the fd of socket. The second is the state of socket.
#self.sock.fileno() return the fd of self.sock,which is the server sock.
#So, blow is deal with the request for server.
connection, address = self.sock.accept()
#create a sock connection to deal with the request.
connection.setblocking(0)
#Set blocking or non-blocking mode of the socket:
#if flag is 0, the socket is set to non-blocking,
#else to blocking mode. Initially all sockets are in blocking mode.
self.epoll.register(connection.fileno(),select.EPOLLIN)
#Register a fd descriptor with the epoll object.
connections[connection.fileno()] = connection
requests[connection.fileno()]=b''
responses[connection.fileno()]=SERVER_RESPONSE
# define the connection, requests, responses.
elif event & select.EPOLLIN:
# bitwise and of x and y
# deal with the EPOLLIN socked.
requests[fileno]+=connections[fileno].recv(1024)
#socket wihch created above recive data.
if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
# the request is from the webbrowser the
# change the socket to be EPOLLOUT to rsponse back.
self.epoll.modify(fileno,select.EPOLLOUT)
#change the socket state to EPOLLOUT.
print('_'*40 + '\n' +requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
#when the socket state is EPOLLOUT, send back the response.
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno]=responses[fileno][byteswritten:]
# responses{}清空
if len(responses[fileno]) == 0:
self.epoll.modify(fileno,0)
#Modify a register file descriptor.
connections[fileno].shutdown(socket.SHUT_RDWR)
#socket.shutdown(how)
#Shut down one or both halves of the connection.
#If how is SHUT_RD, further receives are disallowed.
#If how is SHUT_WR, further sends are disallowed.
#If how is SHUT_RDWR, further sends and receives are disallowed.
elif event & select.EPOLLHUP:
self.epoll.unregister(fileno)
#epoll.unregister(fd)
#Remove a registered file descriptor from the epoll object.
connections[fileno].close()
del connections[fileno]
finally:
self.epoll.unregister(self.sock.fileno())
self.epoll.close()
self.sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Socket Server Example with Epoll')
parser.add_argument('--port',action='store',dest="port",type=int,required=True)
pdb.set_trace()
given_args = parser.parse_args()
port = given_args.port
server = EpollServer(host=SERVER_HOST,port=port)
server.run()
调试过程
发现python中的pdb还挺好用的!开始吧!
在server = EpollServer(host=SERVER_HOST,port=port)
设置断点,进入关键部分。
创建服务器socket–self.sock,如下图。
创建服务器socket后,再创建select.epoll,然后将socket在其epoll上注册:
self.epoll = select.epoll() self.epoll.register(self.sock.fileno(),select.EPOLLIN)
然后,继续,这时我们的epoll已经开始监控服务器socket了,不信继续往下看。如下图,运行了events = self.epoll.poll(1)
后,epoll就开始监控了,奇怪为什么返回的events是空呢[]?
这是因为没有相应的网页请求,好就加一个吧,注意输入localhost:8800
回来继续调试:
哦耶,来了,我们看到events=[(3,1)]
,其中3是服务器socket的句柄fd,1是select.EPOLLIN
的值,这说明服务器接收到了服务请求。来看看它怎么处理呢?
我们看到程序进入了if fileno == self.sock.fileno():
这个if条件是要判断监测到的句柄fd是不是服务器socket,如果是则按照相应的程序进行处理,处理的方法是创建新的socket对象connection处理请求,并将其在epoll中注册,设置为EPOLLIN,即接收信息,对其进行监控处理。以下是新建的connection的信息,它的句柄fd=5:
对新建的connection的请求内容requests和返回内容进行指定:
继续运行,再运行一次events = self.epoll.poll(1)
后,events的值变成了[(5,1)]
,这是因为epoll监测到了我们之间注册的connection。
ok,继续运行,由于event=1,故进入接受信息的处理过程。
接收信息的过程是:首先,将收到的信息存入requests中,然后查看该信息是否来自浏览器,方法是执行if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
,如果是浏览器发来的请求信息,则准备反馈消息给浏览器,方法是它在epoll中的标志位改为select.EPOLLOUT。
经过再一次循环执行events = self.epoll.poll(1)
,我们发现events的值变成了[(5,4)]
,这是由于在上面将connection在epoll中的标志位改为select.EPOLLOUT的缘故。
继续调试,程序会进入发消息阶段,发送消息后将对应的response重置为空。然后将connection的EPOLL标志位置为0。
将会在浏览器中看到:
此时,一定要果断关掉浏览器,否则它将再一次向epoll服务器发请求,你将再次看到重复上面的内容。再一次运行events = self.epoll.poll(1)
将得到events的值变成了[(5,16)]
:
程序将运行到以下位置,将connection从epoll中解除注册,并关闭。
我们将看到events重新变成了空:
ok,good night!