在C/Unix中的Socketpair()函数

42
我在同一系统上有2个应用程序,需要相互通信。根据我的研究,我认为这被称为进程间通信,使用socketpair()是解决我的问题的最佳方法。
我正在竭尽全力(真的)尝试用C语言创建socketpair()套接字。据我所知,socket是一个非常复杂的主题,而我作为一个初学者的C程序员肯定没有帮助到这种情况。
我谷歌了最后48小时,阅读了教程等,但仍然无法理解。我理解概念,但代码太混乱了。我已经多次阅读了这篇文章:http://beej.us/guide/bgnet/html/single/bgnet.html,但它并不够简单。
有人能提供一些例子(如此简单,以至于一个5年级的孩子都能理解),或者指点我一个好的教程吗?

7
请不要真的拔掉你的头发,听起来很痛苦。我们需要回答的第一个问题是:这两个程序是否由共同的祖先连接?也就是说,一个程序启动另一个程序吗?还是它们由你编写的第三个程序启动?或者它们彼此独立地启动? - Robᵩ
1
IPC不是关于套接字的,它是完全不同的东西。你不应该按照你发布的链接去做,而是应该按照这个链接去做:http://beej.us/guide/bgipc/output/html/multipage/unixsock.html#socketpair - Aftnix
1
这两个程序由一个共同的祖先连接。 - NASA Intern
那个例子很棒!正是我在寻找的! - NASA Intern
3
我猜你之前在找那个“仁慈”的食品补充剂,你链接的文章第三段就有这个好吃的小东西:“你可以立即使用这些套接字描述符进行进程间通信。” 在我的首字母缩略词书中,这代表IPC。还有,“dude”的用户名怎么看都很牛逼啊!除非他/她在开玩笑,不然他/她可能真的是一个火箭科学家,值得更多的尊重(我个人认为,就此而已)。 - ack
4个回答

77
您只能在创建两个进程的情况下使用socketpair,具体步骤如下:
  1. 调用socketpair - 现在您有两个套接字文件描述符(单个管道的两端)。
    • 指定其中一个端口为父进程,另一个为子进程端口。哪个是哪个不重要,只需要选择一种并在以后坚持即可
  2. 调用fork - 现在您有两个进程。
    1. 如果fork返回0,则为子进程。关闭parent文件描述符,保留child描述符,并将其用作此进程的管道端点
    2. 如果fork返回非零值,则为父进程。关闭child文件描述符,保留parent描述符,并将其用作您的管道端点
  3. 现在您有两个进程,每个进程都有一个表示同一管道不同端的文件描述符。请注意,两个进程都运行相同的程序,但在调用fork后,它们遵循了不同的分支。如果parent在其套接字上调用write,则child将能够从它的套接字读取该数据,反之亦然。

这是一份直译成代码的示例:

void child(int socket) {
    const char hello[] = "hello parent, I am child";
    write(socket, hello, sizeof(hello)); /* NB. this includes nul */
    /* go forth and do childish things with this end of the pipe */
}

void parent(int socket) {
    /* do parental things with this end, like reading the child's message */
    char buf[1024];
    int n = read(socket, buf, sizeof(buf));
    printf("parent received '%.*s'\n", n, buf);
}

void socketfork() {
    int fd[2];
    static const int parentsocket = 0;
    static const int childsocket = 1;
    pid_t pid;

    /* 1. call socketpair ... */
    socketpair(PF_LOCAL, SOCK_STREAM, 0, fd);

    /* 2. call fork ... */
    pid = fork();
    if (pid == 0) { /* 2.1 if fork returned zero, you are the child */
        close(fd[parentsocket]); /* Close the parent file descriptor */
        child(fd[childsocket]);
    } else { /* 2.2 ... you are the parent */
        close(fd[childsocket]); /* Close the child file descriptor */
        parent(fd[parentsocket]);
    }
    exit(0); /* do everything in the parent and child functions */
}

请注意这只是示例代码:我省略了所有的错误检查和合理的流协议。


如果您想要两个 独立的 程序进行通信(例如,您有一个名为 client 的可执行文件和一个名为 server 的可执行文件),那么您不能使用此机制。 相反,您可以:

  • 使用UNIX套接字(其中一个主机上的IPC管道由文件名标识 - 仅在 clientserver 在同一台机器上运行时才有效)
  • 或使用TCP/IP套接字(其中IP地址和端口标识管道,并且clientserver可以在不同的计算机上)

