Search code examples
csocketsunix-socket

How to emulate PUB/SUB with unix sockets (AF_UNIX, SOCK_DGRAM)?


I am struggling to make this simple communication working.

I made it with zmq in less than five minutes. Doing it with UNIX sockets is a pain (obviously because of my lack of confidence).

This is the server:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "streamsocket.h"

char *socket_path = "/tmp/stream";
int socket_fd=0;
struct sockaddr_un addr;

int main(){
        socket_setup();
        while(1){
                socket_sendstr("a");
                sleep(1);
        }
}

void socket_setup(){

  int rc;

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, socket_path);

  if ( (socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
    perror("socket error");
    exit(-1);
  }

  rc=bind(socket_fd, (struct sockaddr *) &addr, sizeof(addr));
  if(rc<0){
    perror("bind error");
    exit(-2);
  }

}

int socket_sendstr(char* buffer) {
  int len=strlen(buffer);
  // corrected after suggestion in answer below (rc->len)
  // int rc=write(socket_fd, buffer, rc);
  int rc=write(socket_fd, buffer, len);

  if (rc != len) {
      if (rc > 0) fprintf(stderr,"partial write");
      else {
        perror("write error");
        //exit(-1);
      }
  }
}

And this is the client:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
        char * server_filename = "/tmp/stream";
        char * client_filename = "/tmp/stream-client";

        struct sockaddr_un server_addr;
        struct sockaddr_un client_addr;

        int rc;

        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strncpy(server_addr.sun_path, server_filename, 104); 

        memset(&client_addr, 0, sizeof(client_addr));
        client_addr.sun_family = AF_UNIX;
        strncpy(client_addr.sun_path, client_filename, 104);

        // get socket
        int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);

        // bind client to client_filename
        rc = bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr));
        if(rc==-1) perror("bind error");

        // connect client to server_filename
        rc = connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
        if(rc==-1) perror("connect error");


        char buf[1024];
        int bytes=0;
        while(bytes = read(sockfd, buf, sizeof(buf))){
                printf("%s\n",buf);
        }

        close(sockfd);
}

What I am doing wrong?

At the moment the client does not print anything.

EDIT1: correct wrong "rc" in server write( , ,rc) to write( , ,len)

EDIT2: as client does not work socat either: socat UNIX-CLIENT:/tmp/stream -

so I think that the problem could be in the server.


Solution

  • You misunderstand how datagram sockets work, and this is not local to Unix sockets -- you'd have the same problem with UDP.

    Datagram sockets are entirely connectionless. A connect() on a datagram socket doesn't actually make any connection, it just sets a default destination for packets sent on the socket. It's just for convenience so that you can use send instead of sendto in the case where you always send to the same address.

    You can make the server reply to a particular client if the client sends a packet to the server first, in which case you can reply, using sendto, to the same address that you got from recvfrom. If you actually want to make a connection between the client and the server, however, you'll need to use either a stream socket or a seqpacket socket instead. In that case, you will also need to ensure you're doing the proper listen/accept sequence in the server as well.

    In fact, I would, if anything, be surprised if your server doesn't print errors when it tries to send. It should be saying write error: Transport endpoint is not connected.