Skip to content

48. (П21) Блокуючий ехо-сервер на сокетах

Передумови

Персоналізація

У кожному файлі оголосіть константу STUDENT = "Imya Prizvysche" (ваші дані латиницею) і виводьте її у першому рядку кожного запуску програми: print(f"Student: {STUDENT}"). Окрім того, сервер надсилає це ім'я у вітальному повідомленні кожному клієнту — тому на скріншотах звіту воно має бути видно і на стороні сервера, і на стороні клієнта.

Завдання

Реалізувати блокуючий TCP ехо-сервер, який, окрім простого повторення повідомлень, розуміє кілька керуючих команд і веде статистику для кожного з'єднання.

Сервер слухає на 127.0.0.1:9100, використовує SO_REUSEADDR. Кожне повідомлення від клієнта — це один виклик recv (на практиці у nc це один рядок, бо stdin лінійно-буферизовано).

Файл echo_server.py.

При підключенні клієнта сервер веде per-connection лічильник отриманих повідомлень n (з 1) і запам'ятовує момент підключення started_at = time.monotonic().

Одразу після accept сервер надсилає клієнту вітання:

Hello from <STUDENT>! Commands: PING | TIME | WHOAMI | STATS | QUIT

Далі у циклі читайте від клієнта recv(4096). Якщо повернулось b"" — клієнт закрив з'єднання, виходимо з внутрішнього циклу. Інакше декодуйте байти через decode() і обріжте пробіли по краях — це і буде msg. Залежно від msg.upper() сервер реагує так:

Команда Відповідь
PING PONG
TIME поточний час у форматі ISO-8601 — datetime.datetime.now().isoformat(timespec="seconds")
WHOAMI <ip>:<port> клієнта (з кортежу addr, що повернув accept)
STATS messages: <n>, uptime: <s>s, де n — поточне значення лічильника, sint(time.monotonic() - started_at)
QUIT надіслати BYE і закрити з'єднання (break із внутрішнього циклу)
будь-що інше [<n>] <msg> — ехо з префіксом-номером повідомлення

Лічильник n інкрементується на кожне отримане повідомлення, незалежно від того, команда це чи звичайний текст. Вітання відправляється до першого recv і у лічильнику не враховується.

На стороні сервера на кожну подію друкуйте один рядок логу:

  • connected: <ip>:<port> — після accept;
  • <- <ip>:<port>: <msg> — отримано повідомлення;
  • -> <ip>:<port>: <reply> — надіслано відповідь;
  • disconnected: <ip>:<port> (n=<n>, uptime=<s>s) — після виходу з внутрішнього циклу.

Не забудьте:

  • setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) перед bind;
  • слухаючий сокет — у with socket.socket(...) as server:, а сокет з'єднання — with conn:, щоб гарантовано закривалися навіть на винятках;
  • зовнішній try/except KeyboardInterrupt у головному циклі — щоб після Ctrl+C надрукувати server stopped і завершитись штатно.

Запустіть сервер. Перевірте всі команди через nc 127.0.0.1 9100. Очікуваний приклад сесії клієнта:

$ nc 127.0.0.1 9100
Hello from Imya Prizvysche! Commands: PING | TIME | WHOAMI | STATS | QUIT
PING
PONG
hello world
[2] hello world
TIME
2026-05-05T14:33:02
STATS
messages: 4, uptime: 7s
QUIT
BYE

Зверніть увагу: ехо hello world отримало префікс [2], а не [1] — бо PING був першим повідомленням і вже інкрементував лічильник.

Результат

Після виконання роботи здайте звіт зі скріншотом сесії з nc, де видно вітання з вашим ім'ям та виконано всі п'ять команд (PING, TIME, WHOAMI, STATS, QUIT); поряд — лог сервера зі своїм Student: ... та подіями.


Знайшли помилку чи бажаєте додати інформацію, щоб покращити курс? Створіть issue на GitHub