Как лучше всего обрабатывать полученные сообщения в Twisted для Python?

Я новичок в Python и нуждаюсь в помощи с архитектурой. Вот моя установка: у меня есть устаревшее клиентское приложение, написанное на LiveCode, которое работает в нескольких местах для отображения синхронизированной информации в зависимости от требований сервера. Думайте об этом как о киоске. Эта клиентская часть никуда не денется.

Серверное приложение - это то, что я переписываю на Python. Моя цель состоит в том, чтобы серверное приложение работало постоянно, прослушивая соединения клиентских сокетов и отправляя / получая данные этим клиентам / от них. Я успешно передал сообщения между этим клиентским приложением LiveCode и скриптом python, который использует Twisted для обработки сокетов, поэтому теперь мне нужно начать обработку этих сообщений. Код выглядит примерно так:

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor

class MessageListener(LineReceiver):

    def __init__(self, users):
        self.users = users
        self.name = None

    def connectionMade(self):
        d = self.transport.getHost()
        print("Connection established with {}:{}".format(d.host, d.port))

    def connectionLost(self, reason):
        print("Connection lost: {}".format(reason))
        if self.name in self.users:
            del self.users[self.name]

    def dataReceived(self, line):
        d = self.transport.getHost()
        print("Received message from {}:{}...{}".format(d.host, d.port, line))
        self.handle_GOTDATA(line)

    def handle_GOTDATA(self, msg):
        #convert "msg" to string and parse it into the necessary chunks

        #*****Go do something based on the requestor and the command*****
        #Use if-elif or dictionary to determine which function to run
        #based on what the string tells us.
        #Should these functions be defined here or in a separate class?

class MessageListenerFactory(Factory):

    def __init__(self):
        self.users = {} # maps user names to Chat instances

    def buildProtocol(self, addr):
        return MessageListener(self.users)

reactor.listenTCP(50010, MessageListenerFactory())

reactor.run()

Пара вопросов:

  1. Функция handle_GOTDATA () - это то место, где я беру полученное сообщение, разбираю его на фрагменты, которые говорят мне, что делать с данными, а затем вызываю другую функцию в зависимости от того, что нужно сделать с этими данными. Должен ли я просто определить все 20 этих функций в одном и том же классе «MessageListener» или написать отдельный класс для хранения всех этих функций? Я могу получить 10 сообщений одновременно, и им может потребоваться вызвать одну и ту же функцию, поэтому я не был уверен, что здесь лучший архитектурный подход.

  2. Я также хочу создать графический интерфейс для взаимодействия с сервером для устранения неполадок и мониторинга. Я знаком с Tkinter, и это было бы хорошо для этого, и я могу написать графический интерфейс в отдельном файле и подключить его к серверу через сокет. Но могу ли я использовать тот же прослушиватель сокетов, реализованный выше, и просто передавать ему похожие сообщения? Или мне следует создать отдельный класс и фабрику для прослушивания подключений графического интерфейса?


person Stuart    schedule 22.04.2020    source источник


Ответы (1)


Если вы собираетесь использовать LineReceiver, не следует переопределять dataReceived. Вместо этого замените lineReceived. Если ваш протокол не ориентирован на линию, вы, вероятно, не захотите использовать LineReceiver. Также в любом случае вы можете рассмотреть возможность использования интерфейса Tubes.

Должен ли я просто определить все 20 этих функций в одном и том же классе «MessageListener» или написать отдельный класс для хранения всех этих функций?

Вам, вероятно, следует отнести их к другому классу. Если вы разместите их на MessageListener, вам будет труднее тестировать, рефакторинг и поддерживать код, потому что логика вашего протокола тесно связана с логикой вашего приложения.

Определите явный интерфейс, который MessageListener использует для отправки событий высокого уровня, представляющих сетевые действия. Затем реализуйте этот интерфейс соответствующим образом для вашего конкретного приложения. Позже вы можете реализовать его по-другому для другого приложения. Или вы можете написать тестовые двойники. Или вы можете изменить свой протокол, не изменяя логику приложения. Разделение двух частей дает вам большую гибкость по сравнению с объединением их обоих в один класс.

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

Я думаю, что это ортогонально, но если это достаточно важная часть вашего протокола или логики приложения, вы можете подумать о создании какой-то векторизации в явном интерфейсе, о котором я упоминал выше. Вместо appObj.doOneThing(oneThing), возможно, у вас appObj.doSeveralThings([oneThing, anotherThing]).

Я также хочу создать графический интерфейс для взаимодействия с сервером для устранения неполадок и мониторинга. Я знаком с Tkinter, и это было бы хорошо для этого, и я могу написать графический интерфейс в отдельном файле и подключить его к серверу через сокет. Но могу ли я использовать тот же прослушиватель сокетов, реализованный выше, и просто передавать ему похожие сообщения? Или мне следует создать отдельный класс и фабрику для прослушивания подключений графического интерфейса?

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

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

person Jean-Paul Calderone    schedule 23.04.2020
comment
Когда вы сказали, что я не должен переопределять dataReceived, что вы имели в виду? Сообщения, которые отправляются в мое существующее клиентское приложение, представляют собой строку, отформатированную с помощью SOF и EOF. lineReceived не будет их интерпретировать, я думаю, потому что он был написан для IRC и ожидал \ r \ n в сообщении? В любом случае, метод dataReceived отлично улавливает сообщения, и это был единственный способ заставить его работать. То же самое было и с методом sendLine (не включенным в мой фрагмент кода). Мне пришлось отказаться от него, потому что он встраивал \ r \ n. Я использую transport.write, и он прекрасно им передается. - person Stuart; 23.04.2020
comment
Полная и единственная цель LineReceiver - lineReceived и sendLine. Если вам не нужен ни один из них, тогда вам нужен Protocol в качестве базового класса. - person Jean-Paul Calderone; 24.04.2020
comment
О да. Имеет смысл. Просто изменил это на Протокол и работает нормально. Спасибо. - person Stuart; 24.04.2020