diff --git a/README.md b/README.md index 426da01..b121397 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ -# innopol-protocol-mayak-server +# about + +## server.py + +Запускаем на удаленном сервере +Слушает указанный порт, ждет подключение клиента, печатает принятое сообщение, при получении отправляет заданное сообщение + +``` +python3 server.py --host 0.0.0.0 --port 5000 --response "OK, пакет получен" +``` + +## client.py + +Запускаем на локальной машине +Указываем адрес сервера, порт сообщение которое отправляем и интервал повторения отправки + +``` +python3 client.py --host 127.0.0.1 --port 5000 --message "ping" --interval 5 +``` diff --git a/client.py b/client.py new file mode 100644 index 0000000..362b270 --- /dev/null +++ b/client.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import socket +import struct +import argparse +import time +import sys + +def recv_exact(sock, nbytes): + chunks = [] + got = 0 + while got < nbytes: + chunk = sock.recv(nbytes - got) + if not chunk: + raise ConnectionError("Соединение закрыто сервером") + chunks.append(chunk) + got += len(chunk) + return b"".join(chunks) + +def recv_packet(sock): + raw_len = recv_exact(sock, 4) + (length,) = struct.unpack("!I", raw_len) + if length > 16 * 1024 * 1024: + raise ValueError(f"Слишком большой пакет: {length} байт") + return recv_exact(sock, length) + +def send_packet(sock, data: bytes): + sock.sendall(struct.pack("!I", len(data)) + data) + +def connect(host, port, timeout=10): + return socket.create_connection((host, port), timeout=timeout) + +def main(): + ap = argparse.ArgumentParser(description="TCP клиент (длина+данные) с периодической отправкой.") + ap.add_argument("--host", default="127.0.0.1", help="Адрес сервера") + ap.add_argument("--port", type=int, default=5000, help="Порт сервера") + ap.add_argument("--message", default="Привет от клиента!", help="Сообщение (UTF-8)") + ap.add_argument("--interval", type=float, default=5.0, help="Интервал отправки, сек (по умолчанию 5)") + args = ap.parse_args() + + msg_bytes = args.message.encode("utf-8") + + sock = None + try: + while True: + # Подключаемся, если не подключены + if sock is None: + try: + sock = connect(args.host, args.port, timeout=10) + sock.settimeout(15) # таймаут на операции, чтобы не зависать навсегда + print(f"Подключено к {args.host}:{args.port}") + except Exception as e: + print(f"Не удалось подключиться: {e}. Повтор через {args.interval} сек.") + time.sleep(args.interval) + continue + + # Пытаемся отправить/получить + try: + send_packet(sock, msg_bytes) + print(f"Отправлено серверу: {args.message!r}") + + resp = recv_packet(sock) + try: + print(f"Ответ сервера: {resp.decode('utf-8')!r}") + except UnicodeDecodeError: + print(f"Ответ сервера (bytes): {resp!r}") + + time.sleep(args.interval) + + except (ConnectionError, BrokenPipeError, TimeoutError, socket.timeout) as e: + print(f"Потеряно соединение: {e}. Переподключение через {args.interval} сек.") + try: + sock.close() + except Exception: + pass + sock = None + time.sleep(args.interval) + + except KeyboardInterrupt: + print("\nЗавершение по Ctrl+C.") + finally: + if sock: + try: + sock.close() + except Exception: + pass + +if __name__ == "__main__": + main() diff --git a/server.py b/server.py new file mode 100644 index 0000000..7d6406a --- /dev/null +++ b/server.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import socket +import struct +import argparse +import threading + +def recv_exact(sock, nbytes): + chunks, got = [], 0 + while got < nbytes: + chunk = sock.recv(nbytes - got) + if not chunk: + raise ConnectionError("Клиент закрыл соединение") + chunks.append(chunk) + got += len(chunk) + return b"".join(chunks) + +def recv_packet(sock): + raw_len = recv_exact(sock, 4) + (length,) = struct.unpack("!I", raw_len) + if length > 16 * 1024 * 1024: + raise ValueError(f"Слишком большой пакет: {length} байт") + return recv_exact(sock, length) + +def send_packet(sock, data: bytes): + sock.sendall(struct.pack("!I", len(data)) + data) + +def handle_client(conn, addr, response_text: str): + print(f"[+] Подключился {addr[0]}:{addr[1]}") + try: + while True: + data = recv_packet(conn) # ждём следующий пакет + try: + print(f"[{addr[0]}:{addr[1]}] получил: {data.decode('utf-8')!r}") + except UnicodeDecodeError: + print(f"[{addr[0]}:{addr[1]}] получил (bytes): {data!r}") + send_packet(conn, response_text.encode("utf-8")) + except (ConnectionError, OSError) as e: + print(f"[-] {addr[0]}:{addr[1]} отключился: {e}") + finally: + conn.close() + +def main(): + ap = argparse.ArgumentParser(description="TCP сервер (длина+данные), держит соединение.") + ap.add_argument("--host", default="0.0.0.0") + ap.add_argument("--port", type=int, default=5000) + ap.add_argument("--response", default="OK, пакет получен") + args = ap.parse_args() + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv: + srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + srv.bind((args.host, args.port)) + srv.listen(16) + print(f"Сервер слушает {args.host}:{args.port} ... (Ctrl+C для выхода)") + try: + while True: + conn, addr = srv.accept() + t = threading.Thread(target=handle_client, args=(conn, addr, args.response), daemon=True) + t.start() + except KeyboardInterrupt: + print("\nОстановка сервера.") + +if __name__ == "__main__": + main()