首页 文章

Unix域套接字:使用一个服务器进程和多个客户端进程之间的数据报通信

提问于
浏览
31

我想在Linux上的几个进程之间 Build IPC连接 . 我之前从未使用过UNIX套接字,因此我不知道这是否是解决此问题的正确方法 .

一个进程接收数据(未格式化,二进制),并使用数据报协议通过本地AF_UNIX套接字分发此数据(即类似于带AF_INET的UDP) . 从该进程发送到本地Unix套接字的数据应由在同一套接字上侦听的多个客户端接收 . 接收器的数量可能会有所不同

为实现此目的,以下代码用于创建套接字并向其发送数据(服务器进程):

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);

此写操作返回-1,并且errno报告ENOTCONN(“传输 endpoints 未连接”) . 我想这是因为没有接收进程当前正在侦听这个本地套接字,对吗?

然后,我尝试创建一个连接到此套接字的客户端 .

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);

这里,绑定失败(“地址已在使用中”) . 那么,我是否需要设置一些套接字选项,或者这通常是错误的方法?

在此先感谢任何意见/解决方案!

7 回答

  • 7

    使用unix数据报套接字有一个技巧 . 与流套接字(tcp或unix域)不同,数据报套接字需要为服务器和客户端定义 endpoints . 当在流套接字中 Build 连接时,操作系统将隐式创建客户端的 endpoints . 无论这是对应于短暂的TCP / UDP端口,还是对应于unix域的临时inode,都会为您创建客户端的 endpoints . 这就是为什么你通常不需要为客户端中的流套接字发出bind()调用 .

    您're seeing 2994029 is because you'告诉客户端绑定到与服务器相同的地址的原因 . bind() 是关于断言外部身份 . 两个套接字通常不能具有相同的名称 .

    使用数据报套接字,特别是unix域数据报套接字,客户端必须 bind() 到其自己的 endpoints ,然后 connect() 到服务器的 endpoints . 这是您的客户端代码,略有修改,还有其他一些好东西:

    char * server_filename = "/tmp/socket-server";
    char * client_filename = "/tmp/socket-client";
    
    struct sockaddr_un server_addr;
    struct sockaddr_un client_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, server_filename, 104); // XXX: should be limited to about 104 characters, system dependent
    
    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
    bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr));
    
    // connect client to server_filename
    connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
    
    ...
    char buf[1024];
    int bytes = read(sockfd, buf, sizeof(buf));
    ...
    close(sockfd);
    

    此时,您的套接字应完全设置 . 我认为理论上你可以使用 read() / write() ,但通常我会使用 send() / recv() 作为数据报套接字 .

    通常,您需要在每次调用后检查错误,然后发出 perror() . 当出现问题时,它会极大地帮助你 . 通常,使用这样的模式:

    if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
    }
    

    这适用于任何C系统调用 .

    最好的参考是史蒂文的“Unix网络编程” . 在第3版,第15.4节,第415-419页显示了一些例子并列出了许多警告 .

    顺便提一下,参考

    我想这是因为没有接收进程当前正在侦听这个本地套接字,对吗?

    我认为你对服务器中 write() 的ENOTCONN错误是正确的 . UDP套接字通常不会抱怨,因为它无法知道客户端进程是否正在侦听 . 但是,unix域数据报套接字是不同的 . 实际上,如果客户端的接收缓冲区已满而不是丢弃数据包, write() 将实际阻塞 . 这使得unix域数据报套接字远远优于用于IPC的UDP,因为UDP在加载时肯定会丢弃数据包,即使在localhost上也是如此 . 另一方面,这意味着你必须小心快速写作和慢读者 .

  • -2

    您错误的直接原因是 write() 不知道您要将数据发送到的位置 . bind() 设置套接字的名称 - 即 . 数据来自何处 . 要设置套接字的目标端,您可以使用 connect() ;或者您可以使用 sendto() 而不是 write() .

    另一个错误("Address already in use")是因为只有一个进程可以 bind() 到一个地址 .

    您需要更改方法以将此考虑在内 . 您的服务器需要侦听一个众所周知的地址,使用 bind() 进行设置 . 您的客户需要在此地址向服务器发送消息,以注册他们对接收数据报的兴趣 . 服务器将使用 recvfrom() 从客户端接收注册消息,并记录每个客户端使用的地址 . 当它想要发送消息时,它必须遍历它知道的所有客户端,使用 sendto() 依次向每个客户端发送消息 .

    或者,您可以使用本地IP多播而不是UNIX域套接字(UNIX域套接字不支持多播) .

  • 2

    如果问题是关于广播(据我所知),那么根据unix(4) - UNIX-domain protocol family,广播它不适用于UNIX域套接字:

    Unix Ns -domain协议系列不支持广播寻址或传入消息的任何形式的“通配符”匹配 . 所有地址都是其他Unix Ns -domain套接字的绝对路径名或相对路径名 .

    可能是多播可能是一个选项,但我觉得它不适用于POSIX,尽管Linux supports UNIX Domain Socket multicast .

    另见:Introducing multicast Unix sockets .

  • -1

    它会发生,因为服务器或客户端死亡之前unlink / remove for bind()文件关联 . 使用此绑定路径的任何客户端/服务器,尝试再次运行服务器 .

    解决方案:当您想要再次绑定时,只需检查该文件是否已关联,然后取消链接该文件 . 如何步骤:首先通过访问(2)检查该文件的访问权限;如果是,则取消链接(2) . 在bind()调用之前放置这段代码,位置是独立的 .

    if(!access(filename.c_str()))
        unlink(filename.c_str());
    

    更多参考资料阅读unix(7)

  • 0

    使用共享内存或命名管道会不会更容易?套接字是两个进程(在相同或不同的机器上)之间的连接 . 它不是一种大众传播方法 .

    如果您想为多个客户端提供一些东西,您可以创建一个等待连接的服务器,然后所有客户端都可以连接,并为它们提供信息 . 您可以通过使程序多线程或通过分叉进程来接受并发连接 . 服务器与多个客户端 Build 多个基于套接字的连接,而不是具有多个客户端连接到的一个套接字 .

  • -6

    您应该研究IP多播而不是Unix域 . 目前你只是想写到无处 . 如果您连接到一个客户端,您将只写入该客户端 .

    这些东西不像你认为的那样工作 .

  • 37

    您可以使用以下代码解决绑定错误:

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

    使用UDP协议,如果要使用 write()send() ,则必须调用 connect() ,否则应使用 sendto() .

    为了满足您的要求,以下伪代码可能会有所帮助:

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

相关问题