UNIX域Socket传送文件描述符

相关文章:

    struct msghdr 和 struct cmsghdr的详细介绍及个别IO操作函数


发送端:

#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>

void socketSendfd(int, int);

void namedSocket() {
    int sockfd, size;
    sockaddr_un un;    //①sockaddr_un结构体
    char buf[20] = {"123"};

    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, "/home/enheng/CLionProjects/17_ProcessCommPro/foo.socket");

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        print("socket error");
    size = sizeof(un.sun_family) + strlen(un.sun_path);
//    print(size);

    if (bind(sockfd, (sockaddr *) &un, size) < 0) {//绑定
        print("bind error");
        return;
    }
//    print("bound OK");
    if (listen(sockfd, 5) == -1) {//监听
        print("listen error");
        return;
    }

    sockaddr_un client_addr;//OUT,接收客户端un信息
    socklen_t len;
    int clifd;
    if ((clifd = accept(sockfd, (sockaddr *) &client_addr, &len)) == -1) {
        print("accept error", strerror(errno));
        return;
    }

//    write(clifd, buf, 3);
    send(clifd, buf, 10, 0);//传送普通数据

//    pause();
    int fd = open("../fd.test", O_CREAT | O_RDWR, 0666);
    socketSendfd(clifd, fd);//传送文件描述符

    close(fd);
    close(sockfd);
    unlink(un.sun_path);//解除链接操作
}

void socketSendfd(int clifd, int fdToSend){
    struct iovec iov[1];
    struct msghdr msg;//②msghdr结构体
    struct cmsghdr *cmptr = nullptr;//③cmsghdr结构体
    int cmsghdrlen = CMSG_LEN(sizeof(int));//CMSG_LEN()返回cmsghdr结构的cmsg_len成员的值,考虑到任何必要的对齐。它取出参数的长度。这是一个常量表达式。
    char buf[2] = {0};

    iov[0].iov_base = buf;
    iov[0].iov_len = 2;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

//    cmsghdr cmsghdr1;
//    cmsghdr1.cmsg_level = SOL_SOCKET;
//    cmsghdr1.cmsg_type = SCM_RIGHTS;
//    cmsghdr1.cmsg_len = 20;

    cmptr = (cmsghdr *)malloc(cmsghdrlen);
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;//SCM_RIGHTS表明在传送访问权,访问权仅能通过UNIX域套接字传送。
    cmptr->cmsg_len = cmsghdrlen;

//    for (int i = 0; i < 20; ++i) {
//        printf("%X ", *((char *)cmptr + i));
//    }
//    cout << endl;

    msg.msg_control = cmptr;
    msg.msg_controllen = cmsghdrlen;
    *(int *)CMSG_DATA(cmptr) = fdToSend;//CMSG_DATA()返回cmsghdr的数据部分指针。

//    cout << hex << (long)CMSG_DATA(cmptr) << " " << cmptr;
//    for (int i = 0; i < 20; ++i) {
//        printf("%X ", *((char *)cmptr + i));
//    }
//    cout << endl;

    if(sendmsg(clifd, &msg, 0) == -1){
        perror("send error");
    }
}

int main() {

    namedSocket();

    return 0;
}


接收端:

#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>

int recvfd(int);

void client_socketun(){
    int sockfd, newfd, len, nr;
    sockaddr_un un;
    char buf[20]={0};

    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, "/home/enheng/CLionProjects/17_ProcessCommPro/foo.socket");
    len = sizeof(un.sun_family) + strlen(un.sun_path);

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    print("socketfd:", sockfd);

    if(connect(sockfd, (sockaddr *)&un, len) == -1){//连接socket
        print("connect error");
        return ;
    }

    // read(sockfd, buf, 20);
    recv(sockfd, buf, 20, 0);//接收普通数据
    print(buf);

    if((newfd = recvfd(sockfd)) == -1){//接收msg数据
        print("rec error");
    }else{
        if((nr = read(newfd, buf, 10)) == -1){
            perror("read error");
        }else{
            print(buf);
            fflush(stdout);
        }
    }

    close(sockfd);
}

