简单的说,首先须要在这两个进程之间创建一个 Unix 域套接字接口做为消息传递的通道( Linux 系统上使用 socketpair 函数能够很方面便的创建起传递通道),而后发送进程调用 sendmsg 向通道发送一个特殊的消息,内核将对这个消息作特殊处理,从而将打开的描述符传递到接收进程。linux
而后接收方调用 recvmsg 从通道接收消息,从而获得打开的描述符。然而实际操做起来并不像看起来那样单纯。web
先来看几个注意点:windows
1 须要注意的是传递描述符并非传递一个 int 型的描述符编号,而是在接收进程中建立一个新的描述符,而且在内核的文件表中,它与发送进程发送的描述符指向相同的项。数组
2 在进程之间能够传递任意类型的描述符,好比能够是 pipe , open , mkfifo 或 socket , accept 等函数返回的描述符,而不限于套接字。socket
3 一个描述符在传递过程当中(从调用 sendmsg 发送到调用 recvmsg 接收),内核会将其标记为“在飞行中”( in flight )。在这段时间内,即便发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会使其引用计数加 1 。函数
4 描述符是经过辅助数据发送的(结构体 msghdr 的 msg_control 成员),在发送和接收描述符时,老是发送至少 1 个字节的数据,即便这个数据没有任何实际意义。不然当接收返回 0 时,接收方将不能区分这意味着“没有数据”(但辅助数据可能有套接字)仍是“文件结束符”。ui
5 具体实现时, msghdr 的 msg_control 缓冲区必须与 cmghdr 结构对齐,能够看到后面代码的实现使用了一个 union 结构来保证这一点。spa
[cpp]view plaincopy.net
struct msghdr {unix
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;
};
结构成员能够分为下面的四组,这样看起来就清晰多了:
1 套接口地址成员 msg_name 与 msg_namelen ;
只有当通道是数据报套接口时才须要; msg_name 指向要发送或是接收信息的套接口地址。 msg_namelen 指明了这个套接口地址的长度。
msg_name 在调用 recvmsg 时指向接收地址,在调用 sendmsg 时指向目的地址。注意, msg_name 定义为一个 (void *) 数据类型,所以并不须要将套接口地址显示转换为 (struct sockaddr *) 。
2 I/O 向量引用 msg_iov 与 msg_iovlen
它是实际的数据缓冲区,从下面的代码能看到,咱们的 1 个字节就交给了它;这个 msg_iovlen 是 msg_iov 的个数,不是什么长度。
msg_iov 成员指向一个 struct iovec 数组, iovc 结构体在 sys/uio.h 头文件定义,它没有什么特别的。
[cpp]view plaincopy
struct iovec {
ptr_t iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
};
有了 iovec ,就可使用 readv 和 writev 函数在一次函数调用中读取或是写入多个缓冲区,显然比屡次 read , write 更有效率。 readv 和 writev 的函数原型以下:
[cpp]view plaincopy
#include <sys/uio.h>
int readv( int fd, const struct iovec *vector, int count);
int writev( int fd, const struct iovec *vector, int count);
3 附属数据缓冲区成员 msg_control 与 msg_controllen ,描述符就是经过它发送的,后面将会看到, msg_control 指向附属数据缓冲区,而 msg_controllen 指明了缓冲区大小。
4 接收信息标记位 msg_flags ;忽略
轮到 cmsghdr 结构了,附属信息能够包括若干个单独的附属数据对象。在每个对象以前都有一个 struct cmsghdr 结构。头部以后是填充字节,而后是对象自己。最后,附属数据对象以后,下一个 cmsghdr 以前也许要有更多的填充字节。
[cpp]view plaincopy
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/* u_char cmsg_data[]; */
};
cmsg_len 附属数据的字节数,这包含结构头的尺寸,这个值是由 CMSG_LEN() 宏计算的;
cmsg_level 代表了原始的协议级别 ( 例如, SOL_SOCKET) ;
cmsg_type 代表了控制信息类型 ( 例如, SCM_RIGHTS ,附属数据对象是文件描述符; SCM_CREDENTIALS ,附属数据对象是一个包含证书信息的结构 ) ;
被注释的 cmsg_data 用来指明实际的附属数据的位置,帮助理解。
对于 cmsg_level 和 cmsg_type ,当下咱们只关心 SOL_SOCKET 和 SCM_RIGHTS 。
#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);
void *CMSG_DATA(struct cmsghdr *cmsg);
CMSG_LEN() 宏
输入参数:附属数据缓冲区中的对象大小;
计算 cmsghdr 头结构加上附属数据大小,包括必要的对其字段,这个值用来设置 cmsghdr 对象的 cmsg_len 成员。
CMSG_SPACE() 宏
输入参数:附属数据缓冲区中的对象大小;
计算 cmsghdr 头结构加上附属数据大小,并包括对其字段和可能的结尾填充字符,注意 CMSG_LEN() 值并不包括可能的结尾填充字符。 CMSG_SPACE() 宏对于肯定所需的缓冲区尺寸是十分有用的。
注意若是在缓冲区中有多个附属数据,必定要同时添加多个 CMSG_SPACE() 宏调用来获得所需的总空间。
下面的例子反映了两者的区别:
[cpp]view plaincopy
printf( "CMSG_SPACE(sizeof(short))=%d/n" , CMSG_SPACE( sizeof ( short ))); // 返回16
printf( "CMSG_LEN(sizeof(short))=%d/n" , CMSG_LEN( sizeof ( short ))); // 返回14
CMSG_DATA() 宏
输入参数:指向 cmsghdr 结构的指针 ;
返回跟随在头部以及填充字节以后的附属数据的第一个字节 ( 若是存在 ) 的地址,好比传递描述符时,代码将是以下的形式:
[cpp]view plaincopy
struct cmsgptr *cmptr;
. . .
int fd = *( int )CMSG_DATA(cmptr); // 发送:(int *)CMSG_DATA(cmptr) = fd;
CMSG_FIRSTHDR() 宏
输入参数:指向 struct msghdr 结构的指针;
返回指向附属数据缓冲区内的第一个附属对象的 struct cmsghdr 指针。若是不存在附属数据对象则返回的指针值为 NULL 。
CMSG_NXTHDR() 宏
输入参数:指向 struct msghdr 结构的指针,指向当前 struct cmsghdr 的指针;
这个用于返回下一个附属数据对象的 struct cmsghdr 指针,若是没有下一个附属数据对象,这个宏就会返回 NULL 。
经过这两个宏能够很容易遍历全部的附属数据,像下面的形式:
[cpp]view plaincopy
struct msghdr msgh;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msgh,cmsg) {
// 获得了cmmsg,就能经过CMSG_DATA()宏取得辅助数据了
[cpp]view plaincopy
#include <sys/types.h>
#include <sys/socket.h>
int sendmsg( int s, const struct msghdr *msg, unsigned int flags);
int recvmsg( int s, struct msghdr *msg, unsigned int flags);
两者的参数说明以下:
s, 套接字通道,对于 sendmsg 是发送套接字,对于 recvmsg 则对应于接收套接字;
msg ,信息头结构指针;
flags , 可选的标记位, 这与 send 或是 sendto 函数调用的标记相同。
函数的返回值为实际发送 / 接收的字节数。不然返回 -1 代表发生了错误。
具体参考 APUE 的高级 I/O 部分,介绍的很详细。
好了准备工做已经作完了,下面就准备进入正题。