new generic secop client
new client intended as base class for all clients - based on Ennos secop client in nicos ([WIP] provide secop client) - self healing connection singletons - extension for other than TCP is foreseen (by extending new uri schemes) - extensible name mangling - seperate rx and tx threads supporting events - internal cache - extensible error handling - callback for unhandled messages - callback for descriptive data change - callback for node stat change (connected, disconnected) - a short close down and reconnect without change in descriptive data does not disturb the client side works with secop-gui (change follows), planned to be used for Frappy internal secop proxy and as a replacement for secop.client.baseclient.Client in the nicos secop device. -> secop/client/baseclient.py to be removed after planned changes moved secop/client/__init__.py to secop/client/console.py because secop.client would be the natural place to put the new base class. Change-Id: I1a7b1f1ded2221a8f9fcdd52f9cc7414e8fbe035 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22218 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
140
secop/lib/asynconn.py
Normal file
140
secop/lib/asynconn.py
Normal file
@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""asynchonous connections
|
||||
|
||||
generic class for byte oriented communication
|
||||
includes implementation for TCP connections
|
||||
"""
|
||||
|
||||
import socket
|
||||
import time
|
||||
|
||||
from secop.lib import parseHostPort, tcpSocket, closeSocket
|
||||
|
||||
|
||||
class ConnectionClosed(ConnectionError):
|
||||
pass
|
||||
|
||||
|
||||
class AsynConn:
|
||||
timeout = 1 # inter byte timeout
|
||||
SCHEME_MAP = {}
|
||||
connection = None # is not None, if connected
|
||||
defaultport = None
|
||||
|
||||
def __new__(cls, uri):
|
||||
scheme = uri.split('://')[0]
|
||||
iocls = cls.SCHEME_MAP.get(scheme, None)
|
||||
if not iocls:
|
||||
# try tcp, if scheme not given
|
||||
try:
|
||||
host_port = parseHostPort(uri, cls.defaultport)
|
||||
except (ValueError, TypeError, AssertionError):
|
||||
raise ValueError('invalid uri: %s' % uri)
|
||||
iocls = cls.SCHEME_MAP['tcp']
|
||||
uri = 'tcp://%s:%d' % host_port
|
||||
return object.__new__(iocls)
|
||||
|
||||
def __init__(self, *args):
|
||||
self._rxbuffer = b''
|
||||
|
||||
def __del__(self):
|
||||
self.disconnect()
|
||||
|
||||
@classmethod
|
||||
def register_scheme(cls, scheme):
|
||||
cls.SCHEME_MAP[scheme] = cls
|
||||
|
||||
def disconnect(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def send(self, data):
|
||||
"""send data (bytes!)
|
||||
|
||||
tries to send all data"""
|
||||
raise NotImplementedError
|
||||
|
||||
def recv(self):
|
||||
"""return bytes received within timeout
|
||||
|
||||
in contrast to socket.recv:
|
||||
- returns b'' on timeout
|
||||
- raises ConnectionClosed if the other end has disconnected
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def readline(self, timeout=None):
|
||||
"""read one line
|
||||
|
||||
return either a complete line or None in case of timeout
|
||||
the timeout argument may increase, but not decrease the default timeout
|
||||
"""
|
||||
if timeout:
|
||||
end = time.time() + timeout
|
||||
while b'\n' not in self._rxbuffer:
|
||||
data = self.recv()
|
||||
if not data:
|
||||
if timeout:
|
||||
if time.time() < end:
|
||||
continue
|
||||
raise TimeoutError('timeout in readline')
|
||||
return None
|
||||
self._rxbuffer += data
|
||||
line, self._rxbuffer = self._rxbuffer.split(b'\n', 1)
|
||||
return line
|
||||
|
||||
def writeline(self, line):
|
||||
self.send(line + b'\n')
|
||||
|
||||
|
||||
class AsynTcp(AsynConn):
|
||||
def __init__(self, uri):
|
||||
super().__init__()
|
||||
self.uri = uri
|
||||
if uri.startswith('tcp://'):
|
||||
# should be the case always
|
||||
uri = uri[6:]
|
||||
self.connection = tcpSocket(uri, self.defaultport, self.timeout)
|
||||
|
||||
def disconnect(self):
|
||||
if self.connection:
|
||||
closeSocket(self.connection)
|
||||
self.connection = None
|
||||
|
||||
def send(self, data):
|
||||
"""send data (bytes!)"""
|
||||
self.connection.sendall(data)
|
||||
|
||||
def recv(self):
|
||||
"""return bytes received within 1 sec"""
|
||||
try:
|
||||
data = self.connection.recv(8192)
|
||||
if data:
|
||||
return data
|
||||
except socket.timeout:
|
||||
# timeout while waiting
|
||||
return b''
|
||||
raise ConnectionClosed() # marks end of connection
|
||||
|
||||
AsynTcp.register_scheme('tcp')
|
Reference in New Issue
Block a user