通过Linux套接字发送文件描述符

通过Linux套接字发送文件描述符,第1张

通过Linux套接字发送文件描述

史蒂文斯(etal)[UNIX®网络编程,第1卷:套接字网络API描述了在第15章
Unix域协议 (尤其是第15.7节:_传递描述_符)中的过程之间传输文件描述符的过程。对此进行完整描述很麻烦,但是必须在Unix域套接字(

AF_UNIX
AF_LOCAL
)上完成,发送方进程使用,
sendmsg()
而接收方使用
recvmsg()

我从问题中得到了经过轻微修改(和检测)的代码版本,可以在带有GCC 4.9.1的Mac OS X 10.10.1 Yosemite上为我工作:

#include "stderr.h"#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/socket.h>#include <sys/wait.h>#include <time.h>#include <unistd.h>staticvoid wyslij(int socket, int fd)  // send fd by socket{    struct msghdr msg = { 0 };    char buf[CMSG_SPACe(sizeof(fd))];    memset(buf, '', sizeof(buf));    struct iovec io = { .iov_base = "ABC", .iov_len = 3 };    msg.msg_iov = &io;    msg.msg_iovlen = 1;    msg.msg_control = buf;    msg.msg_controllen = sizeof(buf);    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);    cmsg->cmsg_level = SOL_SOCKET;    cmsg->cmsg_type = SCM_RIGHTS;    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));    *((int *) CMSG_DATA(cmsg)) = fd;    msg.msg_controllen = CMSG_SPACe(sizeof(fd));    if (sendmsg(socket, &msg, 0) < 0)        err_syserr("Failed to send messagen");}staticint odbierz(int socket)  // receive fd from socket{    struct msghdr msg = {0};    char m_buffer[256];    struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };    msg.msg_iov = &io;    msg.msg_iovlen = 1;    char c_buffer[256];    msg.msg_control = c_buffer;    msg.msg_controllen = sizeof(c_buffer);    if (recvmsg(socket, &msg, 0) < 0)        err_syserr("Failed to receive messagen");    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);    unsigned char * data = CMSG_DATA(cmsg);    err_remark("about to extract fdn");    int fd = *((int*) data);    err_remark("Extracted fd %dn", fd);    return fd;}int main(int argc, char **argv){    const char *filename = "./z7.c";    err_setarg0(argv[0]);    err_setlogopts(ERR_PID);    if (argc > 1)        filename = argv[1];    int sv[2];    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)        err_syserr("Failed to create Unix-domain socket pairn");    int pid = fork();    if (pid > 0)  // in parent    {        err_remark("Parent at workn");        close(sv[1]);        int sock = sv[0];        int fd = open(filename, O_RDONLY);        if (fd < 0) err_syserr("Failed to open file %s for readingn", filename);        wyslij(sock, fd);        close(fd);        nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);        err_remark("Parent exitsn");    }    else  // in child    {        err_remark("Child at playn");        close(sv[0]);        int sock = sv[1];        nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);        int fd = odbierz(sock);        printf("Read %d!n", fd);        char buffer[256];        ssize_t nbytes;        while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0) write(1, buffer, nbytes);        printf("Done!n");        close(fd);    }    return 0;}

已检测但未修复的原始代码版本的输出为:

$ ./fd-passingfd-passing: pid=1391: Parent at workfd-passing: pid=1391: Failed to send messageerror (40) Message too longfd-passing: pid=1392: Child at play$ fd-passing: pid=1392: Failed to receive messageerror (40) Message too long

请注意,父级在子级之前完成,因此提示出现在输出的中间。

“固定”代码的输出为:

$ ./fd-passingfd-passing: pid=1046: Parent at workfd-passing: pid=1048: Child at playfd-passing: pid=1048: about to extract fdfd-passing: pid=1048: Extracted fd 3Read 3!This is the file z7.c.It isn't very interesting.It isn't even C pre.But it is used by the fd-passing program to demonstrate that filedescriptors can indeed be passed between sockets on occasion.Done!fd-passing: pid=1046: Parent exits$

主要的重大变化是将都添加

struct iovec
structmsghdr
两个函数中的数据中,并在接收函数(
odbierz()
)中为控制消息提供了空间。我报告了调试的中间步骤,在该步骤中,我向
structiovec
父级提供了,并且父级的“消息过长”错误已消除。为了证明它是有效的(通过了文件描述符),我添加了代码以从传递的文件描述符中读取和打印文件。原始代码具有
sleep(0.5)
但由于
sleep()
采用了无符号整数,所以这相当于不hibernate。我用C99复合文字让孩子睡了0.5秒。父级睡眠1.5秒,以便在父级退出之前完成子级的输出。我可以使用
wait()
waitpid()

也是如此,但是太懒了。

我没有回去检查所有添加的内容是否必要。

"stderr.h"
报头声明
err_*()
功能。这是我编写的代码(1987年之前的第一个版本),用于简洁地报告错误。该
err_setlogopts(ERR_PID)
呼叫在所有消息之前加上PID。对于时间戳,
err_setlogopts(ERR_PID|ERR_STAMP)
也可以做。

对齐问题

我是否建议您修改代码以使用复制描述符

int
memcpy()
而不是直接访问数据?它不一定正确对齐(这就是手册页示例也要使用的原因)
memcpy()
,并且在许多Linux体系结构中,未对齐的
int
访问会导致问题(多达SIGBUS信号会杀死进程)。

而且不仅是Linux体系结构:SPARC和Power都需要对齐的数据,并且通常分别运行Solaris和AIX。曾几何时,DEC
Alpha也要求这样做,但如今在现场很少见到它们。

cmsg(3)
与此相关的手册页中的代码是:

struct msghdr msg = {0};struct cmsghdr *cmsg;int myfds[NUM_FD]; char buf[CMSG_SPACe(sizeof myfds)];  int *fdptr;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(sizeof(int) * NUM_FD);fdptr = (int *) CMSG_DATA(cmsg);memcpy(fdptr, myfds, NUM_FD * sizeof(int));msg.msg_controllen = CMSG_SPACe(sizeof(int) * NUM_FD);

对to的赋值

fdptr
似乎假设它
CMSG_DATA(cmsg)
已充分对齐以转换为an,
int *
并且the
memcpy()
的使用
NUM_FD
不只是1
的假设。如此说来,它应该指向数组
buf
,并且可能不够好按照名义动物的建议排列,因此在我看来,这
fdptr
只是闯入者,如果使用以下示例会更好:

memcpy(CMSG_DATA(cmsg), myfds, NUM_FD * sizeof(int));

然后在接收端进行相反的处理将是适当的。该程序仅传递单个文件描述符,因此代码可修改为:

memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));  // Sendmemmove(&fd, CMSG_DATA(cmsg), sizeof(fd));  // Receive

