I/O复用HTTP服务器

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

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

快速创建文件服务

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

  • Golang版本
1
2
3
4
5
6
7
8
9
10
11
package main

import (
"net/http"
)

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

  • Python版本

直接在命令行上写入

1
$python3 -m http.server 8080

对于Python2

1
python -m SocketServer 8080

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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/