Как побороть «Адрес уже используется» в ModbusTcpServer с перезапуском приложения? - PullRequest
0 голосов
/ 28 августа 2018

Описание и код:

Я использую Synchronous ModbusTcpServer с библиотекой pymodbus для создания ведомого / сервера Modbus, вот код:

from pymodbus.server.sync import StartTcpServer, ModbusTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
import threading
import logging

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def run_server():
    block1 = ModbusSequentialDataBlock(0x00, [717] * 0x0F)
    block2 = ModbusSequentialDataBlock(0x10, [323] * 0x1F)
    store2 = ModbusSlaveContext(hr=block1, ir=block2)

    slaves = {
        0x01: store2,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/riptideio/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    interval = 2
    server = ModbusTcpServer(context,
                             identity=identity,
                             address=('0.0.0.0', 5021))  # Problem cause.
    thread_ = threading.Thread(target=server.serve_forever, daemon=True)
    thread_.start()
    loop = LoopingCall(f=update_values, a=server)
    loop.start(interval, now=True)
    reactor.run()


def update_values(a):
    print("-----------START-----------")
    rfuncode = 3
    wfuncode = 16
    slave_id = 0x01
    address = 0x00
    context_ = a.context[slave_id]
    values = context_.getValues(rfuncode, address, count=32)
    print(values)
    values = [val+1 for val in values]
    context_.setValues(wfuncode, address, values)
    print("------------END------------")


if __name__ == "__main__":
    run_server()

Когда клиентское приложение подключается к этому серверу, и когда я закрываю этот код (с помощью Ctrl + C ) и снова запускаюсь, возникает эта ошибка:

OSError: [Errno 98] Address already in use Я знаю, что при создании сокетов мы можем использовать socket.SO_REUSEADDR для преодоления этой проблемы.

Кроме того, я могу .close() установить соединение на стороне клиента для решения этой проблемы, но мне нужен стабильный сервер.


Вопрос:

Есть ли встроенный способ преодолеть это? Я нахожу этот аргумент (socket.SO_REUSEADDR) в асинхронном ModbusTcpServer (в async.py), но его нет в синхронном ModbusTcpServer (sync.py).


[ Примечание ]:

Версия

  • Python: 3.6.5
  • ОС: Ubuntu 16.04
  • Pymodbus: 1.5.2
  • Аппаратное обеспечение Modbus (если используется): нет

Pymodbus Specific

  • Сервер: tcp - sync
  • Клиент: tcp - sync

1 Ответ

0 голосов
/ 29 августа 2018

ModbusTcpServer является производным от socketserver.ThreadingTCPServer. Для повторного использования адреса вам придется явно переопределить переменную класса allow_resuse_address.

class ReusableModbusTcpServer(ModbusTcpServer):

    def __init__(self, context, framer=None, identity=None,
                 address=None, handler=None, **kwargs):
        self.allow_reuse_address = True
        ModbusTcpServer.__init__(self, context, framer, identity, address, handler, **kwargs)

Для получения дополнительной информации обратитесь к исходному коду socketserver здесь


[ UPDATE ]:

Вы смешиваете нити и реактор. А в twisted есть свои собственные обработчики сигналов, которые могут быть причиной того, что сервер не выходит из системы, как ожидалось. Кстати, вы проверили пример update_server.py ? Это похоже на то, что вы делаете, за исключением того, что использует сервер Async. Он поставляется с повторным использованием адреса по умолчанию и обрабатывает изящные завершения.

Но если вы все еще хотите использовать свой код. Вот уродливый взломать, чтобы иметь дело с программой блокировки. Обратите внимание, что в некоторых случаях вам придется дважды просмотреть Ctrl + C `, и вы увидите некрасивые следы обратной связи от модуля потоков.

from pymodbus.server.sync import StartTcpServer, ModbusTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
import threading
import logging
import signal
import time

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

SERVER = None
THREADED_SERVER = None
LOOP = None

class ReusableModbusTcpServer(ModbusTcpServer):
    def __init__(self, context, framer=None, identity=None,
                 address=None, handler=None, **kwargs):
        self.allow_reuse_address = True
        ModbusTcpServer.__init__(self, context, framer, identity, address, handler, **kwargs)

class ThreadedModbusServer(threading.Thread):
    def __init__(self, server):
        super(ThreadedModbusServer, self).__init__(name="ModbusServerThread")
        self._server = server
        self.daemon = True

    def run(self):
        self._server.serve_forever()

    def stop(self):
        if isinstance(self._server, ModbusTcpServer):
            self._server.shutdown()
        else:
            if self._server.socket:
                self._server.server_close()


def update_values(a):
    print("-----------START-----------")
    rfuncode = 3
    wfuncode = 16
    slave_id = 0x01
    address = 0x00
    context_ = a.context[slave_id]
    values = context_.getValues(rfuncode, address, count=32)
    print(values)
    values = [val+1 for val in values]
    context_.setValues(wfuncode, address, values)
    print("------------END------------")
    time.sleep(0.1)

def run_server():
    global SERVER, THREADED_SERVER, LOOP
    block1 = ModbusSequentialDataBlock(0x00, [717] * 0x0F)
    block2 = ModbusSequentialDataBlock(0x10, [323] * 0x1F)
    store2 = ModbusSlaveContext(hr=block1, ir=block2)

    slaves = {
        0x01: store2,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/riptideio/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    interval = 2
    SERVER = ReusableModbusTcpServer(context,
                             identity=identity,
                             address=('0.0.0.0', 5021))  # Problem cause.
    THREADED_SERVER = ThreadedModbusServer(SERVER)
    THREADED_SERVER.start()
    LOOP = LoopingCall(f=update_values, a=SERVER)
    LOOP.start(interval, now=True)
    reactor.run()

def signal_handler(signal, frame):
    global THREADED_SERVER, LOOP
    log.warning("You pressed Ctrl+C! ."
              "If the program does not quit, Try pressing the CTRL+C again!!!")
    if THREADED_SERVER:
        THREADED_SERVER.stop()
        THREADED_SERVER = None
    if LOOP:
        LOOP.stop()
        LOOP = None
    if reactor.running:
        reactor.stop()
    else:
        exit(1)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal_handler)
    run_server()
...