TCP I/O gear
The io_tcp gear is the primary Native Gear for handling raw TCP connections. It serves proprietary, length-prefixed, or stream-based protocols (like ISO8583, healthcare HL7, or raw IoT telemetry) where HTTP is unsuitable.
It handles the "Dirty Work" of networking:
- Connection lifecycle (Listen/Dial, Reconnect, Timeouts).
- Framing (Splitting specific byte streams into discrete messages).
- Idle Timeouts (Automatic session cleanup. Configured by
idle_timeout, default60s). - Link state (client mode): the gear publishes
conn.upwhen its connection opens andconn.downwhen it closes, on the Control Plane atlink.<gear name>. A Conductor whose port ends at this gear uses them to know whether the port can receive traffic. - TLS termination (Server-side & mTLS 1.3 - [Roadmap]).
- Buffers (Read/Write buffering to prevent stalling).
It does NOT parse the payload. It delivers RawPayload (bytes) to the Rack, where a Codec Gear (e.g., ISO8583) turns it into structured data.
| Attribute | Details |
|---|---|
| Source Code | pkg/gears/native/io_tcp |
| Pairs With | Codec Gears (e.g., codec_iso8583) |
| Always Emitted Metadata | conn.id, peer.ip, peer.port |
| Conditionally Emitted Metadata | None |
| Mandatory Consumed Metadata | conn.id (Egress Mode) |
| Optional Consumed Metadata | None |
| Signals Sent | None |
| Signals Subscribed | conn.close (Kill Switch) - [Supported] |
Reference
fluxrig gears doc io_tcp generates the identity, ports, and configuration below from the gear's manifest, so they stay in lockstep with the code.
| Type | io_tcp |
| Category | io |
| Status | stable |
| Terminus | io |
Generic TCP I/O with delimiter/length framing. Bridges one bidirectional socket onto two unidirectional ports.
Ports
| Port | Direction | Role | Summary |
|---|---|---|---|
in | input | egress | Payload to frame and write to the socket. |
out | output | ingress | Deframed payload read from the socket. |
Configuration
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
mode | enum: server, client | yes | - | server (listen for connections) or client (dial an upstream). |
bind | string | :8080 | server mode: address to listen on, e.g. ':9000'. | |
connect | string | - | client mode: upstream address to dial, host:port. | |
delimiter | string | - | message delimiter for framing (e.g. a newline, 0x03); if empty, frames on newline (ScanLines). | |
delimiter_append | boolean | false | egress: append the delimiter to outgoing payloads. | |
delimiter_include | boolean | false | ingress: keep the delimiter in the emitted payload. | |
delimiter_position | enum: suffix, prefix | suffix | where the delimiter sits relative to the message: suffix or prefix. | |
framing | enum: delimiter, length_prefix2, length_prefix4 | delimiter | message framing mode: delimiter (newline, etc), length_prefix2 (2-byte BE), length_prefix4 (4-byte BE). | |
idle_timeout | string | 60s | max time a connection may sit idle before it is closed. | |
max_buffered_messages | integer | 1000 | server mode: outbound messages queued when nothing is connected yet, or when a message names no conn.id (0 selects the default). | |
max_connections | integer | 4096 | server mode: maximum concurrent connections (0 = unlimited). | |
read_responses | boolean | true | client mode: whether to read responses from the server. Set to false for one-way send (e.g. to an echo server that doesn't send proper ISO8583 responses). | |
reconnect_wait | string | 5s | client mode: delay before redialing a dropped connection, e.g. '1s'. |
Architecture
IMPORTANT
Wires are unidirectional. Sockets are bidirectional. The gear maps one bidirectional TCP socket onto two unidirectional ports. It deframes bytes received from the socket and emits them on out. It frames messages arriving on in and writes them to the socket. See the architecture overview for the Wire definition and the ISO8583 I/O gear for the mode-by-mode port table.
Configuration
The full field list, with types and defaults, is in the Manifest reference table at the top of this page. In brief:
- Mode
server(listener) acts as a gateway, accepting connections from POS terminals, ATMs, or partners. It requiresbind. - Mode
client(initiator) acts as a connector, dialing a host (e.g. Visa, core banking). It requiresconnect.
Framing strategies
Currently, io_tcp supports Delimiter-based framing.
| Strategy | Config Key | Description |
|---|---|---|
| Delimiter | delimiter | Reads until specific byte (e.g. \n, 0x03). Warning: Does not support escaping. If the payload canonically contains the delimiter, framing will break. Use fixed-length framing for binary protocols. |
| ScanLines | (Default) | If delimiter is empty, breaks on \n (Buffer default). |
Note: A future release will add fixed-length framing (e.g. 2-byte BE).
- name: "tcp-server"
type: "io_tcp"
config:
mode: "server"
bind: ":3034"
delimiter: "\n"
delimiter_include: false
Description
-
Ingress (
receive):- Gear reads bytes from socket.
- Framer detects message boundary (e.g., reads header
00 20-> 32 bytes). - Gear encapsulates the 32 bytes into a
fluxMsg. - It adds metadata:
conn.id,peer.ip,peer.port. - It publishes the message to the configured NATS Subject.
-
Egress (
send):- Gear subscribes to NATS Subject.
- Receives
fluxMsg. extractsconn.idfrom Metadata. - Finds the active socket in the Connection Registry.
- Writes
fluxMsg.RawPayloadto the socket (prefixing Length Header if configured).
Control plane integration (the "kill switch")
This gear subscribes to the Universal Control Plane for error handling. This facilitates the "Kill Switch" pattern.
- Subject:
flux.ctrl.{gear_id} - Command:
conn.close - Purpose: Allows logic/codec gears to force a socket disconnection when they detect a fatal protocol violation (e.g. invalid MTI), even though they don't own the socket.
{
"command": "conn.close",
"payload": {
"conn_id": "uuid-1234",
"reason": "protocol_violation",
"code": "isomsg_pack_fail"
}
}
Rationale & extended info
Observability
The io_tcp gear exports native OpenTelemetry metrics regarding the health of its connection pools.
flux.port.connections_active(Gauge): Tracks currently established sockets per gear.flux.port.bytes_in(Counter): Total bytes ingested from the wire.flux.port.bytes_out(Counter): Total bytes pushed to the wire.flux.gear.messages_in(Counter): Total messages ingested from the wire.flux.gear.messages_out(Counter): Total messages pushed to the wire.flux.gear.errors(Counter): Logs the number of malformed frames or buffer overflows detected.
Resource limits
Unlike higher-level gears, TCP I/O directly bounds to OS resources (File Descriptors).
Currently, the gear relies on both OS-level ulimit -n and an internal max_connections setting. It is strongly recommended to set a high OS limit (e.g., 65535) while using the max_connections field for application-level graceful rejection.
Backpressure handling
The gear maps the TCP window directly to NATS JetStream backpressure.
- Ingress: If load overwhelms the downstream pipeline (NATS), publishing blocks. This fills the internal Go buffers. Full buffers apply backpressure to the TCP read operation. The client's TCP window closes as a result.
- Egress: If the remote client reads too slowly, the outbound socket buffer fills. This applies backpressure to the NATS consumer and prevents memory bloat inside the Rack process.