Взаимодействие на основе патчей. Работа с сетью

TODO в 2025-м эта лекция закончилась за час — возможно, стоит питонью часть делать подробнее (не копипастить код коротких примеров, а писать их в прямом эфире)?

Ещё про работу с историей

Немного лайфхаков:

Работа с патчами и наборами патчей

Немного о формате

Патчи и Git:

Патч или набор с точки зрения GIT — это сериализация коммитов, превращение их в пригодный для передачи формат.

Пример взаимодействия:

   1 $ cp /usr/lib64/python3.13/calendar.py .
   2 $ git add .                               
   3 $ git commit -a -m initial
   4 $ git tag root   
   5 $ sed -i 's/WEDNESDAY/ADDAMS/' calendar.py
   6 $ git commit -a -m "Addams"
   7 $ sed -i 's/= 2/= 13/' calendar.py 
   8 $ git commit -a -m "Thirteen"
   9 $ git format-patch HEAD~2
  10 $ git reset --hard HEAD~2
  11 $ git am *.patch

BTW: difflib

Простейший сетевой сервер

Простейший низкоуровневый сервер с помощью socket

   1 import socket
   2 
   3 HOST, PORT = 'localhost', 1337
   4 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
   5     s.bind((HOST, PORT))
   6     s.listen()
   7     while True:
   8         conn, addr = s.accept()
   9         with conn:
  10             print('Connected by', addr)
  11             while data := conn.recv(1024):
  12                 conn.sendall(data.swapcase())

   1 import socket
   2 import multiprocessing
   3 
   4 def serve(conn, addr):
   5     with conn:
   6         print('Connected by', addr)
   7         while data := conn.recv(1024):
   8               conn.sendall(data.swapcase())
   9 
  10 HOST, PORT = 'localhost', 1337
  11 with socket.create_server((HOST, PORT)) as s:
  12     s.listen()
  13     while True:
  14         conn, addr = s.accept()
  15         multiprocessing.Process(target=serve, args=(conn, addr)).start()

Тупой аналог netcat с помощью socket:

   1 import sys
   2 import socket
   3 
   4 host = "localhost" if len(sys.argv) < 2 else sys.argv[1]
   5 port = 1337 if len(sys.argv) < 3 else int(sys.argv[2])
   6 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
   7     s.connect((host, port))
   8     while msg := sys.stdin.buffer.readline():
   9         s.sendall(msg)
  10         print(s.recv(1024).rstrip().decode())

Asyncio Streams

Недостаток threading — необходимость следить за thread safe и синхронизация по доступу к данным; недостаток multiprocessing — требует отдельной дисциплины по передаче обработанных данных из процесса в процесс.

Выход: asyncio. Посмотрим в примеры. В частности, echo-сервер:

   1 import asyncio
   2 
   3 async def echo(reader, writer):
   4     while data := await reader.readline():
   5         writer.write(data.swapcase())
   6     writer.close()
   7     await writer.wait_closed()
   8 
   9 async def main():
  10     server = await asyncio.start_server(echo, '0.0.0.0', 1337)
  11     async with server:
  12         await server.serve_forever()
  13 
  14 asyncio.run(main())

Используем Streams для написания «общего чата».

   1 #!/usr/bin/env python3
   2 import asyncio
   3 
   4 clients = {}
   5 
   6 async def chat(reader, writer):
   7     me = "{}:{}".format(*writer.get_extra_info('peername'))
   8     print(me)
   9     clients[me] = asyncio.Queue()
  10     send = asyncio.create_task(reader.readline())
  11     receive = asyncio.create_task(clients[me].get())
  12     while not reader.at_eof():
  13         done, pending = await asyncio.wait([send, receive], return_when=asyncio.FIRST_COMPLETED)
  14         for q in done:
  15             if q is send:
  16                 send = asyncio.create_task(reader.readline())
  17                 for out in clients.values():
  18                     if out is not clients[me]:
  19                         await out.put(f"{me} {q.result().decode().strip()}")
  20             elif q is receive:
  21                 receive = asyncio.create_task(clients[me].get())
  22                 writer.write(f"{q.result()}\n".encode())
  23                 await writer.drain()
  24     send.cancel()
  25     receive.cancel()
  26     print(me, "DONE")
  27     del clients[me]
  28     writer.close()
  29     await writer.wait_closed()
  30 
  31 async def main():
  32     server = await asyncio.start_server(chat, '0.0.0.0', 1337)
  33     async with server:
  34         await server.serve_forever()
  35 
  36 asyncio.run(main())

Д/З

  1. Почитать про asyncio

  2. Превратить «общий чат» в «коровий» следующим образом:
    • Вводимые строки состоят из команды с возможными параметрами
    • Вместо get_extra_info('peername') уникальным идентификатором пользователя является название коровы из python-cowsay, под которым он регистрируется

      • Пока пользователь не зарегистрировался, он не имеет право ни писать, ни получать сообщения
    • Сообщения оформляются с помощью cowsay() из модуля python-cowsay

    • Команды:
      • who — просмотр зарегистрированных пользователей

      • cows — просмотр свободных имён коров

      • login название_коровы — зарегистрироваться под именем название_коровы

      • say название_коровы текст сообщения — послать сообщение пользователю название_коровы

      • yield текст сообщения — послать сообщение всем зарегистрированным пользователям

      • quit — отключиться

  3. Вместо клиента можно пользоваться либо системным netcat, либо скриптом из простого модуля netcat, либо чуть более продвинутым ntsh

  4. Разработку вести согласно дисциплине оформления коммитов в подкаталоге 05_DiffPatchNet отчётного репозитория по Д/З

  5. Предполагается, что модуль python-cowsay устанавливается в окружение с помощью pipenv, в каталоге должен присутствовать соответствующий Pipfile

LecturesCMC/PythonDevelopment2026/05_DiffPatchNet (последним исправлял пользователь FrBrGeorge 2026-03-10 23:44:36)