編寫一個簡單的web服務器,向每個鏈接服務器的網頁瀏覽器返回一行文本。html
腳本核心在web服務器的初始化過程當中調用select.epoll(),註冊服務器的文件描述符,已達到事件通知的目的。python
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》web
http://scotdoyle.com/python-epoll-howto.html#source-code算法