Взаимодействие на основе патчей. Работа с сетью
Ещё про работу с историей
Статья на Хабре про стратегии git merge
Раздельное добавление ханков
- Откат истории (повторение):
Команды git-reset (и git reset --hard)
git add --interactive (ALT: пакет perl-Git)
Работа с патчами и наборами патчей
Немного о формате
(в частности, diff -u)
- понятие контекста
- fuzzy контекст
BTW, [g]vimdiff и прочие mergetool-ы
Патчи и Git:
git-format-patch и git-am / git-apply
Замечание: git не умеет в fuzzy (и правильно!)
⇒ иногда уместнее patch -u или patch --git
- Серия коммитов ⇒ серия патчей, условная нестрогость порядка
Патч или набор с точки зрения GIT — это сериализация коммитов, превращение их в пригодный для передачи формат.
BTW: difflib
Простейший сетевой сервер
Простейший низкоуровневый сервер с помощью socket
1 import socket
2 import sys
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.bind((host, port))
8 s.listen()
9 conn, addr = s.accept()
10 with conn:
11 print('Connected by', addr)
12 while data := conn.recv(1024):
13 conn.sendall(data.swapcase())
Главный недостаток: в каждый момент обрабатывает не более одного подключения
В действительности всю логику после установления соединения (s.accept()) надо выполнять в отдельном процессе / /потоке / корутине:
1 import socket
2 import sys
3 import multiprocessing
4
5 def serve(conn, addr):
6 with conn:
7 print('Connected by', addr)
8 while data := conn.recv(1024):
9 conn.sendall(data.swapcase())
10
11 host = "localhost" if len(sys.argv) < 2 else sys.argv[1]
12 port = 1337 if len(sys.argv) < 3 else int(sys.argv[2])
13 with socket.create_server((host, port)) as s:
14 s.listen()
15 while True:
16 conn, addr = s.accept()
17 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())
Обязателен «ответ» после «запроса» (send() и receive() чередуются, именно поэтому в большинстве старых протоколов так принято делать!)
В общем случае программа сильно сложнее. Мы будем пользоваться netcat
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())
Это 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') уникальным идентификатором пользователя является название коровы из python-cowsay, под которым он регистрируется
- Пока пользователь не зарегистрировался, он не имеет право ни писать, ни получать сообщения
Сообщения оформляются с помощью cowsay() из модуля python-cowsay
- Команды:
who — просмотр зарегистрированных пользователей
cows — просмотр свободных имён коров
login название_коровы — зарегистрироваться под именем название_коровы
say название_коровы текст сообщения — послать сообщение пользователю название_коровы
yield текст сообщения — послать сообщение всем зарегистрированным пользователям
quit — отключиться
Вместо клиента можно пользоваться либо системным netcat, либо скриптом из простого модуля netcat
Разработку вести согласно дисциплине оформления коммитов в подкаталоге 05_DiffPatchNet отчётного репозитория по Д/З
Предполагается, что модуль python-cowsay устанавливается в окружение с помощью pipenv, в каталоге должен присутствовать соответствующий Pipfile