如何将套接字从父进程传递到子进程

时间:2013-01-20 18:41:18

标签: c sockets webserver fork file-descriptor

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

我知道当一个进程被分叉时,子进程从父进程继承了一些东西,包括 open 文件描述符。

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

当子进程尝试从共享内存中读取其中一个描述符时,在select()上出现EBADF错误!

子进程如何在分叉之后读取并使用由父进程创建的套接字(或一般的任何文件描述符)?

2 个答案:

答案 0 :(得分:15)

当您调用fork时,子进程会继承所有打开的文件描述符的副本。执行此操作的典型方法是父进程打开侦听套接字,调用accept阻塞直到连接到达,然后在接收连接后调用fork。然后父节点关闭它的文件描述符副本,而新的子进程可以继续使用文件描述符并进行所需的任何处理。孩子完成后,关闭套接字。记住两件事是很重要的:1。文件描述符/套接字是操作系统中的资源,在fork之后,父和子每个都有一个该资源的句柄,这有点像引用计数的智能指针。我解释一下in more detail here。第二件事是,只有在调用fork之前打开的文件描述符才会被共享,因为在分配父和子之后是完全独立的进程,即使它们可能共享某些资源,如之前存在的文件描述符。叉子。如果您正在使用一个模型,您希望让父项将工作分发给工作进程,那么考虑使用线程和a thread pool可能更好。

顺便说一句,您可以从Unix Network Programming网站下载各种服务器和客户端的好例子。

答案 1 :(得分:13)

您无法通过共享内存将套接字(或任何其他文件描述符)从一个进程传输到另一个进程。文件描述符只是一个小整数。将此整数放在共享内存中并从另一个进程访问它不会从另一个进程的角度自动将相同的整数转换为有效的文件描述符。

将文件描述符从一个进程发送到另一个进程的正确方法是通过两个进程之间的现有套接字通信通道将SCM_RIGHTS辅助数据与sendmsg()一起发送。

首先,在socketpair()之前使用fork()创建您的沟通渠道。现在,在父级中,关闭套接字对的一端,并在子级中关闭另一端。您现在可以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;
    }
}