5  AMQP Connection and Channel Contracts

amqp
messaging
broker
channels

Start with the story: An AMQP application does not open a brand-new network pipe for every message. It keeps one expensive connection open, runs many smaller channels inside it, and lets the broker remember the exchange, queue, and binding map.

5.1 Learning Objectives

After this page, you should be able to:

  • Distinguish an AMQP TCP connection from the channels multiplexed inside it.
  • Explain why topology objects live on the broker rather than inside clients.
  • Use idempotent and passive declarations to make exchanges and queues reviewable.
  • Scope prefetch, acknowledgements, channel errors, and heartbeats correctly.
  • Explain why concurrent publishing threads need separate channels on a shared connection.

5.2 Why This Follows AMQP Core Architecture

AMQP Core Architecture introduces the producer, exchange, binding, queue, and consumer model used for server-side routing. This page tightens the lower-level operating contract underneath that model: one process typically keeps a shared connection, workers use separate channels, durable topology is declared repeatably, and frame ordering remains channel-scoped.

Use it when an IoT gateway publishes telemetry while consuming commands, when a broker sees channel churn or thread-safety errors, when a deployment needs repeatable topology declarations, or when packet captures must separate connection liveness from message acknowledgement.

5.3 One Connection, Many Channels, a Stateful Broker

An AMQP 0-9-1 client opens exactly one TCP connection to the broker and then runs many lightweight channels inside it. A channel is a virtual session: publishes, subscriptions, acknowledgements, and topology commands all happen on a channel, and the broker keeps their state per channel. Opening a fresh TCP connection per operation would waste handshakes, memory, and file descriptors; channels let one connection carry hundreds of concurrent conversations.

The other half of the picture is that the broker is stateful while producers and consumers are not. Exchanges, queues, and bindings are long-lived server-side objects that outlive any single client. A producer can crash and reconnect and the routing topology is still there. That is the opposite of a point-to-point socket, where losing the connection loses the arrangement.

Worked example – gateway fan-in. Suppose an IoT gateway process publishes telemetry, receives command replies, and runs a small health consumer. A poor design opens a new TLS connection for each publish and tears it down after the message. That repeats TCP setup, TLS negotiation, AMQP authentication, and broker resource allocation for work that may last only milliseconds. A better design opens one process-level connection, creates one publishing channel for telemetry, one consuming channel for commands, and another channel for health checks. If the command consumer is redeclared or restarted, the telemetry channel does not need a new socket.

The broker-side objects explain why this works. The exchange called iot.telemetry, the queue called ingest, and the binding between them live on the broker, not inside the gateway’s memory. The gateway can reconnect after a crash, declare the same durable topology, and resume publishing through a fresh channel. For learners, the mental model is: the connection is the pipe, channels are labeled lanes in the pipe, and the broker stores the map that says where each message should go.

Note

Rule of thumb: share one connection per process, but give each thread its own channel. Most client libraries make a channel cheap and a connection expensive on purpose.

5.4 Try It: AMQP Virtual Host Boundaries

A connection also chooses a virtual host on the broker. That vhost scopes the exchanges, queues, bindings, users, and permissions the client can see, so two teams can reuse names such as telemetry without sharing the same resources. Treat the vhost as part of the connection contract: a bridge that moves messages between vhosts needs separate connections and explicit permissions on each side.

5.5 Connection vs Channel, and Declaring Topology

Concern Connection Channel
Count One per process (typically) Many; often one per thread
Cost TCP + TLS + SASL auth handshake A single channel.open
Thread safety Shared Not safe to share across threads
Keep-alive Heartbeat frames on channel 0 Inherits the connection
Failure blast radius Kills every channel inside it A channel error closes just that channel

You build the routing topology by declaring it, and declarations are idempotent: queue.declare and exchange.declare create the object if it is absent and otherwise confirm it matches. A passive declare checks existence without creating, which is how a consumer can fail fast if the expected queue is missing. The durable flag is set here, on the object: a durable exchange or queue survives a broker restart. (Whether the messages inside survive is a separate setting covered in the delivery chapter – durability of the container and persistence of the contents are independent choices.)

Worked example. A gateway process with three worker threads opens one connection and three channels. Thread A declares (durable) exchange iot.telemetry and queue ingest, binding them once at startup. Threads B and C each consume on their own channel with their own prefetch. If thread C sends a malformed frame, the broker closes channel C with a channel.close carrying a reply code; threads A and B keep running on the same connection.

Worked example – declaration mismatch. A deployment script declares queue ingest as durable. Later, a test consumer starts with the same queue name but declares it as non-durable. The broker should reject the mismatch rather than silently changing the existing queue’s contract, because changing durability would alter restart behavior for every consumer using that queue. A safer consumer uses a passive declare in production: if the queue is missing or has the wrong properties, the service fails fast and the runbook points to the topology deployment step. The practical rule is to make topology declarations repeatable, but not ambiguous.

Prefetch belongs in the same review. If a channel consumes with prefetch 50, the broker may deliver up to 50 unacknowledged messages to that channel. If the worker is slow, those messages are no longer available to other consumers until acknowledged or the channel closes. A common IoT ingestion pattern is therefore one connection per process, one channel per worker, durable topology declared at startup, and a prefetch value chosen from processing time and acceptable retry delay.

graph LR
    P["Publisher"] --> X["Exchange: iot.telemetry"]
    X -->|binding key| Q1["Queue: ingest"]
    X -->|binding key| Q2["Queue: audit"]
    Q1 --> C1["Consumer A"]
    Q2 --> C2["Consumer B"]

5.6 Everything Travels as Channel-Tagged Frames

On the wire, AMQP 0-9-1 is a stream of frames, each stamped with the channel number it belongs to, which is how one TCP socket demultiplexes many channels. There are four frame kinds:

  • Method frame – carries a command such as basic.publish, queue.declare, or basic.ack.
  • Content header frame – for a message, carries the body size and the properties (delivery mode, content type, headers, correlation id).
  • Content body frame(s) – the payload bytes, split across as many body frames as the frame-max negotiated at connection time requires.
  • Heartbeat frame – sent on channel 0 to prove the connection is alive when no other traffic flows.

So publishing one message is really a method frame + content header frame + body frames on the same channel, in order. Channel 0 is reserved for connection-global negotiation (connection.start, connection.tune, heartbeats). This is the framing that AMQP 1.0 replaces entirely: instead of channels and these method frames, 1.0 negotiates sessions and links and moves payload in transfer frames – a genuinely different wire protocol, covered in the frames-and-reliability chapter.

Worked example – frame ordering. A publisher sends a 70 kB payload on channel 3 after the connection negotiated a body-frame size below that payload length. The broker sees a basic.publish method frame, then a content header frame that states the total body size, then multiple content body frames tagged with channel 3 until the declared byte count is complete. If another thread writes a second publish on the same channel at the same time, its method/header/body frames can interleave with the first sequence. The broker cannot reconstruct two ordered messages from an interleaved per-channel stream, which is why client libraries warn against sharing one channel across publishing threads.

Heartbeats show the connection boundary. A quiet connection still exchanges heartbeat frames on channel 0 so both sides know the TCP session is alive. Those heartbeats do not acknowledge messages and do not prove a consumer processed anything; they only prove the connection has not gone silent. Message progress is still visible through channel-scoped methods such as basic.ack, basic.nack, and basic.cancel. Under the hood, AMQP architecture is the discipline of keeping these scopes separate: connection liveness, channel state, topology objects, and message delivery are related but not interchangeable.