1

描述和代码:

我正在使用带pymodbus库的同步 ModbusTcpServer 来创建 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 (in ) 中找到了这个参数 ( ),async.py但在 Synchronous ModbusTcpServer ( sync.py) 中没有。


[注意]:

版本

  • 蟒蛇:3.6.5
  • 操作系统:Ubuntu 16.04
  • Pymodbus:1.5.2
  • Modbus 硬件(如果使用):否

Pymodbus 特定的

  • 服务器:tcp - 同步
  • 客户端:tcp - 同步
4

1 回答 1

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 有自己的信号处理程序,这可能是服务器没有按预期退出的原因。顺便说一句,您检查了updates_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()
于 2018-08-29T04:41:16.957 回答