I'm stuck on a problem in a C program on Linux.
我在Linux上的C程序中遇到了问题。
I know that when a processes is forked the child process inherits some things from the parent, including open file descriptors.
我知道当一个进程被分叉时,子进程从父进程继承了一些东西,包括打开文件描述符。
The problem is that I'm writing a multi-process server application with a master process that accepts new connections and puts the descriptors into shared memory.
问题是我正在编写一个多进程服务器应用程序,其主进程接受新连接并将描述符放入共享内存。
When the child process attempts to read from one of these descriptors from the shared memory, on select()
i got an EBADF
error!
当子进程尝试从共享内存中读取其中一个描述符时,在select()上我得到了一个EBADF错误!
How can the child process read and use a socket (or any file descriptor in general) created by a parent process after it has been forked?
子进程如何读取并使用父进程分叉后创建的套接字(或一般的任何文件描述符)?
2 个解决方案
#1
12
When you call fork, the child process inherits copies of all open file descriptors. The typical way of doing this is for a parent process to open a listening socket, call accept which blocks until a connection arrives and then calls fork after receiving the connection. The parent then closes it's copy of the file descriptor, while the new child process can keep using the file descriptor and do any processing which is needed. Once the child is done it also closes the socket. It's important to remember two things: 1. The file descriptor / socket is a resource in the operating system and after the fork the parent and child each have a handle to that resource, which is kind of like a reference counted smart pointer. I explain this in more detail here. The second thing is that only file descriptors which are opened before calling fork are shared, because after forking parent and child are completely separate processes, even though they may share some resources like file descriptors which existed prior to the fork. If you're using a model where you want to have a parent handing out work to worker processes, it may be better for you to consider using threads, and a thread pool.
当您调用fork时,子进程继承所有打开的文件描述符的副本。执行此操作的典型方法是父进程打开侦听套接字,调用accept阻塞直到连接到达,然后在接收连接后调用fork。然后父进程关闭它的文件描述符副本,而新的子进程可以继续使用文件描述符并进行所需的任何处理。孩子完成后,它也会关闭插座。记住两件事是很重要的:1。文件描述符/套接字是操作系统中的资源,在fork之后,父和子都有一个到该资源的句柄,这有点像引用计数的智能指针。我在这里更详细地解释这一点。第二件事是,只有在调用fork之前打开的文件描述符才被共享,因为在分配父和子之后是完全独立的进程,即使它们可能共享一些资源,比如在fork之前存在的文件描述符。如果您正在使用一个模型,您希望让父项将工作分配给工作进程,那么考虑使用线程和线程池可能更好。
By the way, you can download allot of nice examples of servers and clients from Unix Network Programming website.
顺便说一下,你可以从Unix网络编程网站下载很多很好的服务器和客户端例子。
#2
11
You cannot transmit a socket (or any other file descriptor) from one process to another through shared memory. A file descriptor is just a small integer. Placing this integer in shared memory and accessing it from another process does not automatically make the same integer into a valid file descriptor from the point of view of the other process.
您不能通过共享内存将套接字(或任何其他文件描述符)从一个进程传输到另一个进程。文件描述符只是一个小整数。将此整数放在共享内存中并从另一个进程访问它不会从另一个进程的角度自动将相同的整数转换为有效的文件描述符。
The correct way to send a file descriptor from one process to another is to send it as SCM_RIGHTS
ancillary data with sendmsg()
through an existing socket communication channel between the two processes.
将文件描述符从一个进程发送到另一个进程的正确方法是使用sendmsg()通过两个进程之间的现有套接字通信通道将其作为SCM_RIGHTS辅助数据发送。
First, create your communication channel with socketpair()
before you fork()
. Now, in the parent, close one end of the socket pair, and, in the child, close the other end. You can now sendmsg()
from the parent on one end of this socket and receive with recvmsg()
in the child using the other end.
首先,在fork()之前使用socketpair()创建通信通道。现在,在父级中,关闭套接字对的一端,并在子级中关闭另一端。您现在可以在此套接字的一端从父级发送sendms(),并使用另一端在子级中使用recvmsg()接收。
Sending a message with SCM_RIGHTS
looks something like this:
使用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);
Receiving a message with SCM_RIGHTS
in it goes something like this:
在其中接收带有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;
}
}
#1
12
When you call fork, the child process inherits copies of all open file descriptors. The typical way of doing this is for a parent process to open a listening socket, call accept which blocks until a connection arrives and then calls fork after receiving the connection. The parent then closes it's copy of the file descriptor, while the new child process can keep using the file descriptor and do any processing which is needed. Once the child is done it also closes the socket. It's important to remember two things: 1. The file descriptor / socket is a resource in the operating system and after the fork the parent and child each have a handle to that resource, which is kind of like a reference counted smart pointer. I explain this in more detail here. The second thing is that only file descriptors which are opened before calling fork are shared, because after forking parent and child are completely separate processes, even though they may share some resources like file descriptors which existed prior to the fork. If you're using a model where you want to have a parent handing out work to worker processes, it may be better for you to consider using threads, and a thread pool.
当您调用fork时,子进程继承所有打开的文件描述符的副本。执行此操作的典型方法是父进程打开侦听套接字,调用accept阻塞直到连接到达,然后在接收连接后调用fork。然后父进程关闭它的文件描述符副本,而新的子进程可以继续使用文件描述符并进行所需的任何处理。孩子完成后,它也会关闭插座。记住两件事是很重要的:1。文件描述符/套接字是操作系统中的资源,在fork之后,父和子都有一个到该资源的句柄,这有点像引用计数的智能指针。我在这里更详细地解释这一点。第二件事是,只有在调用fork之前打开的文件描述符才被共享,因为在分配父和子之后是完全独立的进程,即使它们可能共享一些资源,比如在fork之前存在的文件描述符。如果您正在使用一个模型,您希望让父项将工作分配给工作进程,那么考虑使用线程和线程池可能更好。
By the way, you can download allot of nice examples of servers and clients from Unix Network Programming website.
顺便说一下,你可以从Unix网络编程网站下载很多很好的服务器和客户端例子。
#2
11
You cannot transmit a socket (or any other file descriptor) from one process to another through shared memory. A file descriptor is just a small integer. Placing this integer in shared memory and accessing it from another process does not automatically make the same integer into a valid file descriptor from the point of view of the other process.
您不能通过共享内存将套接字(或任何其他文件描述符)从一个进程传输到另一个进程。文件描述符只是一个小整数。将此整数放在共享内存中并从另一个进程访问它不会从另一个进程的角度自动将相同的整数转换为有效的文件描述符。
The correct way to send a file descriptor from one process to another is to send it as SCM_RIGHTS
ancillary data with sendmsg()
through an existing socket communication channel between the two processes.
将文件描述符从一个进程发送到另一个进程的正确方法是使用sendmsg()通过两个进程之间的现有套接字通信通道将其作为SCM_RIGHTS辅助数据发送。
First, create your communication channel with socketpair()
before you fork()
. Now, in the parent, close one end of the socket pair, and, in the child, close the other end. You can now sendmsg()
from the parent on one end of this socket and receive with recvmsg()
in the child using the other end.
首先,在fork()之前使用socketpair()创建通信通道。现在,在父级中,关闭套接字对的一端,并在子级中关闭另一端。您现在可以在此套接字的一端从父级发送sendms(),并使用另一端在子级中使用recvmsg()接收。
Sending a message with SCM_RIGHTS
looks something like this:
使用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);
Receiving a message with SCM_RIGHTS
in it goes something like this:
在其中接收带有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;
}
}