int recvfd(int sockfd){
    int newfd, nr;
    struct cmsghdr *cmptr = nullptr;//OUT
    int cmsghdrlen = CMSG_LEN(sizeof(int));
    char buf[2];
    struct iovec iov[1];
    struct msghdr msg;

    iov[0].iov_base = buf;
    iov[0].iov_len = 2;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    cmptr = (cmsghdr *)malloc(cmsghdrlen);
    msg.msg_control = cmptr;
    msg.msg_controllen = cmsghdrlen;

    if((nr = recvmsg(sockfd, &msg, 0)) < 0){
        perror("recvmsg error");
    }else{
        newfd = *(int *)CMSG_DATA(cmptr);
        print("newfd:", newfd);
        fflush(stdout);
        return newfd;
    }

    return -1;
}

int main(){
    print("PID:", getpid());

    client_socketun();

    return 0;
}


①sockaddr_un

进程间通信的一种方式是使用UNIX套接字,人们在使用这种方式时往往用的不是网络套接字,而是一种称为本地套接字的方式。这样做可以避免为黑客留下后门。


创建
使用套接字函数socket创建,不过传递的参数与网络套接字不同。域参数应该是PF_LOCAL或者PF_UNIX,而不能用PF_INET之类。本地套接字的通讯类型应该是SOCK_STREAM或SOCK_DGRAM,协议为默认协议。例如:
 int sockfd;
 sockfd = socket(PF_LOCAL, SOCK_STREAM, 0);


绑定
创建了套接字后,还必须进行绑定才能使用。不同于网络套接字的绑定,本地套接字的绑定的是struct sockaddr_un结构。struct sockaddr_un结构有两个参数:sun_family、sun_path。sun_family只能是AF_LOCAL或AF_UNIX,而sun_path是本地文件的路径。通常将文件放在/tmp目录下。例如:


 struct sockaddr_un sun;
 sun.sun_family = AF_LOCAL;
 strcpy(sun.sun_path, filepath);
 bind(sockfd, (struct sockaddr*)&sun, sizeof(sun));

②理解struct 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;
};
    结构成员可以分为四组。他们是:
    套接口地址成员msg_name与msg_namelen。
    I/O向量引用msg_iov与msg_iovlen。
    附属数据缓冲区成员msg_control与msg_controllen。
    接收信息标记位msg_flags。
    在我们将这个结构分为上面的几类以后,结构看起来就不那样巨大了。


成员msg_name与msg_namelen
    这些成员只有当我们的套接口是一个数据报套接口时才需要。msg_name成员指向我们要发送或是接收信息的套接口地址。成员msg_namelen指明了这个套接口地址的长度。
当调用recvmsg时,msg_name会指向一个将要接收的地址的接收区域。当调用sendmsg时,这会指向一个数据报将要发送到的目的地址。
注意,msg_name定义为一个(void *)数据类型。我们并不需要将我们的套接口地址转换为(struct sockaddr *)。


成员msg_iov与msg_iovlen
    这些成员指定了我们的I/O向量数组的位置以及他包含多少项。msg_iov成员指向一个struct iovec数组。我们将会回忆起I/O向量指向我们的缓冲区。成员msg_iov指明了在我们的I/O向量数组中有多少元素。


成员msg_control与msg_controllen
    这些成员指向了我们附属数据缓冲区并且表明了缓冲区大小。msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。


成员msg_flags
    当使用recvmsg时,这个成员用于接收特定的标记位(他并不用于sendmsg)。在这个位置可以接收的标记位如下表所示:
