Part II · Chapter 7
Modbus TCP/IP
Modbus TCP is Modbus for the Ethernet age. It carries the very same PDU, but swaps the serial address and CRC for a short header suited to networks, and lets the network guarantee delivery.
By the end of this chapter you can
- Explain how Modbus rides TCP/IP on port 502, and why it needs no checksum.
- Decode the four fields of the seven-byte MBAP header.
- Use the transaction identifier to match responses to requests.
- Encode a request as a TCP frame by hand, and explain the unit ID at a gateway.
If you understand the frame from Chapter 4, Modbus TCP is a small, welcome addition rather than a new subject.
Riding on TCP/IP
Modbus TCP runs over the standard networking stack that connects nearly every modern computer: the transmission control protocol over the internet protocol (TCP/IP), carried on Ethernet. That brings two gifts:
- Reach. Any device with an IP address can be reached across a switch, a router, or in principle the whole internet, instead of being confined to one serial bus.
- Reliability. TCP already detects lost, duplicated, or corrupted packets, retransmits as needed, and delivers a clean, in-order stream of bytes. Modbus TCP simply trusts that service.
An exchange starts the way most network conversations do: the client opens a TCP connection to the server's IP address on a specific port. Modbus has a reserved, well-known port, 502, so a client that knows a device's address knows where to knock. Once the connection is open, the client can send many requests over it and keep it alive as long as it likes, rather than reconnecting for every message. Several clients can each hold their own connection to the same server at once.
No checksum needed
The first thing a serial veteran notices about a TCP frame is what's missing: no CRC and no LRC. That isn't an oversight. The TCP and Ethernet layers underneath already protect every packet with their own checksums and retransmit anything that arrives damaged, so another check at the Modbus level would be redundant. Where a serial frame ends in two CRC bytes, a TCP frame simply ends: the PDU is the last thing in it.
The MBAP header
In place of the serial address byte, Modbus TCP puts a seven-byte header in front of the PDU: the MBAP header (Modbus application protocol header). It has four fields. Three are two bytes each and the last is one byte, which makes seven.
Each field has a clear job:
- Transaction identifier. A two-byte number the client chooses for each request. The server copies it, unchanged, into the matching response.
- Protocol identifier. Two bytes, always zero for Modbus: a slot reserved to tell Modbus apart from other protocols that might share the format.
- Length. Two bytes giving the number of bytes that follow it in the frame, so the receiver knows exactly how much more to read.
- Unit identifier. One byte playing the role of the serial address byte: which device the message is for. It matters mainly when a gateway sits between the network and a serial bus.
| Field | Size | Purpose |
|---|---|---|
| Transaction ID | 2 bytes | Client sets it; server echoes it back |
| Protocol ID | 2 bytes | Always 00 00 for Modbus |
| Length | 2 bytes | Count of the bytes that follow (unit ID + PDU) |
| Unit ID | 1 byte | Which device (used mainly by gateways) |
The transaction identifier earns its keep
The transaction identifier is what makes TCP feel different from serial. On a serial bus the client sends one request and waits for its answer before sending the next, so there's never doubt about which response belongs to which request: only one is ever outstanding. Over TCP, a client may fire off several requests in quick succession without waiting. Now it needs a way to tell the responses apart, because they could even arrive in a different order than the requests went out.
The fix is neat. The client stamps each request with a different number, the server returns that same number in the matching response, and the client uses it to pair each answer with its question. A busy client can keep many requests in flight at once, which can make it far faster than a strictly one-at-a-time serial client. Simple as it is, this is what lets TCP relax serial's rigid turn-taking.
A TCP frame, byte by byte
Let's encode the running example as a TCP frame: read four holding registers, starting at address 0, from unit 1. The PDU is the same five bytes as ever, 03 00 00 00 04. In front goes the MBAP header: a transaction ID we choose (say 00 01), the protocol ID 00 00, the length (one unit-ID byte plus five PDU bytes makes six, so 00 06), and the unit ID 01.
| Bytes | Field | Meaning |
|---|---|---|
| 00 01 | Transaction ID | This request's tag |
| 00 00 | Protocol ID | Modbus |
| 00 06 | Length | 6 bytes follow |
| 01 | Unit ID | Device 1 |
| 03 00 00 00 04 | PDU | Read 4 holding registers from 0 |
The complete frame is 00 01 00 00 00 06 01 03 00 00 00 04: twelve bytes, and not a checksum among them. Compare the serial version from Chapter 4, 01 03 00 00 00 04 44 09. The shared heart, the PDU 03 00 00 00 04, sits in both. The serial frame brackets it with a one-byte address and a two-byte CRC; the TCP frame prefixes it with the seven-byte MBAP header and stops. Two envelopes, one letter.
Gateways and the unit identifier
For a device that speaks Modbus TCP natively, the unit identifier often hardly matters: the IP address already names the device, so the unit ID is set to 01 or 0xFF by convention and ignored. The field comes into its own with a gateway, a box that speaks Modbus TCP on its network side and Modbus RTU on a serial side. A great deal of older serial equipment reaches modern networks this way, without replacing any of it.
When a client sends a request to a gateway, the unit ID tells the gateway which serial device to forward it to. The gateway reads the unit ID, builds the matching RTU frame (adding the address byte and computing the CRC), and sends it down the bus. The device's reply comes back through the gateway, which strips the serial wrapper, wraps the PDU in an MBAP header echoing the original transaction ID, and returns it over TCP. To the client it's an ordinary Modbus TCP exchange; the serial bus on the far side is invisible.
TCP vs. RTU side by side
Modbus TCP request
The same request as a gateway sends it on RS-485
You now know all three transports: RTU and ASCII on serial wires, and TCP on Ethernet, each wrapping the same unchanging PDU. What's left is the catalog of operations that PDU can carry: the function codes, what each reads or writes, and how its data field is shaped.
Check your understanding
1. Why doesn't a Modbus TCP frame end with a CRC?
The layers beneath Modbus have their own checksums and retransmission, so a Modbus-level check would be redundant.
2. A request carries a unit ID and a five-byte PDU. What goes in the MBAP length field?
Length counts the bytes that follow it: 1 unit-ID byte + 5 PDU bytes = 6. It doesn't include the header bytes before it (that would give 12, or 0x0C).
3. A client has three requests outstanding, and the responses arrive out of order. How does it tell them apart?
The client stamps each request with a distinct transaction ID, and the server copies it into the response.
4. SCADA polls a TCP-to-RTU gateway. The meter behind it is RTU address 5. What unit ID should SCADA send?
The gateway uses the unit ID as the RTU address on its serial bus. 1 or 0xFF is the convention only for devices that speak TCP natively.