Как побороть "Адрес уже используется" в ModbusTcpServer с перезапуском приложения?
Описание и код:
Я использую Синхронный ModbusTcpServer с pymodbus
библиотека для создания Modbus Slave/Server, вот код:
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 - синхронизация
- Клиент: tcp - sync
1 ответ
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
Вот
[ОБНОВЛЕНИЕ]:
Вы смешиваете потоки и реактор. А в 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()