我似乎也想起了各种 *** 作系统上的历史问题,这些 *** 作系统都带有没有正常有效载荷数据的辅助数据,也通过发送至少一个虚拟字节来避免这种情况,但是我找不到任何可验证的引用,因此我可能会记错。

鉴于Mac OS X(基于Darwin / BSD)至少需要一个

structiovec
,即使它描述了零长度的消息,我也愿意相信上面显示的代码(包括3字节消息)是在正确的总体方向上迈出的重要一步。该消息可能应该是一个空字节,而不是3个字母。

我将代码修改为如下所示。它用于

memmove()
cmsg
缓冲区中复制文件描述符。它传输单个消息字节,即空字节。

在将文件描述符传递给子进程之前,父进程还读取(最多)文件的32个字节。孩子继续在父母离开的地方读书。这证明了传输的文件描述符包括文件偏移量。

接收者应

cmsg
在将其视为文件描述符传递消息之前对进行更多验证。

#include "stderr.h"#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/socket.h>#include <sys/wait.h>#include <time.h>#include <unistd.h>staticvoid wyslij(int socket, int fd)  // send fd by socket{    struct msghdr msg = { 0 };    char buf[CMSG_SPACe(sizeof(fd))];    memset(buf, '', sizeof(buf));        struct iovec io = { .iov_base = "", .iov_len = 1 };    msg.msg_iov = &io;    msg.msg_iovlen = 1;    msg.msg_control = buf;    msg.msg_controllen = sizeof(buf);    struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg);    cmsg->cmsg_level = SOL_SOCKET;    cmsg->cmsg_type = SCM_RIGHTS;    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));    memmove(CMSG_DATA(cmsg), &fd, sizeof(fd));    msg.msg_controllen = CMSG_SPACe(sizeof(fd));    if (sendmsg(socket, &msg, 0) < 0)        err_syserr("Failed to send messagen");}staticint odbierz(int socket)  // receive fd from socket{    struct msghdr msg = {0};        char m_buffer[1];    struct iovec io = { .iov_base = m_buffer, .iov_len = sizeof(m_buffer) };    msg.msg_iov = &io;    msg.msg_iovlen = 1;    char c_buffer[256];    msg.msg_control = c_buffer;    msg.msg_controllen = sizeof(c_buffer);    if (recvmsg(socket, &msg, 0) < 0)        err_syserr("Failed to receive messagen");    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);    err_remark("about to extract fdn");    int fd;    memmove(&fd, CMSG_DATA(cmsg), sizeof(fd));    err_remark("Extracted fd %dn", fd);    return fd;}int main(int argc, char **argv){    const char *filename = "./z7.c";    err_setarg0(argv[0]);    err_setlogopts(ERR_PID);    if (argc > 1)        filename = argv[1];    int sv[2];    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)        err_syserr("Failed to create Unix-domain socket pairn");    int pid = fork();    if (pid > 0)  // in parent    {        err_remark("Parent at workn");        close(sv[1]);        int sock = sv[0];        int fd = open(filename, O_RDONLY);        if (fd < 0) err_syserr("Failed to open file %s for readingn", filename);                char buffer[32];        int nbytes = read(fd, buffer, sizeof(buffer));        if (nbytes > 0) err_remark("Parent read: [[%.*s]]n", nbytes, buffer);        wyslij(sock, fd);        close(fd);        nanosleep(&(struct timespec){ .tv_sec = 1, .tv_nsec = 500000000}, 0);        err_remark("Parent exitsn");    }    else  // in child    {        err_remark("Child at playn");        close(sv[0]);        int sock = sv[1];        nanosleep(&(struct timespec){ .tv_sec = 0, .tv_nsec = 500000000}, 0);        int fd = odbierz(sock);        printf("Read %d!n", fd);        char buffer[256];        ssize_t nbytes;        while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0) write(1, buffer, nbytes);        printf("Done!n");        close(fd);    }    return 0;}

并运行一个示例:

$ ./fd-passingfd-passing: pid=8000: Parent at workfd-passing: pid=8000: Parent read: [[This is the file z7.c.It isn't ]]fd-passing: pid=8001: Child at playfd-passing: pid=8001: about to extract fdfd-passing: pid=8001: Extracted fd 3Read 3!very interesting.It isn't even C pre.But it is used by the fd-passing program to demonstrate that filedescriptors can indeed be passed between sockets on occasion.And, with the fully working pre, it does indeed seem to work.Extended testing would have the parent pre read part of the file, andthen demonstrate that the child precontinues where the parent left off.That has not been pred, though.Done!fd-passing: pid=8000: Parent exits$


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4923368.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-12
下一篇 2022-11-12

发表评论

登录后才能评论

评论列表(0条)

保存