In the case of a telnet session, here is where the kernel (Hijack) sets this up in ktelnetd:
// set up stdin,stdout,stderr to all point at the socket
sockfd = get_fd(parms->clientsock->inode); /* 0: stdin */
dup(sockfd); /* 1: stdout */
dup(sockfd); /* 2: stderr */
So in this case, stdout/stderr (and stdin for reading) point at the "socket", which is a handle for the remote connection (the telnet client running on a PC).
The important bit is that the program itself doesn't need to know this. It just reads stuff from stdin, and writes stuff to stdout (and/or stderr). And it all works. Whether running locally on a serial port, in a window on a desktop GUI, or at the end of some remote network connection (ssh, telnet.. ).
EDIT: So, while we're on this topic: Anything that has been "opened" on a Linux system has a file descriptor associated with it. A file descriptor is what you get back from calling open(). Or socket().
These begin with 0 for the first thing opened, and each new thing gets the lowest number that is not currently in use. So stdin=0, stdout=1, stderr=2 .. is the most commonly seen order, though it is not guaranteed to look like that.
The numbers are simply indexes into a kernel table describing each open "object" and the state of reading/writing on that object. Aka. the File Descriptor table. There is a unique file descriptor table for each process.
In the /proc/ directory on Linux (including the empeg), there is a subdirectory for each process, as well as one called "self" for the current running program. If you telnet to the empeg, and do
cd /proc/self, you will see a lower level subdirectory called
fd. This is a view of the process's file descriptor table. Look into it like this:
ls -lF /proc/self/fd/ls -lF /proc/self/fd/
total 0
lrwx------ 1 0 0 64 Aug 3 12:07 0 -> socket:[8]
lrwx------ 1 0 0 64 Aug 3 12:07 1 -> socket:[8]
lrwx------ 1 0 0 64 Aug 3 12:07 2 -> socket:[8]
lrwx------ 1 0 0 64 Aug 3 12:07 255 -> socket:[8]
Doing the same thing when logged in via the serial port gives this instead:
ls -lF /proc/self/fd/
total 0
lrwx------ 1 0 0 64 Aug 3 12:12 0 -> /dev/ttyS1
lrwx------ 1 0 0 64 Aug 3 12:12 1 -> /dev/ttyS1
lrwx------ 1 0 0 64 Aug 3 12:12 2 -> /dev/ttyS1
lr-x------ 1 0 0 64 Aug 3 12:12 3 -> /proc/35/fd/
Note that the fourth entry in the second case is the file descriptor for reading from /proc/self/fd/ at that point in time, when "self" was process id (PID) number 35.
Some fun, huh!