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

    xiaoxiao2021-03-25  108

    多路复用有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!

    转载请注明原文地址: https://ju.6miu.com/read-25630.html

    最新回复(0)