Fair enough. Again the field is too broad to answer generally, but I can share two varieties I've worked on, both in the general area of SCADA.
Project A is more of a local control application, where there was a decent amount of logic on the distributed systems. www.ni.com/dcaf is a good example of the general design used -- all data goes into a central storage and the control loop operates continuously off of that storage without ever stopping. Events were implemented as tag changes. Data is transferred between device and HMI by copying the full tag table between systems, so there is no concern about missing an event. This could be implemented using the CVT client comm library if you're using the CVT, but in concept its very simple -- make a TCP or UDP socket, send data on that socket at a fixed periodic rate.
Project B was more of a daq application with a lot of attached devices and instruments and a tiny, tiny amount of control. As such, an event-oriented approach made sense. Everything used a QMH, data went over the network as separate packets in a TCP stream and were processed as individual updates (although for some HMIs this was copied into a global tag table like the CVT). Here, a TCP stream was used for data updates, while messages were sent using a request-response protocol on top of TCP (think similar to http). The advantage to this is that you always get a response immediately. If you say "move to here" and it knows it can't, you'll find out immediately rather than having to (for example) look for a fault bit to be set.
For every system I've worked on I've found syslog to be handy -- this could be either the UDP-based library provided by NI or just making a simple string file logger yourself. Debugging is always a challenge, so a standard way to report human-readable information (debug messages, status updates, errors) is critical. If you make your own, timestamping is important.
Some people like to have a central error handler, but I've never seen this work well. Reporting, sure, handling, not as much. My general pattern is 1-check for expected errors, 2-if the error is not expected, reset the process from scratch and report. For example if you're talking to a serial instrument and you get an error, tell anyone who might care, close the visa session, reset, and try again. Don't try anything fancy to fix it.