Created
July 18, 2012 14:17
-
-
Save kykyev/3136452 to your computer and use it in GitHub Desktop.
New Gist All Gists Back to GitHub Simple tcp server based directly on epoll event loop (level triggered). Bare callback style.
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 -*- | |
| """Simple tcp server for educational purposes. | |
| Based directly on python interface to Linux epoll mechanism. | |
| Uses vanilla plain callback style. | |
| """ | |
| import socket | |
| import select | |
| import errno | |
| from functools import partial | |
| TERM = '\n' | |
| def init_server_socket(host='localhost', port=8080): | |
| ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| ss.setblocking(0) | |
| ss.bind(('localhost', 8080)) | |
| ss.listen(1) | |
| return ss | |
| class Stream(object): | |
| """Instances of `Stream` class represent io-state on a socket. | |
| conn: | |
| connection object | |
| req: | |
| stream of bytes to be read | |
| resp: | |
| stream of bytes to be written | |
| """ | |
| def __init__(self, conn, req='', resp=''): | |
| self.conn = conn | |
| self.req = req | |
| self.resp = resp | |
| class Server(object): | |
| """Simple tcp server. | |
| def application(request, write_response): | |
| write_response(">>> {}:{}\n".format(request, len(request))) | |
| server = Server(app=application) | |
| server.start() | |
| """ | |
| def __init__(self, app): | |
| self.server_socket = init_server_socket() | |
| self.epoll = select.epoll() | |
| self.state = {} | |
| self.app = app | |
| self.epoll.register(self.server_socket.fileno(), select.EPOLLIN) | |
| def _handle_accept(self): | |
| """ """ | |
| conn, addr = self.server_socket.accept() | |
| conn.setblocking(0) | |
| print "opening connection, fileno: {0} ".format(conn.fileno()) | |
| self.epoll.register(conn.fileno(), select.EPOLLIN) | |
| self.state[conn.fileno()] = Stream(conn) | |
| def _handle_read(self, fileno): | |
| """ """ | |
| s = self.state[fileno] | |
| s.req += s.conn.recv(1024) | |
| terminal_index = s.req.find(TERM) | |
| if terminal_index: | |
| req_ready = s.req[:terminal_index] | |
| s.req = s.req[terminal_index + 1:] | |
| self.app(req_ready, partial(self._write_response, fileno)) | |
| def _handle_write(self, fileno): | |
| """ """ | |
| s = self.state[fileno] | |
| try: | |
| bytes_written = s.conn.send(s.resp) | |
| except socket.error as e: | |
| # in case when peer has closed connection | |
| if e.errno == errno.EPIPE: | |
| self._close_connection(fileno) | |
| else: | |
| raise e | |
| else: | |
| s.resp = s.resp[bytes_written:] | |
| if not s.resp: | |
| self.epoll.modify(fileno, select.EPOLLIN) | |
| def _handle_hup(self, fileno): | |
| """ """ | |
| self._close_connection(fileno) | |
| def _loop(self): | |
| try: | |
| while True: | |
| events = self.epoll.poll(1) | |
| for fileno, evt in events: | |
| if fileno == self.server_socket.fileno(): | |
| self._handle_accept() | |
| # It important that EPOLLHUP goes before EPOLLIN. | |
| # Otherwise _handle_hup is never reached because | |
| # EPOLLHUP occures not alone but in conjunction | |
| # with EPOLLIN or EPOLLOUT. | |
| elif evt & select.EPOLLHUP: | |
| self._handle_hup(fileno) | |
| elif evt & select.EPOLLIN: | |
| self._handle_read(fileno) | |
| elif evt & select.EPOLLOUT: | |
| self._handle_write(fileno) | |
| finally: | |
| self.shutdown() | |
| def _close_connection(self, fileno): | |
| print "closing connection, fileno: {0} ".format(fileno) | |
| self.epoll.unregister(fileno) | |
| self.state[fileno].conn.close() | |
| del self.state[fileno] | |
| def _write_response(self, fileno, response): | |
| self.state[fileno].resp = response | |
| self.epoll.modify(fileno, select.EPOLLOUT) | |
| def start(self): | |
| self._loop() | |
| def shutdown(self): | |
| self.epoll.unregister(self.server_socket.fileno()) | |
| self.epoll.close() | |
| self.server_socket.close() | |
| if __name__ == "__main__": | |
| def application(request, write_response): | |
| write_response(">>> {}:{}\n".format(request, len(request))) | |
| server = Server(app=application) | |
| server.start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment