【问题标题】:Shared socket descriptor invalid over message queue when socket opened after queue队列后打开套接字时,共享套接字描述符在消息队列上无效
【发布时间】: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.cgcc -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.cgcc -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


【解决方案1】:

问题一:

这两个程序都将以打开且有效的文件描述符 0、1 和 2 开头。

当您使用init_socket 运行send 时,首先返回的套接字文件描述符为3。然后您打开队列为4。您将3 发送到read 进程。

read 中,您打开队列,该队列将是文件描述符 3。您读取队列并发现您收到了一个 3,这实际上是 queue 的 fd。所以你正在“测试”队列文件描述符。

相反,在send 中,您先打开队列,然后再打开套接字,您将文件描述符4 发送到readread 中没有打开文件描述符 4(队列仍将打开为 3),因此它自然无法通过“测试”。

问题 2:

测试错误。

if (master_socket != 0 && (fcntl(master_socket, F_GETFD) != -1 || errno != EBADF))
    printf("... socket is valid\n");
else
    printf("... SOCKET IS INVALID\n");

这一切都在告诉你,它不是文件描述符 0,你可以读取文件描述符标志。消息队列文件描述符将通过的测试。

更好的测试是这样的:

void isSocket(int fd)
{
    struct stat statbuf;

    if (fstat(fd, &statbuf) == -1)
    {
        perror("fstat");
        exit(1);
    }

    if (S_ISSOCK(statbuf.st_mode))
        printf("%d is a socket\n", fd);
    else
        printf("%d is NOT a socket\n", fd);
}

我还没有测试过你的 pgms,但是如果你在打开两个程序时打印出你所有的文件描述符并尝试上面的测试,它应该能证明我在说什么。

【讨论】:

  • 衷心感谢您花费时间和精力来解释这一点和您的回复。这是完全有道理的,经过一些测试,你是对的:它不会在 select() 上给出错误,但也没有任何东西通过套接字,因为它是队列描述符。你帮我省了很多麻烦。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-15
  • 1970-01-01
相关资源
最近更新 更多