44

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

一个进程接收数据(未格式化的、二进制的)并应使用数据报协议(即类似于带有 AF_INET 的 UDP)通过本地 AF_UNIX 套接字分发该数据。从该进程发送到本地 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(“传输端点未连接”)。我猜这是因为当前没有接收进程正在监听这个本地套接字,对吗?

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

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

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

提前感谢您的任何评论/解决方案!

4

7 回答 7

58

在数据报配置中使用 Unix Domain Socket 有一个技巧。与流套接字(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);

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

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

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

这几乎适用于任何 C 系统调用。

最好的参考是 Steven 的“Unix Network Programming”。在第 3 版中,第 15.4 节,第 415-419 页显示了一些示例并列出了许多警告。

顺便说一句,参考

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

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

于 2012-04-21T16:32:46.983 回答
8

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

另一个错误(“地址已在使用”)是因为只有一个进程可以bind()访问一个地址。

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

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

于 2010-07-25T04:14:38.583 回答
5

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

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

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

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

于 2013-03-29T17:26:16.933 回答
0

它会因为在取消链接/删除绑定()文件关联之前服务器或客户端死亡而发生。使用此绑定路径的任何客户端/服务器,请尝试再次运行服务器。

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

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

更多参考请阅读 unix(7)

于 2016-05-25T06:02:07.477 回答
-1

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

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

于 2010-07-24T10:14:35.183 回答
-3

您应该研究 IP 多播而不是 Unix-domain 任何东西。目前,您只是想写信给无处可去。如果你连接到一个客户端,你只会写给那个客户端。

这些东西不像你想象的那样起作用。

于 2010-07-24T10:05:58.163 回答
-6

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

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

connect()使用 UDP 协议,如果你想使用write()or ,你必须调用send(),否则你应该使用sendto()

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

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
set RESUSEADDR with setsockopt
bind()
while (1) {
   recvfrom()
   sendto()
}
于 2010-09-02T06:50:18.690 回答