Modbus TCP Frame Builder & Parser
Build and decode Modbus TCP frames. The MBAP header field by field, the length nobody gets right first time, and splitting a socket read into frames.
Hex, any separator. Parsing happens in this page.
| Field | Bytes | Value |
|---|---|---|
| Transaction id | 00 01 | 1 — the server echoes this back |
| Protocol id | 00 00 | 0 — Modbus |
| Length | 00 06 | 6 — unit id plus 5-byte PDU |
| Unit id | FF | 255 — the TCP device itself, as the specification suggests |
| Function code | 03 | 3 — Read Holding Registers |
| Start address | 00 00 | 0 (0x0000) |
| Quantity | 00 01 | 1 |
The same request on a serial line
The MBAP header is replaced by the unit id as a slave address and a CRC. Useful for checking a gateway against the device behind it.
FF 03 00 00 00 01 91 D4What changes between RTU and TCP
The request itself does not change. The same function codes, the same addresses, the same data layout — that part is the PDU, and it crosses a gateway untouched. What changes is the envelope around it.
RTU frames a request with a one-byte slave address in front and a two-byte CRC behind, and separates frames with silence on the line. TCP already delivers bytes in order and already checks them, so both go away and a seven-byte MBAP header takes their place.
| Field | Size | Meaning |
|---|---|---|
| Transaction id | 2 bytes | Echoed by the server, so a client can match a reply to its request |
| Protocol id | 2 bytes | Always 0x0000 for Modbus |
| Length | 2 bytes | The bytes that follow: unit id plus PDU |
| Unit id | 1 byte | The slave address, when a gateway needs one |
| PDU | 1–253 bytes | Function code and data, identical to serial |
The length field
This is the field that gets written wrong first. It counts the unit id and the PDU, and nothing else — not itself, not the protocol id, not the transaction id. Put another way it is the frame length minus six.
A request to read one holding register is twelve bytes and its length field reads 6:
00 01 | 00 00 | 00 06 | FF | 03 00 00 00 01
Asking for ten registers instead of one does not change it, because the quantity is inside the PDU. Theresponse does change, since it carries the register contents. A client that computes the length from the request it sent rather than from the bytes it is about to send will be right until the first write.
TCP is a stream, not a sequence of messages
This is the difference that causes the most field problems, and it is not really about Modbus. A socket delivers bytes. One read() can return half a frame, one frame, or three frames end to end, and nothing in the data marks where one stops except the length field.
The correct loop is: read until you have seven bytes, take the length from bytes four and five, then read until you have that many more. Anything else works on a desk against a device that replies slowly and one frame at a time, and fails as soon as the network coalesces two replies into one segment — which is exactly when traffic is heaviest and the failure hardest to reproduce.
The third tab above splits a capture the same way, so a buffer pulled from a log can be checked against what a reader should have made of it.
The unit id is not decoration
For a device that speaks Modbus TCP natively the field is usually ignored, and the specification suggests 0xFF. Behind a TCP-to-RTU gateway it is the serial slave address and the gateway forwards on it, so getting it wrong reaches the wrong device or no device.
That is also why so much example code sets it to 1 or 0 with no comment: against a native TCP device it genuinely does not matter, right up until the same code is pointed at a gateway.
Porting serial code
- Drop the CRC. Appending one anyway is the most common porting bug. The server counts those two bytes as part of the PDU, the length no longer agrees, and the request is dropped without a useful error.
- Move the slave address. It becomes the unit id, in the header rather than in front of the function code.
- Keep the addresses. The 40001-style documentation offset works exactly as it does on serial: a register documented as 40108 is address 107 on the wire.
- Match replies by transaction id rather than assuming the next frame answers the last request. Several requests may be outstanding at once, which serial never allowed.
- Stop relying on inter-frame silence. There is none. The length field is the framing.
Frequently asked questions
What exactly does the length field count?
The unit id and the PDU — everything after the length field itself. It does not include the length field, the protocol id or the transaction id, which is why it is always the frame length minus six. A read of one register gives 6, and that figure does not change with the quantity requested, because the quantity lives inside the PDU.
What should the unit id be?
For a device that is natively Modbus TCP, the specification suggests 255, and most devices ignore the field entirely. Behind a TCP-to-RTU gateway it must be the serial slave address, because that is what the gateway forwards to. Many devices also accept 0 or 1, which is why so much example code uses those without explaining them.
Why does my client sometimes read half a frame?
Because TCP is a byte stream, not a message stream. One read can return part of a frame or several frames at once, and nothing marks the boundaries except the length field. Read seven bytes, take the length, then read exactly that many more. A client that treats each read as one message works against a slow device on a desk and fails the first time the network coalesces two replies.
Is there a CRC in Modbus TCP?
No. TCP already checksums and sequences the data, so the CRC-16 of RTU is dropped. Appending one anyway is a common mistake when porting serial code: the extra two bytes fall inside the PDU as far as the server is concerned, the length no longer matches, and the request is rejected without explanation.
What is the maximum frame size?
The PDU is capped at 253 bytes, which with the 7-byte MBAP header fixes the frame at 260. That cap comes from serial Modbus — 256 bytes minus an address and a CRC — and was kept so the same PDU crosses a gateway unchanged.
Related tools
- Modbus CRC16 CalculatorCRC-16/MODBUS for any hex payload, with the wire byte order spelled out.
- Modbus RTU Frame Builder & ParserDecode captured frames field by field, or build a request from scratch.
- Modbus Register DecoderRegister pairs to float32/int32 across every byte order devices use in practice.