I/O复用HTTP服务器
在这里总结几个实用的、起启发作用的网络编程实例。这些实例可能并不局限特定编程语言且不按难度排序。该文章会持续更新。目前包括的实例:
- 快速文件服务
- 使用epoll实现的I/O复用简易HTTP服务器
快速创建文件服务
快速创建一个文件服务,方便设备间共享文件。
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) }
|
直接在命令行上写入
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) server.bind(('', 8080)) server.listen(1) server.setblocking(0)
epoll = select.epoll() epoll.register(server.fileno(), select.EPOLLIN)
try: connections = {} requests = {} responses = {}
while True: events = epoll.poll(timeout=10) 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: 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/