☰ Оглавление

Простейший многонитевой сервер: одна нить на каждый запрос

Напомню, что этот сервер не для продакшена, а для тестов и изучения.

И снова напомню никогда не используйте eval!

Этот сервер, так же как и форкающийся получает математические выражения и возвращает результат вычислений.

Для каждого запроса создаётся отдельная нить и эти нити никак не взаимодействуют.

#!/usr/bin/python


import errno
import threading
import socket
import logging
import time


logger = logging.getLogger('main')


BIND_ADDRESS = ('localhost', 8999)
BACKLOG = 5


def handle(sock, clinet_ip, client_port):
    # обработчик, работающий в процессе-потомке
    logger.info('Start to process request from %s:%d' % (clinet_ip, client_port))
    # получаем все данные до перевода строки
    # (это не очень честный подход, может сожрать сразу несколько строк,
    # если они придут одним какетом; но для наших целей -- сойдёт)
    in_buffer = b''
    while not in_buffer.endswith(b'\n'):
        in_buffer += sock.recv(1024)
    logger.info('In buffer = ' + repr(in_buffer))
    # изображаем долгую обработку
    time.sleep(5)
    # получаем результат
    try:
        result = str(eval(in_buffer, {}, {}))
    except Exception as e:
        result = repr(e)
    out_buffer = result.encode('utf-8') + b'\r\n'
    logger.info('Out buffer = ' + repr(out_buffer))
    # отправляем
    sock.sendall(out_buffer)
    # в отличии от fork-варианта, здесь процесс не завершается
    # автоматического закрытия сокета не произойдёт;
    # поэтому закрываем сокет руками
    sock.close()
    logger.info('Done.')


def serve_forever():
    # создаём слушающий сокет
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # re-use port
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(BIND_ADDRESS)
    sock.listen(BACKLOG)
    # слушаем и при получении нового входящего соединения,
    # порождаем нить, которая будет его обрабатывать
    logger.info('Listning no %s:%d...' % BIND_ADDRESS)
    while True:
        try:
            connection, (client_ip, clinet_port) = sock.accept()
        except IOError as e:
            if e.errno == errno.EINTR:
                continue
            raise
        # запускаем нить
        thread = threading.Thread(
            target=handle,
            args=(connection, client_ip, clinet_port)
        )
        thread.daemon = True
        thread.start()


def main():
    # настраиваем логгинг
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
        '%(asctime)s [%(levelname)s] [%(thread)s] %(message)s',
        '%H:%M:%S'
    )
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    logger.info('Run')
    # запускаем сервер
    serve_forever()


main()

Протокол работы может выглядеть как-то так:

13:40:50 [INFO] [140274152228608] Run
13:40:50 [INFO] [140274152228608] Listning no localhost:8999...
13:40:55 [INFO] [140274112243456] Start to process request from 127.0.0.1:53965
13:40:55 [INFO] [140274112243456] In buffer = b'2+3\n'
13:40:56 [INFO] [140274034538240] Start to process request from 127.0.0.1:53966
13:40:56 [INFO] [140274034538240] In buffer = b'800 * 999\n'
13:41:00 [INFO] [140274112243456] Out buffer = b'5\r\n'
13:41:00 [INFO] [140274112243456] Done.
13:41:01 [INFO] [140274034538240] Out buffer = b'799200\r\n'
13:41:01 [INFO] [140274034538240] Done.

Как видите, две нити работают параллельно и одновременно.