标记位        描述
MSG_EOR        当接收到记录结尾时会设置这一位。这通常对于SOCK_SEQPACKET套接口类型十分有用。
MSG_TRUNC    这个标记位表明数据的结尾被截短,因为接收缓冲区太小不足以接收全部的数据。
MSG_CTRUNC    这个标记位表明某些控制数据(附属数据)被截短,因为缓冲区太小。
MSG_OOB        这个标记位表明接收了带外数据。
MSG_ERRQUEUE    这个标记位表明没有接收到数据,但是返回一个扩展错误。
我们可以在recvmsg(2)与sendmsg(2)的man手册页中查看更多的信息。


③控制信息头部struct cmsghdr

struct cmsghdr {
        socklen_t cmsg_len;
        int       cmsg_level;
        int       cmsg_type;
       
    };
其成员描述如下:
    成员                描述
    cmsg_len        附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。
    cmsg_level     这个值表明了原始的协议级别(例如,SOL_SOCKET)。
    cmsg_type     这个值表明了控制信息类型(例如,SCM_RIGHTS)。

扫描二维码关注公众号,回复: 1030068 查看本文章

    cmsg_data     这个成员并不实际存在。他用来指明实际的额外附属数据所在的位置。

相关宏:

CMSG_LEN()
    这个宏接受我们希望放置在附属数据缓冲区中的对象尺寸作为输入参数。如果我们回顾一个我们前面的介绍,我们就会发现这个宏会计算cmsghdr头结构加上所需要的填充字符的字节长度。这个值用来设置cmsghdr对象的cmsg_len成员。
下面的例子演示了如果附属数据是一个文件描述符,我们应如何来计算cmsg_len成员的值:
int fd;   
printf("cmsg_len = %d\n",CMSG_LEN(sizeof fd));


CMSG_SPACE()
    这个宏用来计算附属数据以及其头部所需的总空白。尽管CMSG_LEN()宏计算了一个相似的度,CMSG_LEN()值并不包括可能的结尾的填充字符。CMSG_SPACE()宏对于确定所需的缓冲区尺寸是十分有用的,如下面的示例代码所示:
int fd;
char abuf[CMSG_SPACE(sizeof fd)];
这个例子在abuf[]中声明了足够的缓冲区空间来存放头部,填充字节以及附属数据本身,和最后的填充字节。如果在缓冲区中有多个附属数据对象,一定要同时添加多个CMSG_SPACE()宏调用来得到所需的总空间。


CMSG_DATA()
    这个宏接受一个指向cmsghdr结构的指针。返回的指针值指向跟随在头部以及填充字节之后的附属数据的第一个字节(如果存在)。如果指针mptr指向一个描述文件描述符的可用的附属数据信息头部,这个文件描述符可以用下面的代码来得到:
struct cmsgptr *mptr;
int fd;
. . .
fd = *(int *)CMSG_DATA(mptr);


CMSG_ALIGN()
    这是一个Linux扩展宏,而不是Posix.1g标准的一部分。指定一个字节长度作为输入,这个宏会计算一个新的长度,这个新长度包括为了维护对齐所需要的额外的填充字节。


CMSG_FIRSTHDR()
    这个宏用于返回一个指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指针。输入值为是指向struct
msghdr结构的指针(不要与struct  cmsghdr相混淆)。这个宏会估计msghdr的成员msg_control与msg_controllen来确定在缓冲区中是否存在附属对象。然后,他会计算返回的指针。
    如果不存在附属数据对象则返回的指针值为NULL。否则,这个指针会指向存在的第一个struct cmsghdr。这个宏用在一个for循环的开始处,来开始在附属数据对象中遍历。


CMSG_NXTHDR()
    这个用于返回下一个附属数据对象的struct cmsghdr指针。这个宏会接受两个输入参数:
    指向struct msghdr结构的指针
    指向当前struct cmsghdr的指针
    如果没有下一个附属数据对象,这个宏就会返回NULL。

猜你喜欢

转载自blog.csdn.net/m_n_n/article/details/80343863