Same goes for bash, it's just a program reading from stdin/stdout. There's a slight difference though, in case of interactive applications like bash: stdin/stdout would better be a terminal (e.g. /dev/tty* or /dev/pty* ). That's because a shell emits, besides textual output, control codes for interpretation by the terminal. These control codes are not displayed as readable text by the terminal. Instead, they do things like "clear the screen", "position the cursor at x/y", or "set the foreground color to green".
Now, you could have a netcat between a local terminal and a shell running on a remote server. But the problem there is that the shell is not blindly reading/writing from/to an input/output stream pair. A terminal is also an entity that's deeply connected to the Operating system and job control. That's why the terminal really has to "run" on the same machine as the shell.
So this is what ssh does: It connects to the remote server, and sets up a pseudo terminal there, so that the remote shell can interact with it and the operating system. On the local side, interpretation of control codes and job control by the local terminal are disabled while the remote system is running (the local terminal is put into so-called raw mode). For example, when you press Ctrl+z (ASCII 26), this does not result in a process suspend on the local side anymore. Instead the byte (26 aka 0x1a) is sent to the remote server, and only there it is interpreted by the terminal subsystem in the operating system, to suspend the current remote operation.
It's probably not super clear from my description, so you should just play with it a little. For example, do "printf 'sleep 10\n\x1a\necho hello\nexit\n' | ssh -t -t jstimpfle.de". What's happening here?
But that's not probably what you want, or sensible.
rl_{in,out}stream. You can use fdopen to get a stdio stream from a fd (socket for example). This can be problematic if not done correctly (buffering & stuff). Official documentation has more info on all this.
You can also fork+dup2+pipe/pty and use stdin/out on the forked process.