【发布时间】:2014-07-04 09:55:30
【问题描述】:
我正在尝试与同一 Linux 机器上的另一个本地进程共享一个套接字描述符。这些进程是“不相关的”,即它们与父/子无关,也没有分叉。他们是独立的。最终,我想要的流程是这样的:
| [Process1]
| -> master_socket = socket()
| -> setsockopt(master_socket...)
| -> fcntl(master_socket...)
| -> bind(master_socket...)
| -> listen(master_socket...)
| -> Share master_socket with Process2
|
| [Process2]
| -> Receive and store master_socket
| -> Use select() on master_socket
| -> Use accept() on master_socket to receive connections...
基于一些相关的线程,这似乎可以使用 Unix 域套接字来跟踪套接字句柄是从内核中的 Process1 发送到 Process2 的,并授予它权限(例如,here,@ 987654322@,and here)。
我要确定的是描述符是否可以通过 POSIX 消息队列共享。奇怪的是,如果我在打开队列之前创建套接字,它似乎可以正常工作.但是,如果我在打开队列后创建套接字,则在 Process2 上读取的描述符显示为“无效”。
示例程序
这是一个通过消息队列发送套接字描述符的示例程序。如果 init_socket() 在 open_queue() 之前被调用,那么接收到的描述符是有效的。如果反之亦然,则它会遇到无效。
send.c:gcc -o send send.c -lrt
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <string.h>
#include <errno.h>
#include <mqueue.h>
int init_socket();
int open_queue();
mqd_t msgq_id;
int main() {
int socket;
char msg_str[16];
// HERE: ordering matters. If open_queue() is done before init_socket(), then
// the descriptor is received invalid. If init_socket() is called BEFORE open_queue()
// then the descriptor received is valid.
open_queue();
socket = init_socket();
// Put the socket on the queue
memset(msg_str, '\0', sizeof(msg_str));
snprintf(msg_str, sizeof(msg_str), "%d", socket);
if(mq_send(msgq_id, msg_str, strlen(msg_str)+1, 1) == -1) {
printf("Unable to send the message on the queue: %s\n", strerror(errno));
return -1;
}
}
int open_queue() {
// Create a queue to share the socket
if(msgq_id = mq_open("/share_socket", O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, NULL)==-1) {
if(errno != EEXIST) {
printf("Failed to create IPC queue: %s\n", strerror(errno));
return -1;
}
// Re-open the already existing queue.
if((msgq_id = mq_open("/share_socket", O_RDWR)) != -1) {
printf("Re-opened the IPC queue: %s\n", "/share_socket");
} else {
printf("Failed to re-open IPC queue %s: %s\n", "/share_socket", strerror(errno));
return -1;
}
}
return 1;
}
int init_socket() {
int master_socket;
int opt=1;
struct sockaddr_in loc_addr = { 0 };
// Create the high level master socket
if( (master_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
printf("Unable to create master_socket\n");
return EXIT_FAILURE;
}
// Set socket to accept multiple connections
if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 ) {
printf("Error setting socket to accept multiple connections\n");
return EXIT_FAILURE;
}
// Set the socket type
bzero(&loc_addr, sizeof(struct sockaddr_in));
loc_addr.sin_family = AF_INET;
loc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
loc_addr.sin_port=htons(1200);
// Set the socket to nonblocking
if (fcntl(master_socket, F_SETFL, O_NDELAY) < 0) {
printf("Can't set socket to non-blocking\n");
return EXIT_FAILURE;
}
// Bind to the socket
if (bind(master_socket, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) {
return EXIT_FAILURE;
}
// Now, listen for a maximum of 6 pending clients
if(listen(master_socket, 6) < 0) {
printf("Could not set the socket to listen\n");
close(master_socket);
return EXIT_FAILURE;
}
return master_socket;
}
read.c:gcc -o read read.c -lrt
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <string.h>
#include <errno.h>
#include <mqueue.h>
#define MAX_MSG_LEN 10000
int main() {
mqd_t msgq_id;
int master_socket;
unsigned int sender;
int bytes;
char msgcontent[MAX_MSG_LEN];
// Re-open the already existing queue.
if((msgq_id = mq_open("/share_socket", O_RDWR)) != -1) {
printf("Re-opened the IPC queue: %s\n", "/share_socket");
} else {
printf("Failed to re-open IPC queue %s: %s\n", "/share_socket", strerror(errno));
return -1;
}
// Try to read from the queue.
if((bytes = mq_receive(msgq_id, msgcontent, MAX_MSG_LEN, &sender)) == -1)
{
// If the failure was due to there being no messages, just return 0.
if(errno==EAGAIN)
return 0;
printf("Unable to read from the queue\n");
return -1;
}
sscanf(msgcontent, "%d", &master_socket);
printf("Got master socket value: %d\n", master_socket);
if(master_socket != 0 && (fcntl(master_socket, F_GETFD) != -1 || errno != EBADF))
printf("... socket is valid\n");
else
printf("... SOCKET IS INVALID\n");
return 1;
}
如何运行:继续运行./read,它将在消息队列中等待,然后运行./send。如果你在 open_queue() 之前用 init_socket() 编译了 send.c,你会看到:
$ ./read
Re-opened the IPC queue: /share_socket
Got master socket value: 3
... socket is valid
否则:
$ ./read
Re-opened the IPC queue: /share_socket
Got master socket value: 4
... SOCKET IS INVALID
在排序很重要的情况下,什么会导致这种行为?
【问题讨论】:
-
简短的回答是您不能通过 msg 队列将文件描述符传递给另一个独立进程。这适用于 unix 域套接字,因为特别为该设施内置了内核魔法。 MQ 不共享该功能。为什么你看到你声称的结果肯定是一个编程错误。
-
谢谢,鸭子。我读过它们是为了通过 unix 域套接字传递的。但是,我认为这些程序中的任何一个都没有编程错误。事实上,只要使用顺序 init_socket() 然后 open_queue() 从 Process1 发送有效,我就可以在 Process2 中的套接字上使用 select() 和 read()。这就是我觉得奇怪的地方。
-
在下面查看我的答案。在 Linux 中,消息队列描述符 (mqd_t) 是一个文件描述符,因此使用
select是允许的,有时也非常方便。我不能说我曾经尝试过使用read队列,所以我有点好奇结果如何。
标签: c linux sockets queue posix