Взаимодействие на основе патчей. Работа с сетью
Ещё про работу с историей
Статья на Хабре про стратегии git merge
Раздельное добавление ханков
- Откат истории (повторение):
Команды git-reset (и git reset --hard)
git add --interactive (ALT: пакет perl-Git)
Работа с патчами и наборами патчей
Немного о формате
Патчи и Git:
git-format-patch и git-am / git-apply
Замечание: git не умеет в fuzzy (и правильно!)
⇒ иногда уместнее patch -u или patch --git
- Серия коммитов ⇒ серия патчей, условная нестрогость порядка
Патч или набор с точки зрения GIT — это сериализация коммитов, превращение их в пригодный для передачи формат.
BTW: difflib
Tools/scripts/diff.py в качестве замены diff
ALT: /usr/share/python3.10/Tools/scripts/diff.py
(в коде)
Простейший сетевой сервер
Тупой аналог netcat с помощью socket:
Обязателен «ответ» после «запроса» (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())
Д/З
Почитать про asyncio
…в частности про разработку и отладку для asyncio
Про читать про Streams и поупражняться в них
- Превратить «общий чат» в «коровий» следующим образом:
- Вводимые строки состоят из команды с возможными параметрами
Вместо get_extra_info('peername') уникальным идентификатором пользователя является название коровы из Streams
- Пока пользователь не зарегистрировался, он не имеет право ни писать, ни получать сообщения
Сообщения оформляются с помощью cowsay() из модуля python-cowsay
- Команды:
who — просмотр зарегистрированных пользователей
cows — просмотр свободных имён коров
login название_коровы — зарегистрироваться под именем название_коровы
say название_коровы текст сообщения — послать сообщение пользователю название_коровы
yield текст сообщения — послать сообщение всем зарегистрированным пользователям
quit — отключиться
(необязательный пункт — оказалось, что это непросто) Написать более продвинутый аналог netcat для соединения с «коровьим чатом»
Клиент должен принимать и отправлять строки асинхронно
Использовать Streams
Использовать readline или cmd (можно подставлять команды)
необязательно: complete_login() и complete_say() могут ходить на сервер, выполнять там , соответственно, cows и who и подставлять соответствующие списки
Если не делать этого пункта, вместо клиента можно пользоваться либо системным netcat, либо простым скриптом
Разработку вести согласно дисциплине оформления коммитов в подкаталоге 05_DiffPatchNet отчётного репозитория по Д/З
Предполагается, что модуль python-cowsay устанавливается в окружение с помощью pipenv, в каталоге должен присутствовать соответствующий Pipfile