Add week 4 materials

This commit is contained in:
2026-10-01 22:08:30 +03:00
parent 6210b46c1f
commit 6008cff9f6
21 changed files with 1707 additions and 0 deletions
@@ -0,0 +1,19 @@
services:
sender:
build:
context: sender
depends_on:
- receiver1
- receiver2
- receiver3
ports:
- 9999:9999/udp
receiver1:
build:
context: receiver
receiver2:
build:
context: receiver
receiver3:
build:
context: receiver
@@ -0,0 +1,70 @@
# IP multicast
Один контейнер принимает UDP-датаграммы на порту 9999 и пересылает их на multicast-адрес `224.0.2.0:10000`. Три контейнера-получателя присоединяются к этой группе и печатают полученные сообщения. В примере нет подтверждений, повторной передачи и восстановления пропусков.
## Запуск
Из корня репозитория перейдите в каталог примера и запустите контейнеры:
```sh
cd materials/04-group-comm/seminar/ip_multicast
docker compose up --build -d
docker compose logs -f sender receiver1 receiver2 receiver3
```
Дождитесь сообщения `ready` от отправителя и каждого получателя. Последняя команда показывает журналы и занимает терминал. Следующие команды выполняйте во втором терминале из того же каталога.
Отправьте пробное сообщение через Python 3:
```sh
python -c "import socket; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'probe',('127.0.0.1',9999)); s.close()"
```
При необходимости замените `python` на имя Python 3 в своём окружении. Убедитесь, что `probe` появился в журналах всех трёх получателей. Отправитель также печатает каждую пересылку. Имена сервисов в журнале позволяют различить получателей.
Если с хоста сообщения не доходят (нет `forwarded`), отправляйте из контейнера: `docker compose exec sender python -c "..."`.
## Эксперимент: получатель пропустил сообщение
До запуска предположите, какие получатели увидят `m1` и `m2` и получит ли вернувшийся участник пропущенные данные.
1. Остановите третьего получателя:
```sh
docker compose stop receiver3
```
2. Отправьте `m1`:
```sh
python -c "import socket; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'm1',('127.0.0.1',9999)); s.close()"
```
3. Верните третьего получателя:
```sh
docker compose start receiver3
```
4. Дождитесь **нового** сообщения `ready` от `receiver3` и снова отправьте `probe`. Убедитесь, что третий получатель его видит.
5. Отправьте `m2`, заменив в команде `b'm1'` на `b'm2'`.
При штатной работе стенда `m1` увидят первый и второй получатели, а `m2` — все три. Третий получатель не получит `m1` задним числом: программа не хранит историю и не запрашивает пропущенные сообщения. UDP допускает потери и без отключения получателя; успешная проба не является доказательством гарантированной доставки.
Обсудите:
- Какие действия выполняет `IP_ADD_MEMBERSHIP` и где задаётся адрес группы?
- Где в программе можно было бы добавить подтверждения или восстановление пропусков?
- Почему получение сетевого пакета и надёжная доставка сообщения приложению — разные события?
Пустая UDP-датаграмма тоже является сообщением: `b''` не означает закрытие соединения. Можно отправить её и затем обычное сообщение, чтобы проверить, что оба процесса продолжают работу.
Повторный запуск контейнера в этом эксперименте иллюстрирует пропуск данных. В домашнем задании используется модель crash-stop: отказавшие процессы не возвращаются.
## Завершение
Остановите просмотр журналов сочетанием Ctrl+C, затем удалите контейнеры стенда:
```sh
docker compose down
```
@@ -0,0 +1,5 @@
FROM python:3.12-slim
COPY . .
CMD ["python", "-u", "receiver.py"]
@@ -0,0 +1,12 @@
import socket
import struct
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', 10000))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, struct.pack('4sL', socket.inet_aton('224.0.2.0'), socket.INADDR_ANY))
print("ready: joined 224.0.2.0, listening on UDP :10000", flush=True)
while True:
data, addr = sock.recvfrom(1024)
print(f'got message {data!r} from {addr}', flush=True)
@@ -0,0 +1,5 @@
FROM python:3.12-slim
COPY . .
CMD ["python", "-u" , "sender.py"]
@@ -0,0 +1,14 @@
import socket
import struct
if __name__ == '__main__':
in_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
in_socket.bind(('', 9999))
out_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
out_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, struct.pack('b', 1))
print("ready: listening on UDP :9999, forwarding to 224.0.2.0:10000", flush=True)
while True:
data, addr = in_socket.recvfrom(1024)
out_socket.sendto(data, ('224.0.2.0', 10000))
print(f'forwarded {data!r} from {addr} to 224.0.2.0:10000', flush=True)