如果您没有 特别 需要套接字,并且希望要求 clientserver 运行在同一台计算机上,您还可以使用共享内存或消息队列。


3
实际上,您可以将描述符传递给不相关的进程:请参见sendmsg/recvmsg和“描述符传递”。这似乎是一个受欢迎的搜索 :) - fork0
为什么在子进程中要“关闭父文件描述符”,而在父进程中要“关闭子文件描述符”? - Gary Gauh
孩子端和父亲端都使用同一个套接字,这样会有竞争条件,所以让孩子去触碰父亲端的管道是个坏主意,反之亦然。当他们每个人都使用不同的套接字时,就可以避免这种风险。如果不能安全地使用该文件描述符,则为什么要保持它处于打开状态呢? - Useless
1
当然:它们仍然只是管道的两端,一个子进程和另一个子进程并没有什么特别之处。 - Useless
1
为什么在父进程和子进程中都要关闭套接字的另一端?这是必要的吗?并且,socketpair 可以用于在同一进程的两个线程之间进行通信吗? - Guillaume Brunerie
显示剩余4条评论

9
socketpair 函数创建一对匿名的 socket,通常是 Unix/本地 socket,在父子进程间或者其他需要使用共同祖先的文件描述符继承的情况下非常有用。
如果你需要在没有关联(在父子进程层面上)的进程之间进行通信,你需要使用 socketbindconnect 函数,在一个进程中创建监听 socket,然后在另一个进程中创建客户端 socket 连接到它。

1
奇怪的是,我通过简单搜索“_socketpair示例代码_”就找到了一些。我已经提供了一些,但我很惊讶这是必要的。 - Useless
@Useless:那个搜索听起来很无用,因为我指出socketpair可能不是OP想要做的正确工具。 - R.. GitHub STOP HELPING ICE

1

如果要在两个进程之间进行通信,那么你应该寻找的是进程间通信(Inter Process Communication,IPC)。

套接字(Sockets)只是通信的一种方法,如果你需要实现一对多的连接,那么它就非常有用。也就是说,一个服务器进程与多个客户端进程以请求-响应方式进行通信。由于你是 IPC 的新手,所以理解套接字地址和相关细节可能会比较困难。(不过随着时间的推移,你会发现它们很容易掌握的 :-))

针对你的问题,我建议你使用更简单的 IPC 机制,例如管道(Pipe)、命名管道(FIFO)或消息队列(Message Queue)。

我不确定你是如何得出使用 socketpair 的结论的。既然你没有提到任何关于所需 IPC 的设计或类型的信息,并且基于使用级别,我强烈建议你查阅一些书籍或互联网上的 Pipe 或 FIFO 示例代码。它们应该比套接字更容易实现并且速度更快。


1
FIFO是什么意思(帖子建议它与Pipe不同)? - domen

-4

使用TCP/IP。虽然还有其他IPC机制可用(例如Unix域套接字和SYSV IPC),但出于许多原因,您最好选择TCP/IP。以下是一些原因:

  1. 网络上有很多教程和其他信息描述如何使用TCP/IP
  2. 现代系统,特别是Linux和*BSD,与使用Unix域套接字或甚至SYSV IPC相比,对使用TCP/IP没有明显的惩罚。
  3. 有许多库和框架可供您用于通过TCP/IP通信的应用程序。

唯一不使用TCP/IP在两个“程序”之间进行通信的情况是它们实际上是线程而不是单独的程序。


7
我不同意。在本地进程之间使用TCP/IP进行IPC有很多缺点,最大的缺点是你必须关注与远程主机或甚至本地主机上的进程可能存在的安全问题相关联的连接。Unix(本地)套接字更容易保护,并且如果您将传输数据以主机的二进制格式,它们更加适用。您还可以执行诸如通过它们传递文件描述符等有用的事情。 - R.. GitHub STOP HELPING ICE
5
Unix套接字速度肯定更快。只需考虑TCP必须经过创建IP数据包、通过环回设备发送它、应用过滤规则等所有循环即可。 - Per Johansson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接