LearnSCADA

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.

An RTU frame is shown as address, PDU, and CRC. The address and CRC fade away, the PDU slides down unchanged, and a seven-byte MBAP header made of transaction ID, protocol ID, length, and unit ID attaches in front of it to form the Modbus TCP frame. Nothing follows the PDU. RTU TCP Addr CRC PDU function code + data Trans. ID2 bytes Proto ID2 bytes Length2 bytes Unit ID1 byte Address and CRC are dropped; the PDU doesn't change no checksum
Figure 7.1 · A Modbus TCP frame is a seven-byte MBAP header followed by the PDU. There's no checksum at the end: TCP/IP beneath already guarantees the data arrived intact.

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.
FieldSizePurpose
Transaction ID2 bytesClient sets it; server echoes it back
Protocol ID2 bytesAlways 00 00 for Modbus
Length2 bytesCount of the bytes that follow (unit ID + PDU)
Unit ID1 byteWhich device (used mainly by gateways)
The twelve bytes of the running example over TCP: 00 01, 00 00, 00 06, 01, then the PDU 03 00 00 00 04. Brackets name the four MBAP fields: transaction ID, protocol ID, length, and unit ID. The bytes after the length field are counted one by one, from the unit ID to the end of the PDU, reaching 6, which is the value in the length field. Transaction ID Protocol ID Length Unit PDU 00 01 00 00 00 06 01 03 00 00 00 04 1 2 3 4 5 6 6 bytes follow the length field → length = 00 06 7 header bytes + 5 PDU bytes = 12 bytes, and the frame simply ends.
Figure 7.2 · The four fields of the MBAP header: transaction identifier, protocol identifier (always zero), length, and unit identifier, seven bytes in all. Length counts the unit ID and the PDU.

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 client sends three requests in quick succession, tagged with transaction IDs 0001, 0002, and 0003. The responses come back in a different order: 0002, then 0003, then 0001. As each response arrives, the client matches its echoed transaction ID to the request it sent and ticks it off. Client waiting for… 0001holding regs 0002coils 0003input regs ✓ ✓ ✓ Server port 502 replies when each is ready requests → ← responses, in any order ID 0001 ID 0002 ID 0003 ID 0002 ID 0003 ID 0001
Figure 7.3 · The client gives each request a distinct transaction identifier and the server echoes it. By matching the echoed number, the client pairs every response with its request even when several are outstanding.

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.

BytesFieldMeaning
00 01Transaction IDThis request's tag
00 00Protocol IDModbus
00 06Length6 bytes follow
01Unit IDDevice 1
03 00 00 00 04PDURead 4 holding registers from 0
MBAP header Function code Data
00 01trans ID 00 00protocol 00 06length 6 01unit ID 03function 00 00start 0 00 04quantity 4

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.

The same request in both worlds, aligned on the PDU. Top, Modbus RTU: address 01, PDU 03 00 00 00 04, CRC 44 09. Bottom, Modbus TCP: MBAP header 00 01 00 00 00 06 01, then the identical PDU 03 00 00 00 04, and nothing after it. A shaded band marks the PDU that the two frames share. same PDU in both RTU TCP 01 03 00 00 00 04 44 09 00 01 00 00 00 06 01 03 00 00 00 04 address CRC, low byte first MBAP header, 7 bytes no checksum RTU: 1 + 5 + 2 = 8 bytes · TCP: 7 + 5 = 12 bytes
Figure 7.4 · The same request in both worlds. The serial frame wraps the shaded PDU in an address and a CRC; the TCP frame wraps the identical PDU in the MBAP header and no checksum.

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.

A SCADA client sends a Modbus TCP request with transaction ID 0007 and unit ID 2 over Ethernet to a gateway at 10.0.0.50. The gateway turns it into an RTU frame addressed to device 2 on its RS-485 bus. Device 2, a drive, answers; the gateway wraps the reply in an MBAP header with the same transaction ID 0007 and returns it to the client. SCADA 10.0.0.10 Ethernet · TCP 502 Gateway 10.0.0.50 TCP ⇄ RTU RS-485 Meter RTU addr 1 Drive RTU addr 2 Analyzer RTU addr 3 ID 0007 · unit 2 RTU · addr 2 RTU reply ID 0007 · reply
Figure 7.5 · A gateway bridging Modbus TCP and Modbus RTU. The unit ID chooses the serial device; the reply returns with the same transaction ID, the serial bus hidden from the client. Unit 3 instead would reach the analyzer.
"Modbus RTU over TCP" is not Modbus TCP Some serial servers and drivers send raw RTU frames, address and CRC included, inside a TCP socket, often on a port other than 502. If one side speaks Modbus TCP and the other expects RTU-over-TCP, nothing works even though the network is fine. Check that both ends use the same mode.

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.