Skip to content

Instantly share code, notes, and snippets.

@kykyev
Created July 18, 2012 14:17
Show Gist options
  • Select an option

  • Save kykyev/3136452 to your computer and use it in GitHub Desktop.

Select an option

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.
# -*- 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