Created
May 31, 2016 10:16
-
-
Save limboinf/e850776b4e0f497074ee4cdfd94d1b46 to your computer and use it in GitHub Desktop.
Python implements a http server by epoll(version 2.1, epoll, edge-triggered)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding=utf-8 | |
""" | |
:copyright: (c) 2016 by fangpeng(@beginman.cn). | |
:license: MIT, see LICENSE for more details. | |
""" | |
import socket | |
import select | |
EOL1 = b'\n\n' | |
EOL2 = b'\n\r\n' | |
response = b'HTTP/1.1 200 OK\r\nDate: Mon,1 Jan 1996 01:01:01 GMT\r\n' | |
response += b'Content-Type: text/plain\r\nContent-Length:13\r\n\r\n' | |
response += b'Hello, world!' | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
# 因为socket默认是阻塞的, 我们需要设置成非阻塞(异步)模式. | |
sock.setblocking(0) | |
sock.bind(('0.0.0.0', 8000)) | |
sock.listen(1) | |
# 建立一个epoll对象 | |
epoll = select.epoll() | |
# 注册服务器socket, 监听读取事件. 服务器socket接收一个连接的时候, 产生一个读取事件. | |
# select.EPOLLET use edge-triggered | |
epoll.register(sock.fileno(), select.EPOLLIN|select.EPOLLET) | |
try: | |
# connections表映射文件描述符(file descriptors, 整型)到对应的网络连接对象上面. | |
connections = {} | |
requests = {} | |
responses = {} | |
while 1: | |
# epoll对象查询一下是否有感兴趣的事件发生, | |
# 参数1说明我们最多等待1秒的时间. 如果有对应事件发生, 立刻会返回一个事件列表. | |
events = epoll.poll(1) | |
for fileno, event in events: | |
# 如果是服务器socket的事件, 那么需要针对新的连接建立一个socket. | |
if fileno == sock.fileno(): | |
try: | |
while 1: | |
conn, addr = sock.accept() | |
conn.setblocking(0) # 设置socket为非阻塞模式. | |
# 注册socket的read(EPOLLIN)事件.并设置为edge-triggered模式 | |
epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET) | |
connections[conn.fileno()] = conn | |
requests[conn.fileno()] = b'' | |
responses[conn.fileno()] = response | |
except socket.error: | |
pass | |
# 如果读取事件发生, 从客户端读取新数据. | |
elif event & select.EPOLLIN: | |
try: | |
while 1: | |
requests[fileno] += connections[fileno].recv(1024) | |
except socket.error: | |
pass | |
if EOL1 in requests[fileno] or EOL2 in requests[fileno]: | |
# 一旦完整的http请求接收到, 取消注册读取事件, 注册写入事件(EPOLLOUT) | |
# 写入事件在 发送数据回客户端 的时候产生. | |
epoll.modify(fileno, select.EPOLLOUT | select.EPOLLET) | |
# 打印完整的http请求, 展示即使通讯是交错的, 数据本身是作为一个完整的信息组合和处理的. | |
print('-'*40 + '\n' + requests[fileno].decode()[:-2]) | |
# 如果写入事件发生在一个客户端socket上面, 我们就可以发送新数据到客户端了. | |
elif event & select.EPOLLOUT: | |
try: | |
while len(responses[fileno]) > 0: | |
# 一次发送一部分返回数据, 直到所有数据都交给操作系统的发送队列. | |
byteswritten = connections[fileno].send(responses[fileno]) | |
responses[fileno] = responses[fileno][byteswritten:] | |
except socket.error: | |
pass | |
# 一旦所有的返回数据都发送完, 取消监听读取和写入事件.只保留EPOLLET | |
if len(responses[fileno]) == 0: | |
epoll.modify(fileno, select.EPOLLET) | |
# 如果连接被明确关闭掉, 这一步是可选的. | |
# 这个例子采用这个方法是为了让客户端首先断开, 告诉客户端没有数据需要发送和接收了, 然后让客户端断开连接. | |
connections[fileno].shutdown(socket.SHUT_RDWR) | |
# HUP(hang-up)事件表示客户端断开了连接(比如 closed), 所以服务器这端也会断开. | |
# 不需要注册HUP事件, 因为它们都会标示到注册在epoll的socket. | |
elif event & select.EPOLLHUG: | |
epoll.unregister(fileno) # 取消注册. | |
connections[fileno].close() # 断开连接 | |
del connections[fileno] | |
finally: | |
# try...finally | |
# 在这里的异常捕捉的作用是, 我们的例子总是采用键盘中断来停止程序执行. | |
# 虽然开启的socket不需要手动关闭, 程序退出的时候会自动关闭, 明确写出来这样的代码, 是更好的编码风格. | |
epoll.unregister(sock.fileno()) | |
epoll.close() | |
sock.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment