6

ncat (from the nmap folk) has a neat default action of duplicating any input to all connected clients. E.g.:

Start a server on terminal 1:

% mkfifo messages
% exec 8<>messages  # hold the fifo open
% ncat -l 5555 -k --send-only < messages

Start clients listening on terminals 2 & 3:

% nc localhost 5555

Output something to the fifo on terminal 4 and watch the same message appear on all connected clients (terminals 2 & 3):

% printf 'Hello, clients.\n' > messages

Is this same pattern possible with socat?

Update: screenshot of Philippe's solution:

enter image description here

whiteinge
  • 255
  • 3
  • 9
  • I cannot reproduce the expected behaviour with your commands. Could you give information on the shell used, the versions of the tools... ? – lgeorget May 16 '15 at 22:35
  • Bourne shell. % ncat --version Ncat: Version 6.47 ( http://nmap.org/ncat ) – whiteinge May 19 '15 at 00:17

2 Answers2

3

What you want to do is, I'm afraid, impossible. The closest you can get is:

On terminal 1:

mkfifo messages
socat PIPE:messages TCP4-LISTEN:5555,fork

On terminals 2 & 3:

nc localhost 5555

Then, text sent to messages will appear sometimes on terminal 2 and sometimes on terminal 3. This is because socat will 'consume' the text in messages before making them available through the socket open on localhost:5555. Then, the first netcat process to wake up and read from the socket will get the messages and the other will get nothing.

heemayl
  • 54,820
  • 8
  • 124
  • 141
lgeorget
  • 13,656
  • 2
  • 41
  • 63
  • I agree this isn't possible using named pipes. ncat forks a process for each incoming listener and then it is _duplicating_ what is coming in from the named pipe in that POC to each processes using some other form of IPC. Since socat is so much more flexible here I wonder if it is possible to use a UNIX domain socket (a datagram socket?). This is unfamiliar territory for me and I haven't been able to understand the syntax. – whiteinge May 19 '15 at 00:29
  • Sorry for the delay, I've been testing several schemes. With a socket, you have the same problem, you cannot magically duplicate what you read from the socket. What is the real problem you're trying to solve? – lgeorget May 26 '15 at 20:33
  • Even with a regular file, it does not work, the second process gets nothing (EOF) when connecting to the socket localhost:5555. – lgeorget May 26 '15 at 22:41
  • I agree this isn't possible with a standard UNIX domain socket for the same reason it isn't possible with a named pipe. I'm not looking for magic but rather wondering if socat can copy (multiplex?) an input stream to multiple listeners in the same way that ncat is able to. – whiteinge Jun 01 '15 at 01:38
  • @SethHouse Yes, I'm interested in that too but I just don't think it's possible just using socat :/ . However, there are solutions using `tee`: http://stackoverflow.com/q/17480967 and `https://gist.github.com/mathieue/3505472`. I think you can go with that. – lgeorget Jun 01 '15 at 06:48
  • You may be right it's not possible with current socat. It seems pub/sub-like interprocess communication is not overly common in the C world (outside of libs like ZeroMQ/Nanomsg). Skimming the ncat source it appears they are simply looping over the file descriptors for all attached clients and manually duplicating the input to each. I was hoping for some lesser-known socket type that would abstract that away (datagram, local-only UDP, or something similar). Oh well. Thanks for your time. – whiteinge Jun 01 '15 at 20:52
  • You're welcome, thank you for the question. : -) feel free to post and accept your own answer with the details you discovered. It may interest someone else. – lgeorget Jun 02 '15 at 20:53
1

This command will do:

socat tcp-listen:5555,fork,reuseaddr \
'system:
PIPE=$(mktemp -u /tmp/pns_XXX)
mkfifo $PIPE
while read PIPE_MESSAGE<$PIPE; do
  echo $PIPE_MESSAGE
done &
PID=$!
while read CLIENT_MESSAGE; do
  for OTHER_PIPE in $(ls /tmp/pns_*); do
    [ $PIPE != $OTHER_PIPE ] && echo $CLIENT_MESSAGE > $OTHER_PIPE
  done
done
kill $PID
rm $PIPE'

The idea is to create a named pipe for every new connection and then echoing to std output (i.e. send to client) whatever is read from the pipe. Std input (i.e. send by client to server) is read from and whatever is received is echoed to all the named pipes, except the own pipe.

Philippe
  • 26
  • 3
  • Wow. That works beautifully! Very impressive use of `system`. Thank you for the solution! – whiteinge Apr 27 '21 at 14:21
  • I love how you maintain a count of connected clients by referencing existing named pipes and incrementing from there. Backgrounding the main read and then killing the PID on completion and removing the (now unneeded) pipe is a clever way to differentiate the readers from the writer -- I'm still wrapping my head around it. :) – whiteinge Apr 27 '21 at 14:51
  • Nice to hear you like this solution! – Philippe May 05 '21 at 21:35
  • 1
    I have simplified a bit the part where the name of the pipe is determined. Before it was with a counter, and that needed quite some bash code to make sure to get a unique number. I recently found out about the mktemp command, which will create unique files, so I've edited the solution to use mktemp. – Philippe May 05 '21 at 21:38
  • Good simplifications! – whiteinge May 06 '21 at 06:02