Linux环境:C编程之进程传递文件描述符

在多进程编程时,会用到进程间传递文件描述符的情况,例如进程池编程通信时,在主进程中建立连接,然后把建立后的socket转给子进程来处理任务。接下来看一下如何在进程间传递文件描述符。

需要注意的是:

文件描述符的编号在进程间独立,每个进程都维护一个进程文件打开表,所以同一文件描述符在不同进程中有不同的含义。因此,传递文件描述符,其实是在传递文件描述符对应的指向该文件的引用。

具体流程:通过socketpair建立unix域套接字,然后利用这对套接字进行通信,利用sendmsgrecvmsg函数传递文件描述符对应文件的辅助信息。

socketpair函数

  • 作用:sockpair函数用来建立一对无名套接字,用于全双工通信,和无名管道相比,无名套接字可以全双工通信,并且支持主机和网络通信,功能更加强大。
  • 函数原型:int socketpair(int d, int type, int protocol, int sv[2]);
  • 参数:前三个参数和socket函数一致,分别是通信域,类型和协议,第四个参数是传出参数,传出创建的一对描述符。
  • 注意事项:
  1. 可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
  2. 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
  3. 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。

sendmsg和recvmsg函数

  • 函数原型:
    size_t sendmsg (int s, const struct msghdr *msg, int flags);
    int recvmsg(int s, struct msghdr *msg, unsigned int flags);
  • 作用:
    这一对发送函数可以替代之前的send,recv,sendto和recvfrom,向另一个套接口发送信息。
    参数:第一个参数是要发信息或收信息的套接字描述符;第二个参数是发送的信息结构体头指针;第三个参数是标志位,和之前的sendto/recvfrom等函数一致。
  • 返回值:返回实际接受或发送的字节数,失败返回-1

struct msghdr结构体

要理解sendmsg和recvmsg,需要弄清楚struct msghdr结构体。该结构体定义如下:

//有这么多成员,所以才能实现多种功能
struct msghdr {

/*下面两组是套接字的地址指针和长度,用来替代sendto和recvfrom中指明接收方或发送方的地址参数*/
void *msg_name;
socklen_t msg_namelen;

/*这一组是要发送的数据信息的缓冲区指针和长度*/
struct iovec *msg_iov;
size_t msg_iovlen;

/*这一组是发送的辅助信息,用来传递一些特殊信息,比如文件描述符的控制信息*/
void *msg_control;
size_t msg_controllen;

/*接收信息标记位*/
int msg_flags;
}

sendmsg和recvmsg能实现这么多功能就是因为这个结构体很复杂,内容很多,不过我们使用的时候只需要初始化能用到的信息就可以了。
需要注意的是,如果想利用它传递辅助信息,比如文件描述符,必须携带至少一个字节的真实数据,也就是iov指针指向的缓冲区要有数据,iovlen至少是1。

struct cmsghdr结构体

我们要传递的辅助信息需要是一个struct cmsghdr结构体类型保存,然后用msg_control指向它。该结构体的定义如下:

struct cmsghdr {
 socklen_t cmsg_len; /* 该变量的长度,包含结构体头部 */
 int cmsg_level; /* 原始协议,比如SOL_SOCKET代表套接字API层*/
 int cmsg_type; /* 控制信息类型,比如SCM_RIGHTS代表了文件描述符 */
 /* followed by unsigned char cmsg_data[]; */
 };

该结构体比较复杂所以有一系列的宏来对其进行操作

  • CMSG_FIRSTHDR() :接收一个msghdr指针,返回指向msghdr的控制信息缓冲区的第一个指针,前提是msghdr已经申请了缓冲区空间。
  • CMSG_DATA() :接收一个cmsghdr指针,返回cmsghdr的数据区的第一个字节的指针。
  • CMSG_LEN() :接受我们希望放置在附属数据缓冲区中的对象尺寸作为输入参数。返回cmsghdr头结构加上所需要的填充字符的字节长度。这个值用来设置cmsghdr对象的cmsg_len成员。

通过把要传递的文件描述符放在data区,内核就可以帮我们完成转换。

recvmsg接收时和上述一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章