UNIX Domain Socket编程3套接字

UNIX Domain Socket programming 3 sockets

我正在尝试制作一个支持 3 个套接字的 server.c 文件,分别由 3 个客户端 类 表示:client1、client2、client3。

在我的 server.c 文件中,我目前有这段代码,是我在互联网上找到的。

如果我想让它有 3 个插座。我想使用 select() 命令查看 3 个客户端的写入活动。我的问题是如何使用它来支持 3 个套接字。

我可以将 3 个客户端绑定到服务器可以侦听的 3 个套接字吗?如果是这样,服务器如何分别监听这3个套接字?可能用数组?

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

#define socket1 "sock1"
#define socket2 "sock2"
#define socket3 "sock3"

int main(int argc, char *argv[]) {
    //struct sockaddr_un addr;
    struct sockaddr_un addr1;
    struct sockaddr_un addr2;
    struct sockaddr_un addr3;
    char buf[100];
    int socket1;
    int socket2;
    int socket3;
    //int fd;
    int cl,rc;

    if (argc > 1) socket_path=argv[1];

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

    memset(&addr1, 0, sizeof(addr1));
    addr1.sun_family = AF_UNIX;
    strncpy(addr1.sun_path, socket_path, sizeof(addr1.sun_path)-1);

    unlink(socket_path1);

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

    memset(&addr2, 0, sizeof(addr2));
    addr1.sun_family = AF_UNIX;
    strncpy(addr2.sun_path, socket_path, sizeof(addr2.sun_path)-1);

    unlink(socket_path2);

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

    memset(&addr3, 0, sizeof(addr3));
    addr3.sun_family = AF_UNIX;
    strncpy(addr3.sun_path, socket_path, sizeof(addr3.sun_path)-1);

    unlink(socket_path3);

    if (bind(socket1, (struct sockaddr*)&addr1, sizeof(addr1)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (bind(socket2, (struct sockaddr*)&addr2, sizeof(addr2)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (bind(socket3, (struct sockaddr*)&addr3, sizeof(addr3)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (listen(socket1, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    if (listen(socket2, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    if (listen(socket3, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    while (1) {
        if ( (cl = accept(fd, NULL, NULL)) == -1) {
            perror("accept error");
            continue;
        }

        while ( (rc=read(cl,buf,sizeof(buf))) > 0) {
            printf("read %u bytes: %.*s\n", rc, rc, buf);
        }
        if (rc == -1) {
            perror("read");
            exit(-1);
        }
        else if (rc == 0) {
            printf("EOF\n");
            close(cl);
        }
    }

    return 0;
}

如果你想在同一个进程中使用三个监听套接字,你必须让它们唯一。在 AF_INET family you do that by bind(2)-ing different ports, in the AF_UNIX 系列中,您可以使用不同的路径。

还有你的台词:

char *socket_path = "[=10=]hidden";

至少有两个问题:

  • 赋值右侧的字符串字面量类型为const char[8],衰减为const char*指针类型,而非char*类型。使左侧 const char*。另外,使用更高的警告级别编译,例如-Wall -pedantic以获得编译器的帮助。
  • 字符串开头的零字节使 strncpy(3) 不复制任何内容,因为它 copies at most n characters from the string pointed to by src, including the terminating null byte ('[=20=]') .

创建一个将 UNIX 路径作为参数并创建、绑定和标记套接字为侦听的函数,returns 创建套接字描述符。调用它三次 - 您有三个侦听 UNIX 套接字。在活动套接字上设置 select(2) on them for reading - that'll tell you when client connections arrive. At that point call accept(2) 以获得 连接的客户端套接字 ,它与侦听套接字本身是分开的。

好吧,因为 select 一直是我的 最爱 Unix 系统调用,我决定做点小事,依我的拙见,你在找什么。

我无耻地从这里拿走了服务器和客户端的代码: https://troydhanson.github.io/misc/Unix_domain_sockets.html

当然我做了一些小修改,以使其符合您的需要,让我们看看:

server.c:

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

char *socket_path = "/tmp/socket";

int main() {
  int fd, i;
  int clients[10], num_clients;
  fd_set read_set;
  char buf[100];

  struct sockaddr_un addr;

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

  unlink(socket_path);

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

  if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("bind error");
    exit(-1);
  }

  if (listen(fd, 5) == -1) {
    perror("listen error");
    exit(-1);
  }

  num_clients = 0;

  while (1) {
    FD_ZERO(&read_set);
    FD_SET(fd, &read_set);

    for (i = 0; i < num_clients; i++) {
      FD_SET(clients[i], &read_set);
    }

    select(fd + num_clients + 1, &read_set, NULL, NULL, NULL);

    if (FD_ISSET(fd, &read_set)) {
      if ( (clients[num_clients++] = accept(fd, NULL, NULL)) == -1) {
        perror("accept error");
        continue;
      }
      printf("we got a connection!\n");
    }

    for (i = 0; i < num_clients; i++) {
      if (FD_ISSET(clients[i], &read_set)) {
        read(clients[i], buf, sizeof(buf));
        printf("client %d says: %s\n", i, buf);
      }
    }
  }
}

client.c:

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

char *socket_path = "/tmp/socket";

int main(int argc, char *argv[]) {
  struct sockaddr_un addr;
  char buf[100];
  int fd,rc;

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

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

  if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("connect error");
    exit(-1);
  }

  while( (rc=read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
    printf("writing\n");
    *index(buf, '\n') = 0;

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

  return 0;
}

好的,这很容易实现,您只需在一个终端中启动服务器,然后打开几个其他终端并启动几个客户端。

运行 它在我的电脑上得到:

exe@atreides:~/tmp$ ./server 
we got a connection!
client 0 says: Hello!
we got a connection!
client 1 says: Hey man!

同时另一个终端:

exe@atreides:~/tmp$ ./client 
Hey man!
writing

另一个:

exe@atreides:~/tmp$ ./client 
Hello!
writing

这一切背后的魔力在于正确使用套接字和select。

首先你需要一个服务器套接字,一个接受连接的套接字。

一旦绑定到服务器套接字,让它成为 Unix 套接字或网络套接字,您就可以通过接受该套接字上的连接来为您的客户端获取套接字。每个客户端都获得一个新的套接字号。

然后,将这些套接字、服务器套接字和客户端套接字添加到 fd_set 并将其传递给 select。 Select 将同时监听所有套接字,并将接收到数据的套接字留在集合中。

现在您迭代集合以查看哪些套接字是热的,您就在那里!

还有一件事,我猜你很困惑,所有客户端都连接到 相同的 服务器套接字地址(文件)。是的,就像许多进程同时打开同一个文件一样......但这不是一个普通文件,它是一个 Unix 套接字。 :)

玩得开心,祝你好运!!!