如何通过重新启动应用程序克服 ModbusTcpServer 中的 "Address already in use"?

How to overcome to "Address already in use" in ModbusTcpServer with restarting app?

描述和代码:

我正在使用带 pymodbus 库的同步 ModbusTcpServer 创建 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() 在客户端连接来解决这个问题,但我想要一个稳定的服务器。


问题:

有没有内置的方法来克服这个问题?我在异步 ModbusTcpServer(async.py)中找到了这个参数(socket.SO_REUSEADDR),但在同步 ModbusTcpServer(sync.py)中没有。


[注意]:

版本

Pymodbus 特定

ModbusTcpServer 派生自 socketserver.ThreadingTCPServer。要重用该地址,您必须显式覆盖 class 变量 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 here

的源代码

[更新]:

您正在混合使用线程和反应器。 twisted 有自己的信号处理程序,这可能是服务器没有按预期退出的原因。顺便说一句,您检查过 updating_server.py 示例了吗?这看起来与您正在做的相似,只是它使用异步服务器。它默认带有重用地址并处理正常终止。

但以防万一您仍想使用您的代码。这是一个处理阻塞程序的丑陋技巧。请注意,在某些情况下,您将不得不预先查看 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()