通过LIBUV管道发送和接收文件描述符

问题描述

我必须将某些共享内存缓冲区的文件描述符从一个进程发送到另一个进程。我可以通过UNIX域套接字直接传输fds,如下所示:

发送FD:

static void send_fds(int socket,int* fds,int n)  // send fd by socket
{
  struct msghdr msg = {0};
  struct cmsghdr* cmsg;
  char buf[CMSG_SPACE(n * sizeof(int))],dup[256];
  memset(buf,'\0',sizeof(buf));
  struct iovec io = {.iov_base = &dup,.iov_len = sizeof(dup)};

  msg.msg_iov = &io;
  msg.msg_iovlen = 1;
  msg.msg_control = buf;
  msg.msg_controllen = sizeof(buf);

  cmsg = CMSG_FirsTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));

  memcpy((int*)CMSG_DATA(cmsg),fds,n * sizeof(int));

  if (sendmsg(socket,&msg,0) < 0)
    printf("Failed to send message\n");
}

接收FD:

static int* recv_fds(int socket,int n) {
  int* fds = (int*)malloc(n * sizeof(int));
  struct msghdr msg = {0};
  struct cmsghdr* cmsg;
  char buf[CMSG_SPACE(n * sizeof(int))],.iov_len = sizeof(dup)};

  msg.msg_iov = &io;
  msg.msg_iovlen = 1;
  msg.msg_control = buf;
  msg.msg_controllen = sizeof(buf);

  if (recvmsg(socket,0) < 0)
    printf("Failed to receive message\n");

  cmsg = CMSG_FirsTHDR(&msg);

  memcpy(fds,(int*)CMSG_DATA(cmsg),n * sizeof(int));

  return fds;
}

但是当我通过libuv_pipe_t提供的抽象libuv使用域套接字时,无法传输fds。 libuv是否提供任何在服务器管道和客户端管道之间传输文件描述符的方法?如果是,如何准确发送和接收fds?

uv_pipe_t服务器:

#include <assert.h>
#include <memory.h>
#include <stdlib.h>
#include <unistd.h>

#include <uv.h>

#define SOCKET_NAME "socket_name"

void alloc_buffer(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {
  static char buffer[1024];
  buf->base = buffer;
  buf->len = sizeof(buffer);
}

void read_cb(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {
  if (nread < 0) {
    uv_stop(stream->loop);
    uv_close((uv_handle_t*)stream,NULL);
  }
  printf("message received : %s\n",buf->base);
}

static void connection_cb(uv_stream_t* server,int status) {
  printf("new connection recevied\n");
  int r;
  uv_pipe_t connection;
  r = uv_pipe_init(server->loop,&connection,0);
  assert(r == 0);
  r = uv_accept(server,(uv_stream_t*)&connection);
  assert(r == 0);

  r = uv_read_start((uv_stream_t*)&connection,alloc_buffer,read_cb);
  assert(r == 0);
}

int main() {
  uv_pipe_t p;
  int r;

  r = uv_pipe_init(uv_default_loop(),&p,0);
  assert(r == 0);

  unlink(SOCKET_NAME);
  r = uv_pipe_bind(&p,SOCKET_NAME);
  assert(r == 0);

  r = uv_listen((uv_stream_t*)&p,128,connection_cb);
  assert(r == 0);

  printf("listening...\n");
  uv_run(uv_default_loop(),UV_RUN_DEFAULT);
  uv_loop_close(uv_default_loop());
}

uv_pipe_t客户端:

#include <assert.h>
#include <memory.h>

#include <uv.h>

#define SOCKET_NAME "socket_name"

static void connect_cb(uv_connect_t* connect_req,int status) {
  printf("connected!");

  // send file descriptor
  int fds[2];
  fds[0] = fileno(fopen("fd_test_0.txt","w"));
  fds[1] = fileno(fopen("fd_test_1.txt","w"));
  
  // how to send "fds" array to server ?
}

int main() {
  uv_pipe_t p;
  uv_connect_t conn_req;
  int r;
  r = uv_pipe_init(uv_default_loop(),0);
  assert(r == 0);

  uv_pipe_connect(&conn_req,SOCKET_NAME,connect_cb);

  uv_run(uv_default_loop(),UV_RUN_DEFAULT);
  uv_loop_close(uv_default_loop());
}

example显示了如何在管道上转移管道句柄,但这不能完全解决我的问题。

感谢您的帮助!

解决方法

在 libuv 文档中对此进行了解释:http://docs.libuv.org/en/v1.x/guide/processes.html#sending-file-descriptors-over-pipes

您需要:

  • uv_pipe_init 两侧的 ipc 参数设置为 1
  • 在客户端使用 uv_write2 发送文件描述符
  • 通过uv_accept在服务器上接受它,然后从uv_stream_t获取实际传输的fd号码 使用 uv_fileno 接受参数。

但医生也说:

libuv 目前仅支持通过管道发送 TCP 套接字或其他管道。

因此它可能不适用于您的内存缓冲区文件描述符。