forgive me, for I am no expert on networking protocols, but I thought the first part of your paragraph, at least, is indeed handled by TCP. I want to quote Wikipedia here:
You miss the fact that
all abstractions leak: the real world leaks through when the abstraction, like TCP, works in conditions it can't handle: that means, TCP
gives up. It's a perfectly
valid condition for TCP.
Read the Wikipedia carefully. It says TCP guarantees bytes arrive in order and not changed.
It does not guarantee that the bytes arrive. It also does
not guarantee that
all bytes get through - but it
does guarantee that, if some bytes get missing, application will be notified about it -
it kills the socket. Application then needs to reopen the socket, figure out in which state the systems left, and start operating again. This is the unavoidable business logic that goes with any protocol. There is no magic.
In case of Modbus over IP, UDP would be a better choice exactly because it gives those same guarantees
which matter. UDP, of course, also guarantees byte ordering and integrity within the packet, modbus happens to be a packet-oriented protocol, and modbus packets happen to fit within single UDP packets, so it would be a perfect fit. TCP isn't packet-oriented (unless you think one byte, just 8 bits, a "packet") - that's the whole point why it exists, its sole purpose is to turn the underlying packetized IP layer into continuous stream of single bytes. This is exactly what Modbus does
not need. Modbus traditionally works over UART / serial RS485, which also is a stream of bytes, which may have confused the designers; but here, the "stream of bytes" already is a hindrance, not a benefit; Modbus have some peculiar idle timing rules to try to enforce packet boundaries over the UART link which cannot do that inherently. That's already a hack. Modbus TCP does a similar "hack" over TCP: an extra delimiter header. UDP would have delimited the packets inherently.
Ironically enough, if you implement the modbus TCP communication state machine (write -> read -> check that it was yours and it was OK), you
already implemented the ordering guarantee yourself - you had to, as part of TCP socket error handling. As such, you don't need any more special hoops against UDP message "ordering". You start a new poll only after the previous finished, and check that the response is relevant just in case anyway.