【问题标题】:UNIX Domain Socket programming 3 socketsUNIX 域套接字编程 3 个套接字
【发布时间】:2015-07-20 12:30:16
【问题描述】:

我正在尝试制作一个支持 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;
}

【问题讨论】:

  • 我不确定我是否理解正确。我觉得你在回答你自己的问题。是的,select 是用一个线程同时监听多个套接字的方式。基本上,您创建一个 fd_set 并使用 FD_SET 将套接字添加到该集合...
  • 我会调查的。那么FD是一种数据结构吗?我会通过我假设的循环添加套接字?
  • 是的 fd_set 是一个结构体,在 sys/select.h 中定义

标签: c sockets unix-socket


【解决方案1】:

如果您想在同一个进程中使用三个侦听套接字,则必须使它们唯一。在AF_INET 系列中,您可以通过bind(2)-ing 不同的端口来执行此操作,在AF_UNIX 系列中,您可以使用不同的路径来执行此操作。

还有你的台词:

char *socket_path = "\0hidden";

至少有两个问题:

  • 赋值右侧的字符串字面量类型为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 ('\0')

创建一个以 UNIX 路径为参数的函数,并创建、绑定并将套接字标记为侦听,并返回已创建的套接字描述符。调用它 3 次 - 你有 3 个正在监听的 UNIX 套接字。在它们上设置select(2) 以供阅读 - 它会告诉你客户端连接何时到达。此时在活动套接字上调用accept(2) 以获取已连接的客户端套接字,它与侦听套接字本身是分开的。

【讨论】:

  • 但是如果我使用 INET,那么那些不会是 UNIX 提供的本地创建的套接字吧?还是我错了?
  • 您正在使用计算机。套接字是在该计算机上创建的。同一台机器上的客户端将能够连接到 UNIX 和 INET 套接字。网络上其他机器上的客户端将能够连接到 INET 套接字,但不能连接到 UNIX 套接字。这能回答你的问题吗?
  • 我想是的。为了验证..我可以更新上面的代码,看看我是否声明了 3 个单独的套接字并正确监听它们吗?
  • @Michael,对不起,你的问题对我来说没有多大意义。拿起一本关于套接字编程的书,或在线阅读,例如在这里:beej.us/guide/bgnet - 它并不完美,但会让你继续前进。
  • 没关系..我的C语言背景很差。对类进行了重组,所有 Java 类都切换到了 C。我会看看我能做什么。无论如何感谢您的帮助。
【解决方案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

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

首先您需要一个服务器套接字,它可以接受连接。

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

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

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

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

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

【讨论】:

  • 我认为你在服务器中的 maxfd 计算中可能有一个错误,它不仅仅是计数,它是添加的任何 fd 的最大值,+1:“这是一个整数,比任何集合中任何文件描述符的最大值。换句话说,在将文件描述符添加到每个集合时,您必须计算所有集合的最大整数值,然后将该值加一,然后将其作为 nfds 传递。”
猜你喜欢
  • 2014-06-02
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多