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

Big*_*ick 34 c sockets datagram

我想在Linux上的几个进程之间建立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);
Run Code Online (Sandbox Code Playgroud)

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

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

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);
Run Code Online (Sandbox Code Playgroud)

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

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

ada*_*mar 39

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

您看到"地址已在使用中"的原因是您告诉客户端绑定到与服务器相同的地址.bind()是关于断言外部身份.两个套接字通常不能具有相同的名称.

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

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);
Run Code Online (Sandbox Code Playgroud)

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

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

if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
    perror("socket failed");
}
Run Code Online (Sandbox Code Playgroud)

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

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

顺便提一下,参考

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

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

  • 以“A UDP 套接字通常不会抱怨”和“这使得 unix 域数据报套接字远优于 UDP”开头的句子都是不正确的。您对 Unix 域数据报套接字所说的大部分(如果不是全部)同样适用于 IP 域 UDP 套接字:具体来说,它们必须连接才能使用 `write()`,并且它们阻塞在 `write() 中` 或 `send()`,而发送缓冲区已满。-1 错误信息。 (2认同)

caf*_*caf 7

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

另一个错误("已在使用的地址")是因为只有一个进程可以bind()到达一个地址.

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

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


Hib*_*u57 5

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

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

多播可能是一种选择,但我觉得它不适用于 POSIX,尽管Linux 支持 UNIX Domain Socket multicast

另请参阅:介绍多播 Unix 套接字


use*_*421 -5

你应该研究 IP 多播而不是 Unix 域的任何东西。目前你只是想写到无处可去。如果您连接到一个客户端,您将只向该客户端写入数据。

这些东西并不像你想象的那样工作。