select.epoll多路复用web服务器---sock.fileno;select.EPOLLIN;select.EPOLLOUT;

多路复用有select,poll为啥还要epoll呢?文章http://blog.csdn.net/songfreeman/article/details/51179213给出了详细讲解,这里不再赘述。我通过python pdb调试了《Python网络编程攻略》第34页的select.epoll多路复用web服务器程序后,有了较为粗浅的认识,哈哈!与大家分享!

select.epoll大概是一个能够监测在其上面注册过所有socket对象的东西,当socket的状态发生改变时能够及时的对其进行处理,从而实现多路复用。

select.epoll流程图

服务器代码

'''
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)设置断点,进入关键部分。
20170309-1.JPG

创建服务器socket–self.sock,如下图。
20170309-2.JPG

创建服务器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是空呢[]?
20170309-3.JPG

这是因为没有相应的网页请求,好就加一个吧,注意输入localhost:8800
20170309-4.JPG

回来继续调试:
20170309-5.JPG

哦耶,来了,我们看到events=[(3,1)],其中3是服务器socket的句柄fd,1是select.EPOLLIN的值,这说明服务器接收到了服务请求。来看看它怎么处理呢?

20170309-6.JPG

我们看到程序进入了if fileno == self.sock.fileno():这个if条件是要判断监测到的句柄fd是不是服务器socket,如果是则按照相应的程序进行处理,处理的方法是创建新的socket对象connection处理请求,并将其在epoll中注册,设置为EPOLLIN,即接收信息,对其进行监控处理。以下是新建的connection的信息,它的句柄fd=5:

20170309-7.JPG

20170309-8.JPG
对新建的connection的请求内容requests和返回内容进行指定:
20170309-9.JPG

20170309-10.JPG

继续运行,再运行一次events = self.epoll.poll(1)后,events的值变成了[(5,1)],这是因为epoll监测到了我们之间注册的connection。
20170309-11.JPG

ok,继续运行,由于event=1,故进入接受信息的处理过程。
20170309-12.JPG

接收信息的过程是:首先,将收到的信息存入requests中,然后查看该信息是否来自浏览器,方法是执行if EOL1 in requests[fileno] or EOL2 in requests[fileno]:如果是浏览器发来的请求信息,则准备反馈消息给浏览器,方法是它在epoll中的标志位改为select.EPOLLOUT
20170309-13.JPG

经过再一次循环执行events = self.epoll.poll(1),我们发现events的值变成了[(5,4)],这是由于在上面将connection在epoll中的标志位改为select.EPOLLOUT的缘故。

20170309-14.JPG

继续调试,程序会进入发消息阶段,发送消息后将对应的response重置为空。然后将connection的EPOLL标志位置为0。
20170309-17.JPG

将会在浏览器中看到:20170309-18.JPG

此时,一定要果断关掉浏览器,否则它将再一次向epoll服务器发请求,你将再次看到重复上面的内容。再一次运行events = self.epoll.poll(1)将得到events的值变成了[(5,16)]
20170309-19.JPG

程序将运行到以下位置,将connection从epoll中解除注册,并关闭。
20170309-20.JPG

我们将看到events重新变成了空:
20170309-21.JPG

ok,good night!

发布了34 篇原创文章 · 获赞 12 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/zhou8201/article/details/61214790