02 — MODBUS TCP

Modbus, but on Ethernet — and missing some old baggage.

Estimated 25 minutes.

Same Modbus. New cable.

Modbus TCP is the same Modbus you already know — same function codes, same coils and registers, same data model — but it travels over Ethernet using TCP/IP instead of an RS-485 serial cable.

Why bother?

Ethernet gives you real bandwidth (100 Mbps vs 19.2 kbps), no daisy-chain wiring, multiple masters at the same time, and the same network your IT department already runs. The trade-off is determinism: TCP is "best effort," not real-time.

Where you'll meet it.

A SCADA polling 30 PLCs over the plant LAN. A PLC reading a smart power meter over Ethernet. A gateway converting Modbus RTU devices on a serial bus into Modbus TCP for upstream consumers. It is one of the most common industrial Ethernet protocols on Earth.

TCP port 502.

Modbus TCP listens on TCP port 502 — registered with IANA in 1999. If port 502 is open on a device, it almost certainly speaks Modbus. (We saw port numbers in the Networking Lesson 06.)

Quick recap: RTU framing.

In Modbus RTU, every message is: slave address (1 byte) + function code (1 byte) + data + CRC (2 bytes). The slave address says who. The CRC catches noise on the wire. A silent gap marks the end of the frame.

RTU frame, for reference.

8 bytes total. Slave + function + data + CRC.

TCP framing: meet MBAP.

Modbus TCP wraps the same function-code-and-data payload (the "PDU") in a 7-byte header called MBAP — Modbus Application Protocol header. It does three jobs: identify the request, say how long the message is, and route to the right slave.

And drops two old things.

Modbus TCP has no CRC and no silent-gap framing. TCP itself already guarantees that bytes arrive in order, complete, and without corruption. Adding a CRC on top would be wasted work. Length is carried explicitly in the header instead of inferred from timing.

The MBAP header, byte by byte.

7 bytes. Then comes the PDU (function code + data).

Transaction ID.

A 16-bit number the master picks. The slave echoes it back unchanged. Because TCP lets the master have many in-flight requests, the Txn ID is what lets the master match each reply to its question. RTU never needed this — RTU is one-at-a-time.

Protocol ID.

Always zero for Modbus. Reserved by Schneider for future protocols on the same port. If you ever see a non-zero value, the device is broken or speaking something else.

Length.

How many bytes follow the length field itself, counting the Unit ID and the entire PDU. Replaces the silent-gap timing of RTU. The receiver reads exactly this many bytes, then knows the frame is complete.

Unit ID.

The same idea as the RTU slave address. For a native Modbus TCP device, it is usually 1 or 0xFF (just "the device at this IP"). For a TCP-to-RTU gateway, the Unit ID picks which downstream serial slave to route to.

RTU framing vs TCP framing.

Tap each option to compare side-by-side.

A full request.

Let's read 3 holding registers starting at address 107 from the device with Unit ID 17. The PDU is identical to the RTU version. The MBAP header is the only thing that's new.

Request: 12 bytes total.

MBAP (7) + PDU (5) = 12 bytes. Compare to RTU's 8 bytes for the same request.

And the slave's reply.

Same Txn ID lets the master pair this reply with the request. Length grew because the data grew.

Note the matching.

Master sent Txn ID 1; slave echoed Txn ID 1. If the master had three requests in flight (1, 2, 3), the replies could come back in any order — the Txn ID is how each reply finds its home.

Why no CRC?

Modbus TCP has no CRC at the end of the message. Why is that safe?

  • Because Ethernet is naturally noise-free.
  • Because TCP already guarantees delivery and integrity using its own checksums.
  • Because the MBAP header includes a checksum.

Map RTU fields to TCP fields.

Each RTU concept has a TCP equivalent — except one, which is brand new. Tap to study.

Gotcha #1: Unit ID still matters.

New users sometimes assume "TCP routes by IP, so Unit ID is irrelevant." Wrong. Many gateways serve multiple downstream RTU slaves on a single IP, and the Unit ID picks which one. Even native devices often validate it — sending Unit ID 0 may be silently ignored.

Gotcha #2: Determinism.

TCP is best-effort. Round-trip times can spike from 5 ms to 500 ms when the network is busy or a switch reboots. For closed-loop control you want EtherCAT or Profinet IRT. For monitoring and setpoints, Modbus TCP is fine.

Gotcha #3: It is plaintext.

Modbus TCP has no authentication and no encryption. Anyone on the same network can read your registers — or write them. Put it on an isolated OT VLAN. Never expose port 502 to the internet. (Yes, people do, and Shodan finds them.)

Gotcha #4: Off-by-one and byte order — still here.

Modbus TCP inherits Modbus RTU's register-numbering and 32-bit byte-order quirks unchanged. Switching to Ethernet does not magically fix vendor disagreements about whether a float is big-endian or word-swapped.

Decode any MBAP packet.

Paste any Modbus TCP hex string. The header and PDU update live, and the length field is checked against the actual byte count.

You now know Modbus TCP.

  • Same Modbus PDU, wrapped in a 7-byte MBAP header.
  • MBAP = Transaction ID + Protocol ID + Length + Unit ID.
  • No CRC and no silent gap — TCP handles those for you.
  • Listens on TCP port 502.
  • RTU's off-by-one and byte-order gotchas survive unchanged.

Modbus TCP complete.

  • You can decode any MBAP/PDU packet by eye.
  • You understand why TCP needs Txn IDs but RTU does not.
  • You know which RTU concept maps to which TCP field.
  • You know why never to expose port 502 to the internet.