def __init__(self, resolved_name, recv_data_class, queue_size=None, buff_size=DEFAULT_BUFF_SIZE): """ ctor @param resolved_name: resolved service or topic name @type resolved_name: str @param recv_data_class: message class for deserializing inbound messages @type recv_data_class: Class @param queue_size: maximum number of inbound messages to maintain @type queue_size: int @param buff_size: receive buffer size (in bytes) for reading from the connection. @type buff_size: int """ if recv_data_class and not issubclass(recv_data_class, Message): raise TransportInitError( "Unable to initialize transport: data class is not a message data class" ) self.resolved_name = resolved_name self.recv_data_class = recv_data_class self.queue_size = queue_size self.buff_size = buff_size self.direction = BIDIRECTIONAL
def __init__(self, protocol, name, header=None): """ ctor @param name str: identifier @param protocol TCPROSTransportProtocol protocol implementation @param header dict: (optional) handshake header if transport handshake header was already read off of transport. @raise TransportInitError if transport cannot be initialized according to arguments """ super(TCPROSTransport, self).__init__(protocol.direction, name=name) if not name: raise TransportInitError( "Unable to initialize transport: name is not set") self.protocol = protocol self.socket = None self.endpoint_id = 'unknown' self.read_buff = cStringIO.StringIO() self.write_buff = cStringIO.StringIO() self.header = header # #1852 have to hold onto latched messages on subscriber side self.is_latched = False self.latch = None # these fields are actually set by the remote # publisher/service. they are set for tools that connect # without knowing the actual field name self.md5sum = None self.type = None
def connect(self, dest_addr, dest_port, endpoint_id, timeout=None): """ Establish TCP connection to the specified address/port. connect() always calls L{write_header()} and L{read_header()} after the connection is made @param dest_addr: destination IP address @type dest_addr: str @param dest_port: destination port @type dest_port: int @param endpoint_id: string identifier for connection (for statistics) @type endpoint_id: str @param timeout: (optional keyword) timeout in seconds @type timeout: float @raise TransportInitError: if unable to create connection """ try: self.endpoint_id = endpoint_id self.dest_address = (dest_addr, dest_port) if rosgraph.network.use_ipv6(): s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if _is_use_tcp_keepalive(): # OSX (among others) does not define these options if hasattr(socket, 'TCP_KEEPCNT') and \ hasattr(socket, 'TCP_KEEPIDLE') and \ hasattr(socket, 'TCP_KEEPINTVL'): # turn on KEEPALIVE s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # - # keepalive failures before actual connection failure s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 9) # - timeout before starting KEEPALIVE process s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 60) # - interval to send KEEPALIVE after IDLE timeout s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 10) if timeout is not None: s.settimeout(timeout) self.socket = s logdebug('connecting to ' + str(dest_addr) + ' ' + str(dest_port)) self.socket.connect((dest_addr, dest_port)) self.write_header() self.read_header() self.local_endpoint = self.socket.getsockname() self.remote_endpoint = (dest_addr, dest_port) except TransportInitError as tie: rospyerr( "Unable to initiate TCP/IP socket to %s:%s (%s): %s" % (dest_addr, dest_port, endpoint_id, traceback.format_exc())) raise except Exception as e: #logerr("Unknown error initiating TCP/IP socket to %s:%s (%s): %s"%(dest_addr, dest_port, endpoint_id, str(e))) rospywarn( "Unknown error initiating TCP/IP socket to %s:%s (%s): %s" % (dest_addr, dest_port, endpoint_id, traceback.format_exc())) # FATAL: no reconnection as error is unknown self.close() raise TransportInitError(str(e)) #re-raise i/o error
def _validate_header(self, header): """ Validate header and initialize fields accordingly @param header: header fields from publisher @type header: dict @raise TransportInitError: if header fails to validate """ self.header = header if 'error' in header: raise TransportInitError("remote error reported: %s"%header['error']) for required in ['md5sum', 'type']: if not required in header: raise TransportInitError("header missing required field [%s]"%required) self.md5sum = header['md5sum'] self.callerid_pub = header['callerid'] self.type = header['type'] if header.get('latching', '0') == '1': self.is_latched = True
def _tcp_server_callback(self, sock, client_addr): """ TCPServer callback: detects incoming topic or service connection and passes connection accordingly @param sock: socket connection @type sock: socket.socket @param client_addr: client address @type client_addr: (str, int) @raise TransportInitError: If transport cannot be succesfully initialized """ #TODOXXX:rewrite this logic so it is possible to create TCPROSTransport object first, set its protocol, #and then use that to do the writing try: buff_size = 4096 # size of read buffer if python3 == 0: #initialize read_ros_handshake_header with BytesIO for Python 3 (instead of bytesarray()) header = read_ros_handshake_header(sock, StringIO(), buff_size) else: header = read_ros_handshake_header(sock, BytesIO(), buff_size) if 'topic' in header: err_msg = self.topic_connection_handler( sock, client_addr, header) elif 'service' in header: err_msg = self.service_connection_handler( sock, client_addr, header) else: err_msg = 'no topic or service name detected' if err_msg: # shutdown race condition: nodes that come up and down # quickly can receive connections during teardown. # We use is_shutdown_requested() because we can get # into bad connection states during client shutdown # hooks. if not rospy.core.is_shutdown_requested(): write_ros_handshake_header(sock, {'error': err_msg}) raise TransportInitError( "Could not process inbound connection: " + err_msg + str(header)) else: write_ros_handshake_header(sock, {'error': 'node shutting down'}) return except rospy.exceptions.TransportInitError as e: logwarn(str(e)) if sock is not None: sock.close() except Exception as e: # collect stack trace separately in local log file if not rospy.core.is_shutdown_requested(): logwarn("Inbound TCP/IP connection failed from '%s': %s", str(client_addr), e) rospyerr("Inbound TCP/IP connection failed from '%s':\n%s", str(client_addr), traceback.format_exc()) if sock is not None: sock.close()
def set_socket(self, sock, endpoint_id): """ Set the socket for this transport @param sock: socket @type sock: socket.socket @param endpoint_id: identifier for connection endpoint @type endpoint_id: str @raise TransportInitError: if socket has already been set """ if self.socket is not None: raise TransportInitError("socket already initialized") self.socket = sock self.endpoint_id = endpoint_id
def write_data(self, data): """ Write raw data to transport @raise TransportInitialiationError: could not be initialized @raise TransportTerminated: no longer open for publishing """ if not self.socket: raise TransportInitError( "TCPROS transport was not successfully initialized") if self.done: raise TransportTerminated("connection closed") try: #TODO: get rid of sendalls and replace with async-style publishing self.socket.sendall(data.encode()) self.stat_bytes += len(data) self.stat_num_msg += 1 except IOError as xxx_todo_changeme: #for now, just document common errno's in code (errno, msg) = xxx_todo_changeme.args #for now, just document common errno's in code if errno == 32: #broken pipe logdebug("ERROR: Broken Pipe") self.close() raise TransportTerminated(str(errno) + msg) raise #re-raise except socket.error as xxx_todo_changeme1: #for now, just document common errno's in code (errno, msg) = xxx_todo_changeme1.args #for now, just document common errno's in code if errno == 32: #broken pipe logdebug("[%s]: Closing connection [%s] due to broken pipe", self.name, self.endpoint_id) self.close() raise TransportTerminated(msg) elif errno == 104: #connection reset by peer logdebug("[%s]: Peer [%s] has closed connection", self.name, self.endpoint_id) self.close() raise TransportTerminated(msg) else: rospydebug("unknown socket error writing data: %s", traceback.format_exc()) logdebug( "[%s]: closing connection [%s] due to unknown socket error: %s", self.name, self.endpoint_id, msg) self.close() raise TransportTerminated(str(errno) + ' ' + msg) #except: #TODO: try to figure out common errors here #raise return True
def __init__(self, protocol, name, header=None): """ ctor @param name str: identifier @param protocol TCPROSTransportProtocol protocol implementation @param header dict: (optional) handshake header if transport handshake header was already read off of transport. @raise TransportInitError if transport cannot be initialized according to arguments """ super(TCPROSTransport, self).__init__(protocol.direction, name=name) if not name: raise TransportInitError( "Unable to initialize transport: name is not set") self.protocol = protocol self.socket = None self.endpoint_id = 'unknown' self.callerid_pub = 'unknown' self.dest_address = None # for reconnection if python3 == 0: # Python 2.x self.read_buff = StringIO() self.write_buff = StringIO() else: # Python 3.x self.read_buff = BytesIO() self.write_buff = BytesIO() #self.write_buff = StringIO() self.header = header # #1852 have to hold onto latched messages on subscriber side self.is_latched = False self.latch = None # save the fileno separately so we can garbage collect the # socket but still unregister will poll objects self._fileno = None # these fields are actually set by the remote # publisher/service. they are set for tools that connect # without knowing the actual field name self.md5sum = None self.type = None # Endpoint Details (IP, Port) self.local_endpoint = (None, None) self.remote_endpoint = (None, None)
def _tcp_server_callback(self, sock, client_addr): """ TCPServer callback: detects incoming topic or service connection and passes connection accordingly @param sock: socket connection @type sock: socket.socket @param client_addr: client address @type client_addr: (str, int) @raise TransportInitError: If transport cannot be succesfully initialized """ #TODOXXX:rewrite this logic so it is possible to create TCPROSTransport object first, set its protocol, #and then use that to do the writing try: buff_size = 4096 # size of read buffer header = read_ros_handshake_header(sock, cStringIO.StringIO(), buff_size) if 'topic' in header: err_msg = self.topic_connection_handler( sock, client_addr, header) elif 'service' in header: err_msg = self.service_connection_handler( sock, client_addr, header) else: err_msg = 'no topic or service name detected' if err_msg: # shutdown race condition: nodes that come up and down quickly can receive connections during teardown if not rospy.core.is_shutdown(): write_ros_handshake_header(sock, {'error': err_msg}) raise TransportInitError( "Could not process inbound connection: " + err_msg + str(header)) else: write_ros_handshake_header(sock, {'error': 'node shutting down'}) return except rospy.exceptions.TransportInitError, e: logwarn(str(e)) if sock is not None: sock.close()
def write_data(self, data): """ Write raw data to transport @raise TransportInitialiationError: could not be initialized @raise TransportTerminated: no longer open for publishing """ if not self.socket: raise TransportInitError( "TCPROS transport was not successfully initialized") if self.done: raise TransportTerminated("connection closed") try: #TODO: get rid of sendalls and replace with async-style publishing self.socket.sendall(data) self.stat_bytes += len(data) self.stat_num_msg += 1 except IOError, (errno, msg): #for now, just document common errno's in code if errno == 32: #broken pipe logdebug("ERROR: Broken Pipe") self.close() raise TransportTerminated(str(errno) + msg) raise #re-raise
def connect(self, dest_addr, dest_port, endpoint_id, timeout=None): """ Establish TCP connection to the specified address/port. connect() always calls L{write_header()} and L{read_header()} after the connection is made @param dest_addr: destination IP address @type dest_addr: str @param dest_port: destination port @type dest_port: int @param endpoint_id: string identifier for connection (for statistics) @type endpoint_id: str @param timeout: (optional keyword) timeout in seconds @type timeout: float @raise TransportInitError: if unable to create connection """ # first make sure that if ROS_HOSTNAME=localhost, we will not attempt # to connect to anything other than localhost if ("ROS_HOSTNAME" in os.environ) and (os.environ["ROS_HOSTNAME"] == "localhost"): if not rosgraph.network.is_local_address(dest_addr): msg = "attempted to connect to non-local host [%s] from a node launched with ROS_HOSTNAME=localhost" % (dest_addr) logwarn(msg) self.close() raise TransportInitError(msg) # bubble up # now we can proceed with trying to connect. try: self.endpoint_id = endpoint_id self.dest_address = (dest_addr, dest_port) if rosgraph.network.use_ipv6(): s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if _is_use_tcp_keepalive(): # OSX (among others) does not define these options if hasattr(socket, 'TCP_KEEPCNT') and \ hasattr(socket, 'TCP_KEEPIDLE') and \ hasattr(socket, 'TCP_KEEPINTVL'): # turn on KEEPALIVE s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # - # keepalive failures before actual connection failure s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 9) # - timeout before starting KEEPALIVE process s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 60) # - interval to send KEEPALIVE after IDLE timeout s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 10) if timeout is not None: s.settimeout(timeout) self.socket = s logdebug('connecting to ' + str(dest_addr)+ ' ' + str(dest_port)) self.socket.connect((dest_addr, dest_port)) self.write_header() self.read_header() self.local_endpoint = self.socket.getsockname() self.remote_endpoint = (dest_addr, dest_port) except TransportInitError as tie: rospyerr("Unable to initiate TCP/IP socket to %s:%s (%s): %s"%(dest_addr, dest_port, endpoint_id, traceback.format_exc())) raise except Exception as e: #logerr("Unknown error initiating TCP/IP socket to %s:%s (%s): %s"%(dest_addr, dest_port, endpoint_id, str(e))) rospywarn("Unknown error initiating TCP/IP socket to %s:%s (%s): %s"%(dest_addr, dest_port, endpoint_id, traceback.format_exc())) # check for error type and reason. On unknown errors the socket will be closed # to avoid reconnection and error reproduction if not isinstance(e, socket.error): # FATAL: no reconnection as error is unknown self.close() elif not isinstance(e, socket.timeout) and e.errno not in [ errno.ENETDOWN, errno.ENETUNREACH, errno.ENETRESET, errno.ECONNABORTED, errno.ETIMEDOUT, errno.EHOSTDOWN, errno.EHOSTUNREACH]: # reconnect in follow cases, otherwise close the socket: # 1. socket.timeout: on timeouts caused by delays on wireless links # 2. ENETDOWN (100), ENETUNREACH (101), ENETRESET (102), ECONNABORTED (103): # while using ROS_HOSTNAME ros binds to a specific interface. Theses errors # are thrown on interface shutdown e.g. on reconnection in LTE networks # 3. ETIMEDOUT (110): same like 1. (for completeness) # 4. EHOSTDOWN (112), EHOSTUNREACH (113): while network and/or DNS-server is not reachable # # no reconnection as error is not 1.-4. self.close() raise TransportInitError(str(e)) #re-raise i/o error
class TCPROSTransport(Transport): """ Generic implementation of TCPROS exchange routines for both topics and services """ transport_type = 'TCPROS' def __init__(self, protocol, name, header=None): """ ctor @param name str: identifier @param protocol TCPROSTransportProtocol protocol implementation @param header dict: (optional) handshake header if transport handshake header was already read off of transport. @raise TransportInitError if transport cannot be initialized according to arguments """ super(TCPROSTransport, self).__init__(protocol.direction, name=name) if not name: raise TransportInitError( "Unable to initialize transport: name is not set") self.protocol = protocol self.socket = None self.endpoint_id = 'unknown' self.read_buff = cStringIO.StringIO() self.write_buff = cStringIO.StringIO() self.header = header # #1852 have to hold onto latched messages on subscriber side self.is_latched = False self.latch = None # these fields are actually set by the remote # publisher/service. they are set for tools that connect # without knowing the actual field name self.md5sum = None self.type = None def set_socket(self, sock, endpoint_id): """ Set the socket for this transport @param sock: socket @type sock: socket.socket @param endpoint_id: identifier for connection endpoint @type endpoint_id: str @raise TransportInitError: if socket has already been set """ if self.socket is not None: raise TransportInitError("socket already initialized") self.socket = sock self.endpoint_id = endpoint_id def connect(self, dest_addr, dest_port, endpoint_id, timeout=None): """ Establish TCP connection to the specified address/port. connect() always calls L{write_header()} and L{read_header()} after the connection is made @param dest_addr: destination IP address @type dest_addr: str @param dest_port: destination port @type dest_port: int @param endpoint_id: string identifier for connection (for statistics) @type endpoint_id: str @param timeout: (optional keyword) timeout in seconds @type timeout: float @raise TransportInitError: if unable to create connection """ try: self.endpoint_id = endpoint_id s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if _is_use_tcp_keepalive(): # OSX (among others) does not define these options if hasattr(socket, 'TCP_KEEPCNT') and \ hasattr(socket, 'TCP_KEEPIDLE') and \ hasattr(socket, 'TCP_KEEPINTVL'): # turn on KEEPALIVE s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # - # keepalive failures before actual connection failure s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 9) # - timeout before starting KEEPALIVE process s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 60) # - interval to send KEEPALIVE after IDLE timeout s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 10) if timeout is not None: s.settimeout(timeout) self.socket = s self.socket.connect((dest_addr, dest_port)) self.write_header() self.read_header() except TransportInitError, tie: rospyerr( "Unable to initiate TCP/IP socket to %s:%s (%s): %s" % (dest_addr, dest_port, endpoint_id, traceback.format_exc())) raise except Exception, e: self.done = True if self.socket: self.socket.close() self.socket = None #logerr("Unknown error initiating TCP/IP socket to %s:%s (%s): %s"%(dest_addr, dest_port, endpoint_id, str(e))) rospywarn( "Unknown error initiating TCP/IP socket to %s:%s (%s): %s" % (dest_addr, dest_port, endpoint_id, traceback.format_exc())) raise TransportInitError(str(e)) #re-raise i/o error