Напомню, что этот сервер не для продакшена, а для тестов и изучения.
Сервер получает на вход арифметические выражения и возвращает результат вычисления.
Внимание, в боевых проектах не используйте eval
.
Плохие люди могу передать вашему скрипту строку
типа
"import os;os.system('sudo rm -rf /')"
#!/usr/bin/python
import os
import errno
import signal
import socket
import logging
import time
logger = logging.getLogger('main')
BIND_ADDRESS = ('localhost', 8999)
BACKLOG = 5
def collect_zombie_children(signum, frame):
while True:
try:
# не блокирующий waitpid (осторожно)
pid, status = os.waitpid(-1, os.WNOHANG)
logger.info('Vanish children pid=%d status=%d' % (pid, status))
if pid == 0: # больше зомбей нет
break
except ChildProcessError as e:
if e.errno == errno.ECHILD:
# всё, больше потомков нет
break
raise
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)
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
pid = os.fork()
if pid == 0:
# этот код выполняется в потомке
# потомку не нужнен слушающий сокет; сразу закрываем
sock.close()
# обрабатывает входщий запрос и формируем ответ
handle(connection, client_ip, clinet_port)
# выходим из цикла и завершаем работу потока
break
# этот код выполняется в родителе
# родителю не нужно входщее соединенеие, он его закрывает
connection.close()
def main():
# настраиваем логгинг
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] [%(process)s] %(message)s',
'%H:%M:%S'
)
ch.setFormatter(formatter)
logger.addHandler(ch)
logger.info('Run')
# подключаем удлятор зомбей
signal.signal(signal.SIGCHLD, collect_zombie_children)
# запускаем сервер
serve_forever()
main()
Комментировать тут особо нечего. При получении каждого очередного запроса, порождается новый процесс, который обрабатывает запрос и выдаёт ответ.
После этого дочерний процесс завершается. То есть процесс используется для формирования только одного ответа, что, естественно, не самая выгодная стратегия.
Вот примерный вид протокола работы этого сервера:
13:22:13 [INFO] [1132] Run
13:22:13 [INFO] [1132] Listning no localhost:8999...
13:22:17 [INFO] [1135] Start to process request from 127.0.0.1:53803
13:22:17 [INFO] [1135] In buffer = b'2+3\n'
13:22:18 [INFO] [1138] Start to process request from 127.0.0.1:53804
13:22:18 [INFO] [1138] In buffer = b'800 * 999\n'
13:22:22 [INFO] [1135] Out buffer = b'5\r\n'
13:22:22 [INFO] [1135] Done.
13:22:22 [INFO] [1132] Vanish children pid=1135 status=0
13:22:22 [INFO] [1132] Vanish children pid=0 status=0
13:22:23 [INFO] [1138] Out buffer = b'799200\r\n'
13:22:23 [INFO] [1138] Done.
13:22:23 [INFO] [1132] Vanish children pid=1138 status=0
Как видите, на каждый запрос порождается новый потомок, и два запроса обрабатываются одновременно.