如何在父子进程间传递socket

10

我在Linux上的C程序遇到了一个问题。

我知道当一个进程被fork时,子进程会继承一些来自父进程的东西,包括打开的文件描述符。

问题是,我正在编写一个多进程服务器应用程序,其中有一个主进程接受新连接并将描述符放入共享内存中。

当子进程尝试从共享内存中的描述符读取数据时,在select()操作中出现了EBADF错误!

在fork之后,子进程如何读取和使用父进程创建的套接字(或任何文件描述符)?


1
多进程服务器的另一种方法是让所有子进程都调用 accept。在 Linux 上,一个非标准的方法是使用 cloneCLONE_FILES 代替 fork,以便进程共享文件描述符。 - Timothy Baldwin
2个回答

17
当你调用fork时,子进程会继承所有打开的文件描述符的副本。通常的做法是父进程打开一个监听套接字,调用accept阻塞等待连接到来,然后在收到连接后调用fork。然后父进程关闭它的文件描述符副本,而新的子进程可以继续使用文件描述符并进行任何所需的处理。一旦子进程完成,它也会关闭套接字。记住两件事很重要:1. 文件描述符/套接字是操作系统中的资源,在分叉之后,父进程和子进程都有该资源的句柄,这有点像引用计数智能指针。我在这里更详细地解释了这一点。第二件事是,只有在调用fork之前打开的文件描述符才会被共享,因为在分叉后,父进程和子进程是完全独立的进程,即使它们可能共享一些在分叉之前存在的资源,如文件描述符。如果您正在使用一种模型,希望父进程将工作分配给工作进程,那么考虑使用线程和线程池可能更好。

顺便说一句,你可以从UNIX网络编程网站下载许多服务器和客户端的好例子。


13
你无法通过共享内存从一个进程传输套接字(或任何其他文件描述符)到另一个进程。文件描述符只是一个小整数。将此整数放入共享内存并从另一个进程访问它并不能自动使该整数成为另一个进程中有效的文件描述符。
正确的方法是将文件描述符作为SCM_RIGHTS附加数据通过两个进程之间的现有套接字通信渠道使用sendmsg()发送到另一个进程。
首先,在fork()之前使用socketpair()创建通信通道。现在,在父进程中关闭套接字对的一端,在子进程中关闭另一端。现在,您可以在此套接字的一端从父进程中使用sendmsg()发送消息,并在子进程中使用另一端的recvmsg()接收消息。
使用SCM_RIGHTS发送消息看起来像这样:
struct msghdr m;
struct cmsghdr *cm;
struct iovec iov;
char buf[CMSG_SPACE(sizeof(int))];
char dummy[2];    

memset(&m, 0, sizeof(m));
m.msg_controllen = CMSG_SPACE(sizeof(int));
m.msg_control = &buf;
memset(m.msg_control, 0, m.msg_controllen);
cm = CMSG_FIRSTHDR(&m);
cm->cmsg_level = SOL_SOCKET;
cm->cmsg_type = SCM_RIGHTS;
cm->cmsg_len = CMSG_LEN(sizeof(int));
*((int *)CMSG_DATA(cm)) = your_file_descriptor_to_send;
m.msg_iov = &iov;
m.msg_iovlen = 1;
iov.iov_base = dummy;
iov.iov_len = 1;
dummy[0] = 0;   /* doesn't matter what data we send */
sendmsg(fd, &m, 0);

收到带有 SCM_RIGHTS 的消息大致如下:
struct msghdr m;
struct cmsghdr *cm;
struct iovec iov;
struct dummy[100];
char buf[CMSG_SPACE(sizeof(int))];
ssize_t readlen;
int *fdlist;

iov.iov_base = dummy;
iov.iov_len = sizeof(dummy);
memset(&m, 0, sizeof(m));
m.msg_iov = &iov;
m.msg_iovlen = 1;
m.msg_controllen = CMSG_SPACE(sizeof(int));
m.msg_control = buf;
readlen = recvmsg(fd, &m, 0);
/* Do your error handling here in case recvmsg fails */
received_file_descriptor = -1; /* Default: none was received */
for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) {
    if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) {
        nfds = (cm->cmsg_len - CMSG_LEN(0)) / sizeof(int);
        fdlist = (int *)CMSG_DATA(cm);
        received_file_descriptor = *fdlist;
        break;
    }
}

我在谈论父/子进程,所以问题是:fork之后子进程如何访问父进程创建的文件描述符? - user1995143
对于一个好的回答点个赞,但是在我看来,他最好使用线程。 - Robert S. Barnes
@user1995143 这正是我回答的问题。无论这两个进程的父子关系如何,只要你有一个UNIX域套接字作为通信渠道,就可以使用这种技术传输文件描述符。或者你可以遵循Robert S. Barnes的建议,使用多线程而不是多个进程。然后你就不必担心了,因为文件描述符在单个进程的所有线程之间共享。 - Celada

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