【Linux】跨进程传递描述符

论坛 期权论坛 脚本     
匿名技术用户   2021-1-2 09:36   26   0

本文是在其他博文基础上,进行总结汇总,以便能够通过该文章,就能够直接理解其原理和使用方法。

介绍使用方法之前,我们需要先了解几个知识点!


一、 联合的概念union


1、union中可以定义多个成员, union的大小由最大的成员的大小决定。
2、union成员共享同一块大小的内存, 一次只能使用其中的一个成员。
3、对某一个成员赋值,会覆盖其他成员的值(因为他们共享一块内存。
但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,
比如对char成员赋值就不会把整个int成员覆盖掉,
因为char只占一个字节,而int占四个字节
4、联合体union的存放顺序是所有成员都从低地址开始存放的。


二、 msghdr和cmsghdr结构体的关系


列出结构体,后面并给出了说明

struct msghdr {
    void         *msg_name;       /* optional address 通道是数据报套接口才需要设置,执行要发送或者接受信息的套接口地址*/
    socklen_t     msg_namelen;    /* size of address 套接口地址长度*/
    struct iovec *msg_iov;        /* scatter/gather array 实际的数据缓冲区*/
    size_t        msg_iovlen;     /* # elements in msg_iov */
    void         *msg_control;    /* ancillary data辅助数据, 通常指向结构体struct cmsghdr */
    socklen_t     msg_controllen; /* ancillary data buffer len */
    int           msg_flags;      /* flags on received message */
};

struct cmsghdr {
    socklen_t cmsg_len;    /* data byte count, including header */
    int       cmsg_level;  /* originating protocol */
    int       cmsg_type;   /* protocol-specific type */
    /* followed by unsigned char cmsg_data[]; */
};

三、辅助数据结构体的函数说明


1. 查询手册命令进行了解: man 3 cmsg

2. 函数说明

CMSG_FIRSTHDR()返回msghdr关联的辅助数据中的第一个cmsghdr的指针。
CMSG_SPACE()返回一个辅助数据所占用的字节数。这是一个常量表达式。
传入的参数length指的是一个控制信息元素(即一个cmsghdr结构体)后面数据部分的字节数,
返回的是这个控制信息的总的字节数,即包含了头部(即cmsghdr各成员)、数据部分和填充数据的总和。
CMSG_DATA()返回cmsghdr的数据部分指针。
CMSG_LEN()返回cmsghdr结构的cmsg_len成员的值,包括cmsghdr头结构加上附属数据大小,不包括最后填充数据的部分
3. 下面画个图说明各个数据之间的关系,以便加深理解


四、 客户端和服务端代码


客户端创建文件描述符,然后传递给服务端,服务端执行写操作。

1. 客户端代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define clog_info(...) do { \
printf("<%s %s> %s %s:%d INFO:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&& \
printf(__VA_ARGS__)&& \
printf("\n"); \
}while(0) 

#define UNIXSTR_PATH "foo.socket"
#define FILE_NAME    "test"

//保证cmsghdr和msg_control对齐
union {  
 struct cmsghdr cm;
 char control[CMSG_SPACE(sizeof(int))];
} control_un;
 
int main(int argc, char *argv[])
{
 int clifd = -1;
 int ret = -1;
    int filefd = -1;
    char buf[100] = {0};
 struct sockaddr_un servaddr; 
 struct msghdr msg;
 struct iovec iov[1];
 struct cmsghdr *pcmsg;

    // 1. 创建套接字
 clifd  = socket(AF_UNIX, SOCK_STREAM, 0) ;
 if   ( clifd  <  0 ) {
  clog_info ( "socket failed" ) ;
  return  -1 ;
 }

    // 2. 创建文件描述符
 filefd  =  open(FILE_NAME ,O_CREAT | O_RDWR, 0777);
 if( filefd  <  0 ) {
  clog_info("open test failed.");
  return -1;
 }

 bzero (&servaddr, sizeof(servaddr));
 servaddr.sun_family = AF_UNIX;
 strcpy ( servaddr.sun_path, UNIXSTR_PATH);

    // 3. 连接服务器端
 ret = connect(clifd, (struct sockaddr*)&servaddr, sizeof(servaddr));
 if(ret < 0) {
  clog_info ( "connect failed." ) ;
  return -1;
 }
 
 msg.msg_name = NULL;
 msg.msg_namelen = 0;
 iov[0].iov_base = buf;
 iov[0].iov_len = 100;
 msg.msg_iov = iov;
 msg.msg_iovlen = 1;
 //设置缓冲区和长度
 msg.msg_control = control_un.control;
 msg.msg_controllen = sizeof(control_un.control);
 //直接通过CMSG_FIRSTHDR取得附属数据
 pcmsg = CMSG_FIRSTHDR(&msg);
 pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
 pcmsg->cmsg_level = SOL_SOCKET;
 pcmsg->cmsg_type = SCM_RIGHTS;       // 指明发送的是描述符
 *((int*)CMSG_DATA(pcmsg)) = filefd;  // 把文件描述符写入辅助数据

    // 4. 发送文件描述符
 ret = sendmsg(clifd, &msg, 0); 
 clog_info ("ret = %d, filedescriptor = %d", ret, filefd);
 return 0 ;
}

2. 服务端代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define slog_info(...) do { \
printf("<%s %s> %s %s:%d INFO:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&& \
printf(__VA_ARGS__)&& \
printf("\n"); \
}while(0) 

#define UNIXSTR_PATH "foo.socket"

union {  //对齐
 struct cmsghdr cm;
 char control[CMSG_SPACE(sizeof(int))];
}  control_un;
 
int main(int argc, char *argv[])
{
 int clifd = -1, listenfd = -1;
    int ret = -1;
    int recvfd = -1;
    char buf[100] = {0};
 char *testmsg = "test msg.\n";
 struct sockaddr_un servaddr, cliaddr;
 struct msghdr msg;
 struct iovec iov[1];
 struct cmsghdr  * pcmsg;
 socklen_t clilen;

    // 1. 创建监听套接字
 listenfd  =  socket ( AF_UNIX ,  SOCK_STREAM ,  0 ) ;
 if(listenfd < 0) {
  slog_info ( "socket failed." ) ;
  return  -1;
 }

 unlink(UNIXSTR_PATH) ;
 bzero (&servaddr, sizeof(servaddr));
 servaddr.sun_family = AF_UNIX;
 strcpy ( servaddr.sun_path ,  UNIXSTR_PATH ) ;

    // 2. 绑定套接字, 并设置监听
 ret  =  bind ( listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
 if(ret < 0) {
  slog_info ( "bind failed. errno = %d." ,  errno ) ;
  close(listenfd);
  return  -1 ;
 }
 listen(listenfd, 5);

 while(1) {
     // 3. 接受连接    
  clilen = sizeof( cliaddr );
  clifd = accept( listenfd, (struct sockaddr*)&cliaddr , &clilen);
  if ( clifd < 0 ) {
   slog_info ( "accept failed." ) ;
   continue ;
  }

  msg.msg_name  = NULL;
  msg.msg_namelen  = 0;
  //设置数据缓冲区
  iov[0].iov_base = buf;
  iov[0].iov_len = 100;
  msg.msg_iov = iov;
  msg.msg_iovlen = 1;
  //设置辅助数据缓冲区和长度
  msg.msg_control = control_un.control;
  msg.msg_controllen  =  sizeof(control_un.control) ;
  // 4. 接受数据
  ret = recvmsg(clifd , &msg, 0);
  if( ret <= 0 ) {
      slog_info ( "recvmsg failed." ) ;        
            return ret;
  }
  //5. 检查是否收到了辅助数据,以及长度
  if((pcmsg = CMSG_FIRSTHDR(&msg) ) != NULL && ( pcmsg->cmsg_len == CMSG_LEN(sizeof(int)))) {
   if ( pcmsg->cmsg_level  !=  SOL_SOCKET ) {
    slog_info("cmsg_leval is not SOL_SOCKET");
    continue;
   }

   if ( pcmsg->cmsg_type != SCM_RIGHTS ) {
    slog_info ( "cmsg_type is not SCM_RIGHTS" );
    continue;
   }
   // 6. 接收到文件描述符
   recvfd = *((int*)CMSG_DATA(pcmsg));
   slog_info ( "recv fd = %d", recvfd );
            // 7. 对文件描述符执行写操作
   write ( recvfd, testmsg, strlen(testmsg) + 1);
  }
 }

 return 0 ;
}



分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP