Skip to content

Отчет по лабораторной работе №1

Выполнил: Шафиков Максим Азатович

Факультет: ПИН (ИКТ)

Группа: К3339

Преподаватель: Говоров Антон Игоревич


Задание 1

Задача:
Реализовать клиентскую и серверную часть приложения. Клиент отправляет серверу сообщение «Hello, server», и оно должно отобразиться на стороне сервера. В ответ сервер отправляет клиенту сообщение «Hello, client», которое должно отобразиться у клиента.
Протокол: UDP.

Решение:
Для решения задачи был использован модуль socket.
Сервер создаёт UDP-сокет, принимает сообщения и отправляет ответ.
Клиент создаёт UDP-сокет, отправляет строку «Hello, server» и получает от сервера «Hello, client».

Код:

server.py:

import socket
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

if __name__ == '__main__':
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.bind((host, port))
    print(f"Сервер запущен на {host}:{port}")

    while True:
        data, addr = udp_socket.recvfrom(1024)
        message = data.decode("utf-8")
        print(f"Получено от {addr}: {message}")

        reply = "Hello, client"
        udp_socket.sendto(reply.encode("utf-8"), addr)

client.py:

import socket
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

if __name__ == '__main__':
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    message = "Hello, server"
    udp_socket.sendto(message.encode("utf-8"), (host, port))

    data, addr = udp_socket.recvfrom(1024)
    print(f"Ответ от сервера: {data.decode('utf-8')}")

Задание 2

Задача:
Клиент запрашивает выполнение математической операции (Теорема Пифагора).
Сервер обрабатывает данные и возвращает результат клиенту.
Протокол: TCP.

Решение:
Клиент запрашивает у пользователя катеты a и b. Отправляет их на сервер.
Сервер принимает данные, вычисляет гипотенузу по формуле c = sqrt(a^2 + b^2) и возвращает клиенту.

Код:

client.py:

import socket
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

if __name__ == "__main__":
    a = input("Введите катет a: ")
    b = input("Введите катет b: ")

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, port))
        s.sendall(f"{a} {b}".encode("utf-8"))
        data = s.recv(1024)

    print("Ответ сервера:", data.decode("utf-8"))

server.py:

import socket
import math
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

if __name__ == "__main__":
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.bind((host, port))
    tcp_socket.listen(5)
    print(f"Сервер слушает {host}:{port} (TCP)")

    while True:
        conn, addr = tcp_socket.accept()
        with conn:
            data = conn.recv(1024)
            if not data:
                continue
            try:
                a_str, b_str = data.decode("utf-8").split()
                a, b = float(a_str), float(b_str)
                c = math.sqrt(a*a + b*b)
                result = f"Гипотенуза c = {c}"
            except Exception as e:
                result = f"Ошибка: {e}"
            conn.sendall(result.encode("utf-8"))

Задание 3

Задача:
Сервер при подключении клиента отдаёт HTML-страницу из файла index.html.

Решение:
Был создан TCP-сервер, который слушает соединения и на любой запрос возвращает содержимое index.html.
HTML-страница содержит кнопку и счётчик кликов.

Код:

index.html:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Кликер</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
        button { font-size: 20px; padding: 10px 20px; }
        #count { font-size: 24px; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>Клики</h1>
    <button id="btn">Клик</button>
    <div id="count">0</div>

    <script>
        const btn = document.getElementById("btn");
        const countDiv = document.getElementById("count");
        let count = 0;

        btn.addEventListener("click", () => {
            count++;
            countDiv.textContent = count;
        });
    </script>
</body>
</html>

server.py:

import socket
import os
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

if __name__ == "__main__":
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp_socket.bind((host, port))
    tcp_socket.listen(5)

    print(f"Сервер запущен на http://{host}:{port}")

    while True:
        conn, addr = tcp_socket.accept()
        with conn:
            request = conn.recv(1024).decode("utf-8", errors="ignore")
            print(f"\n--- Запрос от {addr} ---")
            print(request)

            if os.path.exists("index.html"):
                with open("index.html", "r", encoding="utf-8") as f:
                    body = f.read()
                response = (
                    "HTTP/1.1 200 OK\r\n"
                    "Content-Type: text/html; charset=utf-8\r\n"
                    f"Content-Length: {len(body.encode('utf-8'))}\r\n"
                    "Connection: close\r\n"
                    "\r\n"
                    f"{body}"
                )
            else:
                body = "<h1>Файл index.html не найден</h1>"
                response = (
                    "HTTP/1.1 404 Not Found\r\n"
                    "Content-Type: text/html; charset=utf-8\r\n"
                    f"Content-Length: {len(body.encode('utf-8'))}\r\n"
                    "Connection: close\r\n"
                    "\r\n"
                    f"{body}"
                )

            conn.sendall(response.encode("utf-8"))

Задание 4

Задача:
Реализовать многопользовательский чат.
Протокол: TCP.
Необходимо сохранять пользователей и рассылать сообщения всем клиентам.

Решение:
Сервер обслуживает каждого клиента в отдельном потоке (через threading).
Клиент сначала выбирает уникальный ник (сервер проверяет уникальность).
Сообщения рассылаются всем пользователям, кроме отправителя.

Код:

server.py:

import socket
import threading
from students.k3339.Shafikov_Maxim.Lr1.config import host, port

ENC = "utf-8"

# conn -> nickname
clients = {}
clients_lock = threading.Lock()


def send_line(conn, text: str):
    try:
        conn.sendall((text + "\n").encode(ENC))
    except OSError:
        pass


def broadcast(text: str, exclude=None):
    with clients_lock:
        dead = []
        for c in list(clients.keys()):
            if c is exclude:
                continue
            try:
                c.sendall((text + "\n").encode(ENC))
            except OSError:
                dead.append(c)
        for d in dead:
            name = clients.pop(d, None)
            try:
                d.close()
            except OSError:
                pass


def handle_client(conn: socket.socket, addr):
    name = None
    try:
        f = conn.makefile("r", encoding=ENC, newline="\n")

        # Выбор ника
        while True:
            send_line(conn, "Введите ник: ")
            name_line = f.readline()
            if not name_line:
                return
            candidate = name_line.strip()
            if not candidate:
                send_line(conn, "❌ Ник не может быть пустым.")
                continue
            with clients_lock:
                if candidate in clients.values():
                    send_line(conn, "❌ Ник уже занят. Попробуйте другой.")
                else:
                    name = candidate
                    clients[conn] = name
                    break

        send_line(conn, f"✅ Добро пожаловать, {name}! Напишите /quit для выхода.")
        broadcast(f"🟢 {name} присоединился к чату.", exclude=None)

        for line in f:
            msg = line.rstrip("\n")
            if not msg:
                continue
            if msg.strip().lower() == "/quit":
                send_line(conn, "Пока! Вы вышли из чата.")
                break
            broadcast(f"[{name}]: {msg}", exclude=conn)

    except Exception:
        pass
    finally:
        with clients_lock:
            if conn in clients:
                left_name = clients.pop(conn)
                broadcast(f"🔴 {left_name} покинул чат.", exclude=None)
        try:
            conn.close()
        except OSError:
            pass


def main():
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp_socket.bind((host, port))
    tcp_socket.listen()
    print(f"Сервер запущен на {host}:{port}")

    try:
        while True:
            conn, addr = tcp_socket.accept()
            threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
    except KeyboardInterrupt:
        print("\nОстановка сервера...")
    finally:
        tcp_socket.close()


if __name__ == "__main__":
    main()

client.py:

import socket
import threading
from students.k3339.Shafikov_Maxim.Lr1.config import host, port


def recv_loop(sock: socket.socket):
    """Фоновый поток для приёма сообщений после входа в чат."""
    try:
        f = sock.makefile("r", encoding="utf-8", newline="\n")
        for line in f:
            print(line.rstrip("\n"))
    except Exception:
        pass
    finally:
        try:
            sock.close()
        except OSError:
            pass


def main():
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.connect((host, port))
    f = tcp_socket.makefile("r", encoding="utf-8", newline="\n")

    # Выбор ника
    while True:
        prompt = f.readline()
        if not prompt:
            print("Сервер закрыл соединение.")
            return
        print(prompt.strip())
        name = input("> ")
        tcp_socket.sendall((name + "\n").encode("utf-8"))
        reply = f.readline()
        if not reply:
            print("Сервер закрыл соединение.")
            return
        print(reply.strip())
        if reply.startswith("✅"):
            break

    t = threading.Thread(target=recv_loop, args=(tcp_socket,), daemon=True)
    t.start()

    try:
        while True:
            line = input()
            if not line:
                continue
            tcp_socket.sendall((line + "\n").encode("utf-8"))
            if line.strip().lower() == "/quit":
                break
    except KeyboardInterrupt:
        tcp_socket.sendall(("/quit\n").encode("utf-8"))
    finally:
        try:
            tcp_socket.close()
        except OSError:
            pass


