Предыдущая часть:
Так как игра походовая, любые изменения на сервере происходят только после запроса клиента. Точно так же и в клиенте любые дальнейшие действия происходят только после получения ответа сервера.
Эти замечания важны, так как естественным образом выстраивается синхронный обмен.
Один ход игры состоит из следующих фаз:
- Клиент получает ответы с сервера
- Клиент рисует обновления
- Клиент ждёт команды
- Клиент отправляет команду на сервер
В то же время где-то вдали:
- Сервер получает запросы от клиента
- Сервер вносит изменения в состояние
- Сервер отправляет клиенту ответы
Это независимые друг от друга активности, так как происходят в разных местах, но они синхронизируются друг с другом за счёт ожидания данных.
Здесь можно увидеть ленту Мёбиуса или двухтактный двигатель: когда клиент заканчивает свой цикл отправкой send(request) внизу, сервер начинает свой цикл опросом get_requests() вверху, и наоборот.
Код клиента:
Вышеописанная схема действий клиента реализована в методе update(), который будет вызываться в цикле.
В конструктор клиента передаётся proxy для него и клиент сразу же вызывает метод connect(). Клиентский прокси устанавливает соединение с серверным прокси, который сразу отправляет первый ответ, где находится GameState.
Этот ответ будет прочитан клиентом в update() с первым же вызовом proxy.get_responses(). То есть гарантируется, что при первом вызове нас уже будут ждать данные от сервера, GameState будет получено и цикл успешно запустится.
В методе render() клиент перебирает все полученные Response (важно то, что они могут прийти пачкой), и обрабатывает каждый из них в соответствии с типом. На данный момент обработки как таковой нет, и клиент просто печатает содержимое response.data (ему даже не не нужен GameState).
Код клиентского прокси (частичный):
В методе connect() прокси открывает сетевой сокет до серверного прокси. Сразу после установки соединения серверный прокси вышлет Response.INIT. Ответ не читается здесь, но уже ждёт в буфере чтения сокета.
Метод send_request() отправляет данные серверному прокси. Он преобразует объекты Request от клиента в сырой текст для передачи, используя encode_request(). Текстовый запрос имеет простой формат "тип - пробел - данные".
Метод get_responses() читает данные из сокета и преобразует их в понятные клиенту объекты Response с помощью метода decode_responses(). На данный момент все Response присылаются в виде строк, отделённых друг от друга символом новой строки ("\n"). А каждая строка имеет формат также "тип - пробел - данные".
Когда прокси декодирует ответы сервера, его задача – обновить состояние game_state для клиента. Для каждого response вызывается метод update_state():
Он проверяет: если тип ответа это INIT, то он перезаписывает все данные в game_state. А иначе... делает то же самое, так как другие варианты пока не предусмотрены.
Наконец, посмотрим на скрипт запуска клиента:
Да, это вся главная программа, которая просто создаёт объекты клиента и прокси, а затем в цикле вызывает метод client.update().
Серверная сторона устроена немного сложнее и кода там больше, поэтому её описание будет в следующей части.
Читайте дальше: