views:

344

answers:

5

I would like to establish an IPC connection between several processes on Linux. I have never used UNIX sockets before, and thus I don't know if this is the correct approach to this problem.

One process receives data (unformated, binary) and shall distribute this data via a local AF_UNIX socket using the datagram protocol (i.e. similar to UDP with AF_INET). The data sent from this process to a local Unix socket shall be received by multiple clients listening on the same socket. The number of receivers may vary.

To achieve this the following code is used to create a socket and send data to it (the server process):

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
// buf contains the data, buflen contains the number of bytes
int bytes = write(socket, buf, buflen);
...
close(socket);
unlink(ipcFile.sun_path);

This write returns -1 with errno reporting ENOTCONN ("Transport endpoint is not connected"). I guess this is because no receiving process is currently listening to this local socket, correct?

Then, I tried to create a client who connects to this socket.

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
char buf[1024];
int bytes = read(socket, buf, sizeof(buf));
...
close(socket);

Here, the bind fails ("Address already in use"). So, do I need to set some socket options, or is this generally the wrong approach?

Thanks in advance for any comments / solutions!

A: 

Client processes do not need to bind(). Simply connect() to the socket where the server is already listening and send() and recv() away!

The error you're getting is because not more than 1 process may bind/listen/accept on a single socket (UNIX or network). If you need your server to notify lots of other processes, you need to find a different way to do it. For example, letting the clients connect to the server socket, notify where they will be listening (like subscribing), and the server can push the data to multiple "client" sockets when they become available.

Some other IPC methods that can help you are memory mapped files, or shared memory.

For an awesome introduction to this topic, read Beej's Guide to UNIX IPC (link to relevant part).

Michael Foukarakis
No it's not. It's because he isn't sending anywhere, because he's using write and he isn't connected.
EJP
A: 

You should look into multicasting. At present you are just trying to write to nowhere. And if you connect to one client you will only be writing to that client.

This stuff doesn't work the way you seem to think it does.

EJP
Multicasting for processes?
Michael Foukarakis
Sure, why not? Otherwise he has to send each message N times, and the clients don't all get the messages at the same time, which raises fairness issues.
EJP
I apologize; I just haven't ever seen multicast addresses for UNIX sockets. Do you have a working example, or did I misunderstand your post?
Michael Foukarakis
That's a different question. Neither have I.
EJP
A: 

Wouldn't it be easier to use shared memory or named pipes? A socket is a connection between two processes (on the same or a different machine). It isn't a mass communication method.

If you want to give something to multiple clients, you create a server that waits for connections and then all the clients can connect and it gives them the information. You can accept concurrent connections by making the program multi-threaded or by forking processes. The server establishes multiple socket-based connections with multiple clients, rather than having one socket that multiple clients connect to.

BobTurbo
+1  A: 

The proximate cause of your error is that write() doesn't know where you want to send the data to. bind() sets the name of your side of the socket - ie. where the data is coming from. To set the destination side of the socket, you can either use connect(); or you can use sendto() instead of write().

The other error ("Address already in use") is because only one process can bind() to an address.

You will need to change your approach to take this into account. Your server will need to listen on a well-known address, set with bind(). Your clients will need to send a message to the server at this address to register their interest in receiving datagrams. The server will recieve the registration messages from clients using recvfrom(), and record the address used by each client. When it wants to send a message, it will have to loop over all the clients it knows about, using sendto() to send the message to each one in turn.

Alternatively, you could use local IP multicast instead of UNIX domain sockets (UNIX domain sockets don't support multicast).

caf
A: 

You can solve the bind error with the following code:

int use = yesno;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&use, sizeof(int));

With UDP protocol, you must invoke connect() if you want to use write() or send(), otherwise you should use sendto() instead.

To achieve your requirements, the following pseudo code may be of help:

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
set RESUSEADDR with setsockopt
bind()
while (1) {
   recvfrom()
   sendto()
}
Emacs