Python библиотека для Photon Server

Python библиотека для Photon Server

706

В течение крайних пары месяцев пришлось близко познакомится с фреймворком для разработки клиент-серверных игр Photon. Речь пойдет о библиотеке для работы с Photon Server на языке Python. В данной статье я не буду останавливаться на плюсах и минусах Photon, так как для этого, пожалуй, нужен опыт работы наиболее чем с одним фреймворком.

Для начала пара слов о том, для чего это вообщем было необходимо и почему я не употреблял одну из уже имеющихся клиентских библиотек.

По данной причине C#, Java и C++ были отметены. В процессе работы над серверной частью игрового проекта у нас нередко появлялись служебные задачки, такие как получение текущих данных впрямую из приложения, graceful остановка игрового сервиса (нельзя просто так взять и вырубить игровой сервер, пока на нем есть люди). При этом не хотелось перекомпиливать все при мельчайших конфигурациях кода. В самом начале для работы со всеми служебными частями приложения было написано маленькое GUI приложение, средством которого эти все трудности и решались. Оставался еще вариант с JavaScript и node.js, но меня довольно издавна приманивал Python и чрезвычайно хотелось с ним познакомиться. Но с течением времени все почаще нам хотелось работать с этими служебными вещами через консоль, да еще и юниксовую. А здесь таковой успешный вариант. Потому решено было написать библиотеку конкретно на этом языке.

Как в последствии выяснилось опосля беседы с разрабами Photon, лучше было взять библиотеку на C#, так как она наиболее свежайшая. Java была выбрана поэтому, что этот язык мне поближе, так как крайние несколько лет я занимался разработкой конкретно на этом языке. Так как протокол Photon не выложен в открытом доступе (ну либо во всяком случае я его отыскать не сумел), пришлось декомпилировать исходники клиентской библиотеки, написанной на Java, изучить их и переписать все это дело на Python.

Вначале я желал написать все на Python версии 2.7 (на момент написания библиотеки актуальная версия была 2.7.9), но столкнувшись с трудностью в реализации неких вещей, которые уже были реализованы «из коробки» в 3.4, решил все же идти в ногу со временем.

С исходниками библиотеки можно ознакомиться на гитхабе.

Разберем небольшой кусочек кода, который употребляет эту библиотеку. Интерфейсы фактически на сто процентов совпадают с библиотеками на остальных языках, потому для тех, кто знаком с Photon, ничего новейшего здесь не будет.

Для начала реализуем интерфейс PeerListener:

class Connection:
def __init__(self, connected=False):
self.connected = connected

class SimpleListener(PeerListener):
def __init__(self, connection):
super().__init__()
self.connection = connection

def debug_return(self, debug_level, message):
print("[{}] — {}".format(debug_level.name, message))

def on_status_changed(self, status_code):
print("[Status changed] — {}".format(status_code.name))
if status_code is StatusCode.Connect:
self.connection.connected = True

def on_operation_response(self, op_response):
print(op_response)

def on_event(self, event_data):
print(op_response)
Пока не будем никак обрабатывать OperationResponse и Event’ы. Так же здесь употребляется небольшой класс Connection, просто как holder для статуса соединения. Будем просто выводить их в консоль. Он нам понадобится чуток позднее.

Сейчас напишем сервисный поток, который будет дергать способ service у нашего пира приблизительно 10 раз в секунду (рекомендация разрабов):

class ServiceThread(threading.Thread):
def __init__(self, pp):
threading.Thread.__init__(self)

self.pp = pp
self._run = False

def run(self):
self._run = True

while self._run:
self.pp.service()

time.sleep(100.0 / 1000.0)

def stop(self):
self._run = False
Ну и естественно же main функция:

def main():
connection = Connection()

pp = PhotonPeer(enums.ConnectionProtocol.Tcp, SimpleListener(connection))
pp.set_debug_level(DebugLevel.All)

pp.connect(your_ip, your_port, your_app_name)

service_thread = ServiceThread(pp)
service_thread.start()

while connection.connected is False:
pass

# Put your code here

service_thread.stop()
service_thread.join()

pp.disconnect()
Полностью исходник тутimport threading
import time

from photon import enums

from photon.enums import DebugLevel, StatusCode
from photon.listener import PeerListener
from photon.peer import PhotonPeer

class Connection:
def __init__(self, connected=False):
self.connected = connected

def main():
connection = Connection()

pp = PhotonPeer(enums.ConnectionProtocol.Tcp, SimpleListener(connection))
pp.set_debug_level(DebugLevel.All)

pp.connect(your_ip, your_port, your_app_name)

service_thread = ServiceThread(pp)
service_thread.start()

while connection.connected is False:
pass

# Put your code here

service_thread.stop()
service_thread.join()

pp.disconnect()

class ServiceThread(threading.Thread):
def __init__(self, pp):
threading.Thread.__init__(self)

self.pp = pp
self._run = False

def run(self):
self._run = True

while self._run:
self.pp.service()

time.sleep(100.0 / 1000.0)

def stop(self):
self._run = False

class SimpleListener(PeerListener):
def __init__(self, connection):
super().__init__()
self.connection = connection

def debug_return(self, debug_level, message):
print("[{}] — {}".format(debug_level.name, message))

def on_status_changed(self, status_code):
print("[Status changed] — {}".format(status_code.name))
if status_code is StatusCode.Connect:
self.connection.connected = True

def on_operation_response(self, op_response):
print(op_response)

def on_event(self, event_data):
print(op_response)

if __name__ == "__main__":
main()

Раздельно хотелось бы тормознуть на сериализации целочисленных типов. Для тех, кто успел заглянуть в исходники, схожий код может показаться незначительно странноватым:

def _serialize_byte(out, value, set_type):
if set_type:
out.extend(bytearray([98]))

value = ((value + ((1 << 7) — 1)) % (1 << 8)) — ((1 << 7) — 1)
out.extend(struct.pack(‘>b’, value))
Это пример сериализации б, для других типов все аналогично. Ответ прост — схожий код тут для сопоставимости с результатом сериализации в Java.

Ну и напоследок о текущих ограничениях:

Поддерживает лишь TCP протокол
Не поддерживает encrypted соединение
Не поддерживает сбор статистики траффа

Добавить UDP протокол и справить 2 крайних пт было бы замечательно, но пока нет на это времени. Охото надеяться, что мы не единственные, кому это пригодилось и наш труд окажется кому-нибудь полезен. Но исходники открыты, как говорится — все карты в руки.

habrahabr.ru P.S. Раз кто-то отыщет ошибки/некорректности в коде и/либо не нормально написанные участки (чего же я не исключаю ввиду не чрезвычайно долгого знакомства с Python) — буду непомерно благодарен за указание на их.