编写一个简单的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注册interest30 #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#添加到connections50 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的interest59 print('-'*40 + '\n' + requests[fileno].decode()[:-2])60 #输出完整的请求,去除最后一个\r\n61 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)#禁用interest69 connections[fileno].shutdown(socket.SHUT_RDWR)70 #将对应的socket连接关闭71 72 #如果一个中止事件发生在客户端73 elif event & select.EPOLLHUP:74 #EPOLLHUP表示对应的文件描述符被挂断75 self.epoll.unregister(fileno)#注销客户端interest76 connections[fileno].close()#关闭socket连接77 del connections[fileno]#删除映射78 finally:79 self.epoll.unregister(self.sock.fileno())#注销服务器interest80 self.epoll.close()#关闭服务器epoll81 self.sock.close()#关闭服务器socket82 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.port88 server = EpollServer(host=SERVER_HOST, port=port)89 server.run()
参考文献: 《Python Network Programming Cookbook》
http://scotdoyle.com/python-epoll-howto.html#source-code