Transports - the only place USB/IP addressing (host/port/socket) lives.
The top layer never names these directly: device.plug() and host.open() use a
default transport unless you pass via=/transport=.
USBIP(host, port) - real USB/IP over TCP (kernel-compatible)
Loopback() - in-process socketpair; same protocol code, no network
USBIP
USBIP(host=None, port=3240)
Bases: Transport
Real USB/IP over TCP. Serving binds a usbipd-compatible listener;
connecting attaches like the kernel client / usbip attach does.
Source code in usbip/transport.py
| def __init__(self, host=None, port=3240):
self.host = host
self.port = port
self._listener = None
|
serve
Source code in usbip/transport.py
| def serve(self, dev):
listener = _listener_for(self.host, self.port)
self._listener = listener
if not listener.register(dev):
return # already bound; dev just joined it
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
srv.bind((self.host or "0.0.0.0", self.port))
srv.listen(4)
except OSError:
srv.close()
listener.unregister(dev)
_drop_listener(listener)
raise
listener.socks.append(srv)
def accept_loop():
while True:
try:
conn, _addr = srv.accept()
except OSError:
break
# Tiny request/response messages, and write_ret() sends header+data
# in separate sendall()s; without TCP_NODELAY, Nagle plus the peer's
# delayed-ACK stalls each IN reply ~40-250 ms.
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# the handshake picks which of these the connection imported
threading.Thread(
target=protocol.serve_connection, args=(conn, listener.snapshot()), daemon=True
).start()
threading.Thread(target=accept_loop, daemon=True).start()
|
connect
Source code in usbip/transport.py
| def connect(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.host or "127.0.0.1", self.port))
return sock
|
stop
Source code in usbip/transport.py
| def stop(self, dev=None):
listener = self._listener
if listener is None:
return
if dev is not None and not listener.unregister(dev):
return # other devices still on this listener
_drop_listener(listener)
self._listener = None
|
Loopback
Bases: Transport
Device and host in one process, wired by a socketpair (no kernel/network).
Each connect() mints its own socketpair and serves the listener's devices on
it, so several devices - and several sequential opens - behave exactly as
they do over TCP.
Source code in usbip/transport.py
| def __init__(self):
self._listener = _Listener() # private: loopback has no address to share
|
serve
Source code in usbip/transport.py
| def serve(self, dev):
self._listener.register(dev)
|
connect
Source code in usbip/transport.py
| def connect(self):
dev_sock, host_sock = socket.socketpair()
# keep only live sockets: an open/close cycle per connect() must not pile up
self._listener.socks = [sock for sock in self._listener.socks if sock.fileno() != -1]
self._listener.socks += [dev_sock, host_sock]
threading.Thread(
target=protocol.serve_connection,
args=(dev_sock, self._listener.snapshot()),
daemon=True,
).start()
return host_sock
|
stop
Source code in usbip/transport.py
| def stop(self, dev=None):
if dev is not None and not self._listener.unregister(dev):
return # other devices still on this listener
self._listener.close()
|
Transport
serve
Source code in usbip/transport.py
| def serve(self, dev): # device side
raise NotImplementedError
|
connect
Source code in usbip/transport.py
| def connect(self): # host side -> connected socket
raise NotImplementedError
|
stop
Source code in usbip/transport.py
| def stop(self, dev=None):
pass
|
use
Set the process-wide default transport.
Source code in usbip/transport.py
| def use(transport):
"""Set the process-wide default transport."""
global _default
_default = transport
|