Различия между версиями 10 и 11
Версия 10 от 2023-04-16 15:53:17
Размер: 9901
Редактор: FrBrGeorge
Комментарий:
Версия 11 от 2023-05-31 16:04:20
Размер: 10256
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 82: Строка 82:
  * <!> Например, `stream` — это последовательность байтов, и мы используем `.readline()` для того, чтобы считывать из неё по одной строке. А вот клиент, использующий `socket.recv(1024)`, примет несколько строк разом.

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

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

Статья на Хабре про стратегии git merge

Раздельное добавление ханков

  • Откат истории (повторение):
    • Команды git-resetgit reset --hard)

  • git add -p

  • git add --interactive (ALT: пакет perl-Git)

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

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

  • diff и patch

    • (в частности, diff -u)

    • понятие контекста
      • fuzzy контекст
    • BTW, [g]vimdiff

Патчи и Git:

  • git-format-patch и git-am / git-apply

    • Замечание: git не умеет в fuzzy (и правильно!)

    • ⇒ иногда уместнее patch -u или patch --git

  • git-cherry-pick

  • Серия коммитов ⇒ серия патчей, условная нестрогость порядка

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

BTW: difflib

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

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

   1 import sys
   2 import socket
   3 
   4 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
   5     s.connect((sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 1337))
   6     while msg := sys.stdin.readline():
   7         s.sendall(msg.strip().encode())
   8         print(s.recv(1024).decode())
  • Обязателен «ответ» после «запроса» (send() и receive() чередуются, именно поэтому в большинстве старых протоколов так принято делать!)

  • Мы будем пользоваться netcat

Посмотрим в примеры. В частности, 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())
  • Это asyncio (вспоминаем):

    • await вместо yield from и в целом Python async

    • явный yield выбрасывает корутину в скрытый управляющий цикл,

  • В примере тоже строго чередуются send() и receive()

  • Сервер принимает несколько TCP-соединений
    • обрабатываются они не в отдельных тредах (и не в отдельных процессах), а асинхронно в одном треде
      • ⇒ любое исключение в одной из запущенных корутин останавливает весь сервер
      • → Из двух зол выбрано меньшее: все исключения молча игнорируются

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

  • Входящий поток от одного соединения сервер ретранслирует на все остальные
    • Поскольку нет многопоточности, гонки могут возникнуть только на неатомарных операциях
    • <!> Например, stream — это последовательность байтов, и мы используем .readline() для того, чтобы считывать из неё по одной строке. А вот клиент, использующий socket.recv(1024), примет несколько строк разом.

  • Ретрансляцию сделаем с помощью asyncio.Queue для каждого клиента

    • ID клиента — это IP+порт (.get_extra_info('peername'))

  • Нет простого способа узнать, есть ли входящие данные в Stream
    • ⇒ асинхронно запустим send() (из Stream) и receive() (из Queue), какой успеет первым, такой и обработаем

  • для планировщика asyncio нужно обмазать корутину Task-ом (заданием), иначе:

    • DeprecationWarning: The explicit passing of coroutine objects to asyncio.wait() is deprecated since Python 3.8, and scheduled for removal in Python 3.11.

  • При отключении клиента остановим оба необработанных Task-а (иначе они останутся в планировщике)

   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') уникальным идентификатором пользователя является название коровы из Streams

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

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

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

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

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

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

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

  3. {2} (необязательный пункт — оказалось, что это непросто) Написать более продвинутый аналог netcat для соединения с «коровьим чатом»

    • Клиент должен принимать и отправлять строки асинхронно

    • Использовать Streams

    • Использовать readline или cmd (можно подставлять команды)

      • необязательно: complete_login() и complete_say() могут ходить на сервер, выполнять там , соответственно, cows и who и подставлять соответствующие списки

    • Если не делать этого пункта, вместо клиента можно пользоваться либо системным netcat, либо простым скриптом

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

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

LecturesCMC/PythonDevelopment2023/05_DiffPatchNet (последним исправлял пользователь FrBrGeorge 2023-05-31 16:04:20)