编写一个简单的web服务器,向每一个连接服务器的网页浏览器返回一行文本。
脚本核心在web服务器的初始化过程中调用select.epoll(),注册服务器的文件描述符,已达到事件通知的目的。
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import socket 5 import select 6 import argparse 7 8 SERVER_HOST = 'localhost' 9 10 EOL1 = b'\n\n' 11 EOL2 = b'\n\r\n' 12 SERVER_RESPONSE = b"""HTTP/1.1 200 OK\r\nDate:Mon, 1 Apr 2013 01:01:01 GMT\r\nContent-Type:text/plain\r\nContent-Length: 25\r\n\r\nHello from Epoll Server!""" 13 14 class EpollServer(object): 15 """ a socket server using epoll""" 16 def __init__(self, host=SERVER_HOST, port=0): 17 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 #创建套接字 19 self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 20 #设置当前套接字选项为可重用 21 self.sock.bind((host, port))#绑定 22 self.sock.listen(1)#监听 23 self.sock.setblocking(0)#设置套接字模式为非阻塞 24 self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 25 #socket阻塞模式自动开启Nagle算法。设置套接字选项关闭。Nagle算法用于对缓冲区内的一定数量的消息进行自动连接。 26 print "Started Epoll Server" 27 self.epoll = select.epoll()#创建epoll对象 28 self.epoll.register(self.sock.fileno(), select.EPOLLIN) 29 #为该socket的read event注册interest 30 #EPOLLIN表示对应的文件描述符可以读,包括对端SOCKET正常关闭 31 #fileno()返回的是该socket的一个整型文件描述符 32 33 def run(self): 34 """Executes epoll server operation""" 35 try: 36 connections = {} #连接对象与socket对象的映射 37 requests = {} 38 responses = {} 39 while True: 40 events = self.epoll.poll(1) 41 #查询epoll对象是否可能发生任何interest的事件。1等待一秒 42 for fileno, event in events:#遍历事件 43 #如果事件发生在服务器 44 if fileno == self.sock.fileno(): 45 connection, address = self.sock.accept()#接收客户端socket和地址 46 connection.setblocking(0)#设置非阻塞模式 47 self.epoll.register(connection.fileno(), select.EPOLLIN) 48 #为新的socket的read event注册兴趣 49 connections[connection.fileno()] = connection#添加到connections 50 requests[connection.fileno()] = b'' 51 responses[connection.fileno()] = SERVER_RESPONSE#要发送的内容 52 53 #如果一个读事件发生在客户端,那么读取从客户端发来的新数据 54 elif event & select.EPOLLIN: 55 requests[fileno] += connections[fileno].recv(1024) 56 if EOL1 in requests[fileno] or EOL2 in requests[fileno]: 57 self.epoll.modify(fileno, select.EPOLLOUT) 58 #注销对read event的interest,注册对write event的interest 59 print('-'*40 + '\n' + requests[fileno].decode()[:-2]) 60 #输出完整的请求,去除最后一个\r\n 61 62 #如果一个写事件发生在客户端,那么可能要接受来自客户端的新数据 63 elif event & select.EPOLLOUT: 64 #EPOLLOUT表示对应的文件描述符可以写 65 byteswritten = connections[fileno].send(responses[fileno]) 66 responses[fileno] = responses[fileno][byteswritten:] 67 if len(responses[fileno]) == 0:#如果无响应 68 self.epoll.modify(fileno, 0)#禁用interest 69 connections[fileno].shutdown(socket.SHUT_RDWR) 70 #将对应的socket连接关闭 71 72 #如果一个中止事件发生在客户端 73 elif event & select.EPOLLHUP: 74 #EPOLLHUP表示对应的文件描述符被挂断 75 self.epoll.unregister(fileno)#注销客户端interest 76 connections[fileno].close()#关闭socket连接 77 del connections[fileno]#删除映射 78 finally: 79 self.epoll.unregister(self.sock.fileno())#注销服务器interest 80 self.epoll.close()#关闭服务器epoll 81 self.sock.close()#关闭服务器socket 82 83 if __name__ == '__main__': 84 parser = argparse.ArgumentParser(description='Socket Server Example with Epoll') 85 parser.add_argument('--port', action="store", dest="port", type=int, required=True) 86 given_args = parser.parse_args() 87 port = given_args.port 88 server = EpollServer(host=SERVER_HOST, port=port) 89 server.run()
参考文献: 《Python Network Programming Cookbook》
http://scotdoyle.com/python-epoll-howto.html#source-code