10

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

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

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

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

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

4

2 回答 2

16

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

顺便说一句,您可以从Unix Network Programming网站下载一些不错的服务器和客户端示例。

于 2013-01-20T20:44:48.910 回答
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_RIGHTSin 的消息是这样的:

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;
    }
}
于 2013-01-20T18:59:59.333 回答