if __name__ == "__main__":
    main()

Задание 5

Задача:
Написать веб-сервер, который принимает и записывает информацию о дисциплине и оценке по дисциплине.
Сервер должен отдать HTML-страницу с таблицей всех оценок.

Решение:
Использован TCP-сокет.
- GET-запрос возвращает HTML-страницу с таблицей и формой для ввода.
- POST-запрос добавляет запись (дисциплина + оценка), проверяет корректность, и снова отдаёт страницу.
- Для дисциплин выводится список оценок и среднее значение.

Код:

server.py:

import socket
import urllib.parse
from collections import defaultdict
from students.k3339.Shafikov_Maxim.Lr1.config import host, port


grades = defaultdict(list)


def render_html():
    rows = ""
    if grades:
        for subj, marks in grades.items():
            avg = sum(marks) / len(marks)
            marks_str = ", ".join(str(m) for m in marks)
            rows += f"<tr><td>{subj}</td><td>{marks_str}</td><td>{avg:.2f}</td></tr>\n"
    else:
        rows = '<tr><td colspan="3">Пока нет оценок</td></tr>'

    return f"""<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>Оценки по дисциплинам</title>
  <style>
    body {{ font-family: Arial, sans-serif; margin: 40px; }}
    table {{ border-collapse: collapse; width: 500px; margin-bottom: 20px; }}
    th, td {{ border: 1px solid #333; padding: 8px; text-align: center; }}
    th {{ background: #eee; }}
    form {{ display: flex; flex-direction: column; width: 400px; gap: 10px; }}
    label {{ display: flex; justify-content: space-between; }}
    input[type=text], input[type=number] {{ flex: 1; margin-left: 10px; }}
    input[type=submit] {{ padding: 8px; font-size: 16px; }}
  </style>
</head>
<body>
  <h1>Оценки по дисциплинам</h1>
  <table>
    <tr><th>Дисциплина</th><th>Оценки</th><th>Средняя</th></tr>
    {rows}
  </table>
  <form method="POST">
    <label>Дисциплина: <input type="text" name="subject" required></label>
    <label>Оценка (1-5): <input type="number" name="grade" min="1" max="5" required></label>
    <input type="submit" value="Добавить">
  </form>
</body>
</html>"""


def handle_request(request: str):
    lines = request.split("\r\n")
    if not lines:
        return "HTTP/1.1 400 Bad Request\r\n\r\n"

    first_line = lines[0]
    method, *_ = first_line.split()

    if method == "POST":
        body = request.split("\r\n\r\n", 1)[-1]
        data = urllib.parse.parse_qs(body)
        subject = data.get("subject", [""])[0].strip()
        grade_str = data.get("grade", [""])[0].strip()

        if subject and grade_str.isdigit():
            grade = int(grade_str)
            if 1 <= grade <= 5:
                grades[subject].append(grade)

    body = render_html()
    headers = [
        "HTTP/1.1 200 OK",
        "Content-Type: text/html; charset=utf-8",
        f"Content-Length: {len(body.encode("utf-8"))}",
        "Connection: close",
        "",
        ""
    ]
    return "\r\n".join(headers) + body


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_socket:
        tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        tcp_socket.bind((host, port))
        tcp_socket.listen(5)
        print(f"Сервер слушает на http://{host}:{port}")

        while True:
            conn, addr = tcp_socket.accept()
            print(f"[+] Подключение от {addr[0]}:{addr[1]}")
            with conn:
                request = conn.recv(4096).decode("utf-8", errors="ignore")
                if not request:
                    continue
                response = handle_request(request)
                conn.sendall(response.encode("utf-8"))


if __name__ == "__main__":
    main()

Вывод

В ходе выполнения лабораторной работы №1 были изучены основы работы с сетью на Python с помощью библиотеки socket.
Реализованы:
- UDP-сервер и клиент (обмен сообщениями).
- TCP-сервер и клиент (математические вычисления).
- Мини-веб-сервер, отдающий HTML-страницу.
- Многопользовательский чат с потоками.
- Веб-сервер для обработки GET/POST-запросов с сохранением и отображением данных.

Работа позволила закрепить понимание различий между протоколами UDP и TCP, научиться обрабатывать сетевые соединения и реализовывать простейшие серверные приложения.