-
Notifications
You must be signed in to change notification settings - Fork 85
进程通信 #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#define ERR_EXIT(m)
do
{
perror(m);
exit(EXIT_FAILURE);
} while(0)
void send_fd(int sock_fd, int send_fd)
{
int ret;
struct msghdr msg;
struct cmsghdr *p_cmsg;
struct iovec vec;
char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
int *p_fds;
char sendchar = 0;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
p_fds = (int *)CMSG_DATA(p_cmsg);
*p_fds = send_fd;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
ret = sendmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("sendmsg");
}
int recv_fd(const int sock_fd)
{
int ret;
struct msghdr msg;
char recvchar;
struct iovec vec;
int recv_fd;
char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
struct cmsghdr *p_cmsg;
int *p_fd;
vec.iov_base = &recvchar;
vec.iov_len = sizeof(recvchar);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
p_fd = (int *)CMSG_DATA(CMSG_FIRSTHDR(&msg));
*p_fd = -1;
ret = recvmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("recvmsg");
p_cmsg = CMSG_FIRSTHDR(&msg);
if (p_cmsg == NULL)
ERR_EXIT("no passed fd");
p_fd = (int *)CMSG_DATA(p_cmsg);
recv_fd = *p_fd;
if (recv_fd == -1)
ERR_EXIT("no passed fd");
return recv_fd;
}
int main(void)
{
int sockfds[2];
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)
ERR_EXIT("socketpair");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid > 0)
{
close(sockfds[1]);
int fd = recv_fd(sockfds[0]);
char buf[1024] = {0};
read(fd, buf, sizeof(buf));
printf("buf=%sn", buf);
}
else if (pid == 0)
{
close(sockfds[0]);
int fd;
fd = open("test.txt", O_RDONLY);
if (fd == -1);
send_fd(sockfds[1], fd);
}
return 0;
} |
进程间传递描述符一每个进程都拥有自己独立的进程空间,这使得描述符在进程之间的传递变得有点复杂,这个属于高级进程间通信的内容,下面就来说说。顺便把 Linux 和 Windows 平台都讲讲。 Linux 下的描述符传递Linux 系统系下,子进程会自动继承父进程已打开的描述符,实际应用中,可能父进程需要向子进程传递“后打开的描述符”,或者子进程需要向父进程传递;或者两个进程可能是无关的,显然这需要一套传递机制。 简单的说,首先需要在这两个进程之间建立一个 Unix 域套接字接口作为消息传递的通道( Linux 系统上使用 socketpair 函数可以很方面便的建立起传递通道),然后发送进程调用 sendmsg 向通道发送一个特殊的消息,内核将对这个消息做特殊处理,从而将打开的描述符传递到接收进程。 然后接收方调用 recvmsg 从通道接收消息,从而得到打开的描述符。然而实际操作起来并不像看起来那样单纯。 先来看几个注意点: 1 需要注意的是传递描述符并不是传递一个 int 型的描述符编号,而是在接收进程中创建一个新的描述符,并且在内核的文件表中,它与发送进程发送的描述符指向相同的项。 2 在进程之间可以传递任意类型的描述符,比如可以是 pipe , open , mkfifo 或 socket , accept 等函数返回的描述符,而不限于套接字。 3 一个描述符在传递过程中(从调用 sendmsg 发送到调用 recvmsg 接收),内核会将其标记为“在飞行中”( in flight )。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会使其引用计数加 1 。 4 描述符是通过辅助数据发送的(结构体 msghdr 的 msg_control 成员),在发送和接收描述符时,总是发送至少 1 个字节的数据,即使这个数据没有任何实际意义。否则当接收返回 0 时,接收方将不能区分这意味着“没有数据”(但辅助数据可能有套接字)还是“文件结束符”。 5 具体实现时, msghdr 的 msg_control 缓冲区必须与 cmghdr 结构对齐,可以看到后面代码的实现使用了一个 union 结构来保证这一点。 msghdr 和 cmsghdr 结构体上面说过,描述符是通过结构体 msghdr 的 msg_control 成员送的,因此在继续向下进行之前,有必要了解一下 msghdr 和 cmsghdr 结构体,先来看看 msghdr 。 struct msghdr {
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 头文件定义,它没有什么特别的。 struct iovec {
ptr_t iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
}; 有了 iovec ,就可以使用 readv 和 writev 函数在一次函数调用中读取或是写入多个缓冲区,显然比多次 read , write 更有效率。 readv 和 writev 的函数原型如下: #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 之前也许要有更多的填充字节。 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 。 msghdr 和 cmsghdr 辅助宏这些结构还是挺复杂的, Linux 系统提供了一系列的宏来简化我们的工作,这些宏可以在不同的 UNIX 平台之间进行移植。这些宏是由 cmsg(3) 的 man 手册页描述的,先来认识一下: #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() 宏调用来得到所需的总空间。 下面的例子反映了二者的区别: 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 结构的指针 ; 返回跟随在头部以及填充字节之后的附属数据的第一个字节 ( 如果存在 ) 的地址,比如传递描述符时,代码将是如下的形式: 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 。 通过这两个宏可以很容易遍历所有的附属数据,像下面的形式: struct msghdr msgh;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msgh,cmsg) {
// 得到了cmmsg,就能通过CMSG_DATA()宏取得辅助数据了 函数 sendmsg 和 recvmsg函数原型如下: #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 部分,介绍的很详细。 转载自csdn [ http://blog.csdn.net/sparkliang/article/details/5486069]([http://blog.csdn.net/sparkliang/article/details/5486069]%28http://blog.csdn.net/sparkliang/article/details/5486069%29) |
进程间传递描述符二发送、接收描述符 发送描述符经过了前面的准备工作,是时候发送描述符了,先来看看函数原型: int write_fd(int fd, void *ptr, int nbytes, int sendfd);参数说明如下:
函数返回值为写入的字节数, <0 说明发送失败; 废话少说,代码先上,发送描述符的代码相对简单一些,说明见代码内注释。 先说明一下,旧的 Unix 系统使用的是 msg_accrights 域来传递描述符,因此我们需要使用宏 HAVE_MSGHDR_MSG_CONTROL 以期能同时支持这两种版本。 int write_fd(int fd, void *ptr, int nbytes, int sendfd)
{
struct msghdr msg;
struct iovec iov[1];
// 有些系统使用的是旧的msg_accrights域来传递描述符,Linux下是新的msg_control字段
#ifdef HAVE_MSGHDR_MSG_CONTROL
union{ // 前面说过,保证cmsghdr和msg_control的对齐
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
}control_un;
struct cmsghdr *cmptr;
// 设置辅助缓冲区和长度
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
// 只需要一组附属数据就够了,直接通过CMSG_FIRSTHDR取得
cmptr = CMSG_FIRSTHDR(&msg);
// 设置必要的字段,数据和长度
cmptr->cmsg_len = CMSG_LEN(sizeof(int)); // fd类型是int,设置长度
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS; // 指明发送的是描述符
*((int*)CMSG_DATA(cmptr)) = sendfd; // 把fd写入辅助数据中
#else
msg.msg_accrights = (caddr_t)&sendfd; // 这个旧的更方便啊
msg.msg_accrightslen = sizeof(int);
#endif
// UDP才需要,无视
msg.msg_name = NULL;
msg.msg_namelen = 0;
// 别忘了设置数据缓冲区,实际上1个字节就够了
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
return sendmsg(fd, &msg, 0);
} 接收描述符发送方准备好之后,接收方准备接收,函数原型为: int read_fd(int fd, void *ptr, int nbytes, int *recvfd);参数说明如下:
函数返回值为读取的字节数, <0 说明读取失败; 接收函数代码如下,相比发送要复杂一些。 int read_fd(int fd, void *ptr, int nbytes, int *recvfd)
{
struct msghdr msg;
struct iovec iov[1];
int n;
int newfd;
#ifdef HAVE_MSGHDR_MSG_CONTROL
union{ // 对齐
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
}control_un;
struct cmsghdr *cmptr;
// 设置辅助数据缓冲区和长度
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
#else
msg.msg_accrights = (caddr_t) &newfd; // 这个简单
msg.msg_accrightslen = sizeof(int);
#endif
// TCP无视
msg.msg_name = NULL;
msg.msg_namelen = 0;
// 设置数据缓冲区
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
// 设置结束,准备接收
if((n = recvmsg(fd, &msg, 0)) <= 0)
{
return n;
}
#ifdef HAVE_MSGHDR_MSG_CONTROL
// 检查是否收到了辅助数据,以及长度,回忆上一节的CMSG宏
cmptr = CMSG_FIRSTHDR(&msg);
if((cmptr != NULL) && (cmptr->cmsg_len == CMSG_LEN(sizeof(int))))
{
// 还是必要的检查
if(cmptr->cmsg_level != SOL_SOCKET)
{
printf("control level != SOL_SOCKET/n");
exit(-1);
}
if(cmptr->cmsg_type != SCM_RIGHTS)
{
printf("control type != SCM_RIGHTS/n");
exit(-1);
}
// 好了,描述符在这
*recvfd = *((int*)CMSG_DATA(cmptr));
}
else
{
if(cmptr == NULL) printf("null cmptr, fd not passed./n");
else printf("message len[%d] if incorrect./n", cmptr->cmsg_len);
*recvfd = -1; // descriptor was not passed
}
#else
if(msg.msg_accrightslen == sizeof(int)) *recvfd = newfd;
else *recvfd = -1;
#endif
return n;
} 发送和接收函数就这么多,就像上面看到的,进程间传递套接字还是有点麻烦的。 转载自csdn http://blog.csdn.net/sparkliang/article/details/5490242 |
膜拜TJ大神。 |
难得一见的好文 |
linux进程通信
进程间如何传递fd
http://www.lst.de/~okir/blackhats/node121.html
The text was updated successfully, but these errors were encountered: