我正在尝试在扭曲的应用程序中使用recently introduced twisted.application.internet.ClientService
类,该应用程序使用pymodbus
执行简单的modbus-tcp轮询。我觉得我的问题与我正在使用的modbus协议无关,因为我使用较低级别的扭曲API创建了许多其他工作原型;但是这个新的ClientService
看起来完全符合我的需求,因此应该减少我的代码占用空间并保持整洁,如果我可以让它工作。
我的测试显示ClientService
正如预期的那样处理重新连接,并且我可以轻松访问第一个连接Protocol
。我遇到的问题是获取重新连接的后续Protocol
个对象。以下是我遇到问题的代码的简化版本:
from twisted.application import internet, service
from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor, endpoints
from pymodbus.client.async import ModbusClientProtocol
class ModbusPollingService(internet.ClientService):
def __init__(self, addrstr, numregs=5):
self.numregs=numregs
internet.ClientService.__init__(self,
endpoints.clientFromString(reactor, addrstr),
ClientFactory.forProtocol(ModbusClientProtocol))
def startService(self):
internet.ClientService.startService(self)
self._pollWhenConnected()
def _pollWhenConnected(self):
d = self.whenConnected()
d.addCallback(self._connected)
d.addErrback(self._connfail)
def _connected(self, p):
self._log.debug("connected: {p}", p=p)
self._mbp = p
self._poll()
return True
def _connfail(self, failstat):
self._log.failure('connection failure', failure=failstat)
self._mbp = None
self._pollWhenConnected()
def _poll(self):
self._log.debug("poll: {n}", n=self.numregs)
d = self._mbp.read_holding_registers(0, self.numregs)
d.addCallback(self._regs)
d.addErrback(self._connfail)
def _regs(self, res):
self._log.debug("regs: {r}", r=res.registers)
# Do real work of dealing storing registers here
reactor.callLater(1, self._poll)
return res
application = service.Application("ModBus Polling Test")
mbpollsvc = ModbusPollingService('tcp:127.0.0.1:502')
mbpollsvc.setServiceParent(application)
当连接失败(无论出于何种原因)时,errback
从deferred
返回的read_holding_registers()
被调用,目的是我的服务可以放弃Protocol
并继续回到等待Protocol
回调返回的新连接whenConnected()
的状态...然而,似乎正在发生的事情是ClientService尚未意识到连接已经死亡并且返回我相同的断开协议,给我一个完整的日志:
2016-05-05 17:28:25-0400 [-] connected: <pymodbus.client.async.ModbusClientProtocol object at 0x000000000227b558>
2016-05-05 17:28:25-0400 [-] poll: 5
2016-05-05 17:28:25-0400 [-] connection failure
Traceback (most recent call last):
Failure: pymodbus.exceptions.ConnectionException: Modbus Error: [Connection] Client is not connected
2016-05-05 17:28:25-0400 [-] connected: <pymodbus.client.async.ModbusClientProtocol object at 0x000000000227b558>
2016-05-05 17:28:25-0400 [-] poll: 5
2016-05-05 17:28:25-0400 [-] connection failure
Traceback (most recent call last):
Failure: pymodbus.exceptions.ConnectionException: Modbus Error: [Connection] Client is not connected
或非常相似,请注意重复的ModbusClientProtocol对象地址。
我很确定我可能只是为这个API做出了糟糕的模式选择,但我已经尝试了几种不同的可能性,例如创建我自己的Protocol
和Factory
基于ModbusClientProtocol
并完全在该类中处理轮询机制;但是通过持久性配置和机制来存储轮询数据这感觉有点混乱,似乎在ClientService级别或以上处理这个是一个更干净的方法,但我无法找到保持跟踪的最佳方式目前连接的协议。我想我真正想要的是最佳做法建议,以便在扩展轮询情况下使用ClientService
类。
答案 0 :(得分:1)
这是一个古老的问题。但是,希望它将对其他人有所帮助。
我遇到的问题是无法获得用于重新连接的后续协议对象。
可调用prepareConnection
构造函数的供应ClientService
。它将提供电流连接。
在下面的示例中,MyService
将自身附加到MyFactory
。这样做的主要原因是,MyFactory
可以在MyService
断开连接时通知ClientService
。可能是因为ClientService
在断开连接时呼叫Factory.stopFactory
。
下次ClientService
重新连接时,它将调用提供当前协议实例的prepareConnection
。
(重新连接)ClientService:
# clientservice.py
# twistd -y clientservice.py
from twisted.application import service, internet
from twisted.internet.protocol import Factory
from twisted.internet import endpoints, reactor
from twisted.protocols import basic
from twisted.logger import Logger
class MyProtocol(basic.Int16StringReceiver):
_log = Logger()
def stringReceived(self, data):
self._log.info('Received data from {peer}, data={data}',
peer=self.transport.getPeer(),
data=data)
class MyFactory(Factory):
_log = Logger()
protocol = MyProtocol
def stopFactory(self):
# Let service know that its current connection is stale
self.service.on_connection_lost()
class MyService(internet.ClientService):
def __init__(self, endpoint, factory):
internet.ClientService.__init__(self,
endpoint,
factory,
prepareConnection=self.on_prepare_connection)
factory.service = self # Attach this service to factory
self.connection = None # Future protocol instance
def on_prepare_connection(self, connection):
self.connection = connection # Attach protocol to service
self._log.info('Connected to {peer}',
peer=self.connection.transport.getPeer())
self.send_message('Hello from prepare connection!')
def on_connection_lost(self):
if self.connection is None:
return
self._log.info('Disconnected from {peer}',
peer=self.connection.transport.getPeer())
self.connection = None
def send_message(self, message):
if self.connection is None:
raise Exception('Service is not available')
self.connection.sendString(bytes(message, 'utf-8'))
application = service.Application('MyApplication')
my_endpoint = endpoints.clientFromString(reactor, 'tcp:localhost:22222')
my_factory = MyFactory()
my_service = MyService(my_endpoint, my_factory)
my_service.setServiceParent(application)
从扭曲示例中略微修改了回显服务器:
#!/usr/bin/env python
# echoserv.py
# python echoserv.py
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
from twisted.protocols import basic
### Protocol Implementation
# This is just about the simplest possible protocol
class Echo(basic.Int16StringReceiver):
def stringReceived(self, data):
"""
As soon as any data is received, write it back.
"""
print("Received:", data.decode('utf-8'))
self.sendString(data)
def main():
f = Factory()
f.protocol = Echo
reactor.listenTCP(22222, f)
reactor.run()
if __name__ == '__main__':
main()
答案 1 :(得分:0)
你没有在我可以看到的任何地方调用self.transport.loseConnection()来响应你的轮询,所以只要扭曲可以告诉你,你实际上并没有断开连接。可能以后,当你停止对旧运输做任何事情时,但到那时你已经忘记了事情。