The Modbus TCP frame, byte by byte: MBAP header and PDU

· By Vladimir Chemeris, developer of Modbus TCP FieldKit

Short answer: a Modbus TCP frame is a 7-byte MBAP header followed by a PDU of up to 253 bytes. There’s no CRC, because TCP handles integrity. All multi-byte fields are big-endian.

A real request

We read three holding registers from a pymodbus 3.16.1 server with a raw socket:

00 01 00 00 00 06 01 03 00 00 00 03
Bytes Field Value Meaning
00 01 Transaction ID 1 The client picks it, and the server repeats it
00 00 Protocol ID 0 Modbus requires 0
00 06 Length 6 Bytes that follow: Unit ID plus PDU
01 Unit ID 1 Routing behind a gateway
03 Function 3 Read Holding Registers
00 00 Address 0 First register, 0-based
00 03 Quantity 3 Three registers

Everything up to the Unit ID is the MBAP header. The function code and what follows form the PDU.

The response

00 01 00 00 00 09 01 03 06 00 64 00 FA 04 00
Bytes Field Meaning
00 01 Transaction ID Same as the request
00 00 Protocol ID 0
00 09 Length 9 bytes follow
01 Unit ID Echoed
03 Function Same function, no error
06 Byte count 6 data bytes
00 64 Register 0 100
00 FA Register 1 250
04 00 Register 2 1024

If the function failed, the response would carry 83 and an exception code instead.

What each header field is for

Transaction ID. TCP may deliver responses after the client has moved on. The client matches each response to its request by this number. The implementation guide says a response with a Transaction ID that matches no pending request “must be discarded”. Some RTU to TCP gateways return 0, which is a common reason for strict clients to complain.

Protocol ID. Modbus requires 0. We sent a frame with 00 01 here, and pymodbus stayed silent until the client timed out. That is a hard failure to diagnose without a capture.

Length. The number of bytes after this field. A receiver uses it to find the end of the frame in the TCP stream. If it’s wrong, the client and server lose track of where frames begin.

Unit ID. Selects the device behind a gateway. For a device on TCP with no gateway, the guide recommends 0xFF and also accepts 0. Details and real behaviour.

Size limits

The serial heritage sets the limits. A serial frame tops out at 256 bytes, which leaves 253 for the PDU. Add the 7-byte MBAP header and you get 260 bytes for TCP.

That’s where the familiar quantity limits come from: a read response holds at most 125 registers (250 bytes of data, plus function code and byte count), and a write of multiple registers at most 123 (the request also carries address, quantity and byte count).

Compared with RTU

The same read as an RTU frame swaps the header for a slave address and adds a CRC:

01 03 00 00 00 03 05 CB

There’s no transaction ID, length or protocol field, so an RTU master can have only one request outstanding on the line. RTU over TCP vs Modbus TCP covers what this means with converters.

To read your own traffic, paste it into the frame decoder.

Sources

FAQ

What is the MBAP header?

The 7-byte header that Modbus TCP puts in front of the PDU: Transaction ID (2 bytes), Protocol ID (2), Length (2) and Unit ID (1). It replaces the address and CRC of a serial RTU frame.

Does Modbus TCP have a CRC?

No. TCP already protects the data, so the Modbus TCP frame has no CRC. RTU frames carry a 2-byte CRC, and so does RTU over TCP.

What is the maximum Modbus TCP frame size?

260 bytes: a PDU of up to 253 bytes plus the 7-byte MBAP header. The limit comes from the original 256-byte serial frame.