4

基于epoll实现的I/O复用HTTP服务器

 2 years ago
source link: https://allenwind.github.io/blog/2622/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Mr.Feng Blog

NLP、深度学习、机器学习、Python、Go

基于epoll实现的I/O复用HTTP服务器

I/O复用HTTP服务器

在这里总结几个实用的、起启发作用的网络编程实例。这些实例可能并不局限特定编程语言且不按难度排序。该文章会持续更新。目前包括的实例:

  • 快速文件服务
  • 使用epoll实现的I/O复用简易HTTP服务器

快速创建文件服务

快速创建一个文件服务,方便设备间共享文件。

  • Golang版本
package main

import (
"net/http"
)

func main() {
h := http.FileServer(http.Dir("~"))
http.ListenAndServe(":8080", h)
}
  • Python版本

直接在命令行上写入

$python3 -m http.server 8080

对于Python2

python -m SocketServer 8080

如果非要写代码实现也不难。

import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingTCPServer

class ThreadingHTTPServer(ThreadingTCPServer, HTTPServer):
pass

if __name__ == '__main__':
httpd = ThreadingHTTPServer(('', 8080), BaseHTTPRequestHandler)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
sys.exit(0)

如果你喜欢,可以把代码中的ThreadingTCPServer改为ThreadingMixIn。根据socketserver源码不难理解,这也更符合编程规范。

使用epoll实现的I/O复用简易HTTP服务器

由于Windows并没有epoll,所以该代码只能在Linux OS下运行。该代码示例用于理解I/O多路复用编程。关于epoll参考Linux I/O模型

import time
import socket
import select


default = True

if default:
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 2017 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
response += b'Hello, world!'

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) #TCP_NODELAY
server.bind(('', 8080))
server.listen(1)
server.setblocking(0)

epoll = select.epoll()
epoll.register(server.fileno(), select.EPOLLIN) #select.EPOLLIN | select.EPOLLET

try:
connections = {} #{fd: sock}
requests = {} #{fd: requests}
responses = {} #{fd: resposne}

while True:
events = epoll.poll(timeout=10) #[(fd, event), ...]
for fileno, event in events:
if fileno == server.fileno():
connection, address = server.accept()
connection.setblocking(0)

epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response

elif event & select.POLLIN:
requests[fileno] += connections[fileno].recv(1024)
if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT) #修改监听文件描述符的方式,修改文件描述符的事件
print('-'*40, requests[fileno].decode()[:-2], sep='\n')

elif event & select.EPOLLOUT:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]

if len(responses[fileno]) == 0:
epoll.modify(fileno, 0) #
connections[fileno].shutdown(socket.SHUT_RDWR)

elif event & select.EPOLLHUP:
#Hang up happened on the assoc. fd
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]

finally:
epoll.unregister(server.fileno())
epoll.close()
server.close()

select模块中poll的用法类似,但底层原理是不一样的。

转载请包括本文地址:https://allenwind.github.io/blog/2622
更多文章请参考:https://allenwind.github.io/blog/archives/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK