在队列之后打开套接字时,共享套接字描述符对消息队

时间:2014-05-15 19:21:51

标签: c linux sockets queue posix

我正在尝试与同一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,给予它许可(例如herehereand here)。

我想确定的是描述符是否可以通过POSIX消息队列共享。奇怪的是,如果我在打开队列之前创建套接字,它似乎工作正常。但是,如果我在打开队列后创建套接字,则在Process2上读取的描述符将显示为"无效。"


示例程序

这是一个通过消息队列发送套接字描述符的示例程序。如果在open_queue()之前调用init_socket(),则接收的描述符有效。如果反之亦然则会发现无效。

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

什么会导致排序很重要的行为?

1 个答案:

答案 0 :(得分:1)

问题一:

两个程序都将以文件描述符0,1和&amp; 2开放且有效。

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

read中打开将成为文件描述符3的队列。您读取队列并发现发送了3,实际上是队列的fd。所以你是&#34;测试&#34;队列文件描述符。

相反,当在send中首先打开队列然后再将套接字打开时,您将文件描述符4发送到read。在read中没有打开文件描述符4(队列仍将打开为3)因此它自然会失败&#34;测试&#34;。

问题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,但如果你在打开它们时打印出两个程序中的所有文件描述符并尝试上面的测试,它应该暴露出我所说的内容。