The external interface used in this example simulation is a single TCP socket. All interesting functionality is in the cSocketRTScheduler class. Its source code is in csocketrtscheduler.h/cc.
cSocketRTScheduler combines synchronizing to real-time with socket communication. When the simulation starts, the cSocketRTScheduler object starts to listen on port 4242 (or the one that was configured) for incoming TCP connections. All connections will be accepted, but only one at a time (the OS serializes connections on the same socket, and for simplicity we don't open a new socket or start a new thread whenever one connection gets established.)
Then normal real-time simulation will begin, except that cSocketRTScheduler will use select() instead of usleep() to wait, so that it always learns immediately whenever some data arrives on the socket.
When data arrives on the socket, it will be received into a buffer (a byte array), the current time/date will be remembered, and a given cMessage object will be scheduled (put into the FES) with the simulation time that corresponds to the current time for the dedicated "interface module". The scheduler function then returns, and the new simulation event will be immediately executed in exactly the same way as any other event would.
At the beginning of the simulation, the extClient simple module (which is of class ExtHTTPClient or ExtTelnetClient) had registered itself with cSocketRTScheduler as the dedicated "interface module". So it's this module that will receive the event, just as a normal self-message.
From the self-message it knows that it has to look into the buffer for the bytes that were received, and it will convert them to a simulated packet (e.g. a HTTPMsg or TelnetPkt in this model). The packet then gets sent on the "out" gate into the simulated network, just as it would on any other OMNeT++/OMNEST simulation.
When the extClient module receives a packet (e.g. HTTP reply) from the server on the simulated network, it has to relay it via the socket back to the external process. So it first converts the message to a byte array (e.g. gets the HTTP reply from the HTTPMsg object), and invokes a cSocketRTScheduler method to send it on the socket.
As you can see, the scheduler object has to work together closely with the interface module. At the beginning, the interface module has to find the scheduler object, cast it to the appropriate type, and register itself. In the registration call (rtScheduler->setInterfaceModule(...) below) it also passes the scheduler the address and length of the buffer (recvBuffer, 4000) as well as the message it wants to get back as a self-message whenever data arrive from the socket. This is done by the following code, found in the initialize() functions of ExtHTTPClient and ExtTelnetClient:
rtScheduler = check_and_cast<cSocketRTScheduler *>(simulation.scheduler()); cMessage *rtEvent = new cMessage("rtEvent"); rtScheduler->setInterfaceModule(this, rtEvent, recvBuffer, 4000, &numRecvBytes);
Notice that this registration function and even the notion of a dedicated interface module are specific to this particular example simulation. cSocketRTScheduler is just an example, and it was deliberately made as simple as possible to facilitate understanding. In real-life applications, you can (and will probably have to) create much more sophisticated hardware-in-the-loop scheduler classes.