zoukankan      html  css  js  c++  java
  • linux网络编程之socket编程(十六)

    继续学习socket编程,今天的内容会有些难以理解,一步步来分解,也就不难了,正入正题:

     

    实际上sockpair有点像之前linux系统编程中学习的pipe匿名管道匿名管道是半双工的,只能用于亲缘关系的进程间进行通信,也就是说父子进程或兄弟进程间进行通讯,因为它是没有名称的,父子进程可以通过共享描述符的方式来进行通信,子进程继承了父进程的文件描述符,从而达到了通信的目的。而今天学习的sockpair是一个全双工的流管道,其它也一样,也只能用于父子进程或亲缘关系之间进行通讯,所以其中sv套接字对就很容易理解了,但是跟pipe有些区别,先来回顾下pipe:

    其中的fd也是套接字对,一端表示读,一端表示写,而sockpair中的sv[0]、sv[1]两端分别既可以表示读端,也可以表示写端。

    认识了sockpair函数原形,下面用程序来说明它的用法:

    首先创业一个套接字对:

    由于它也是只能用于父子进程或亲缘关系之间进行通讯,所以需要fork进程出来:

    下面就来实现父子进程进行通讯:

    而对于子进程而言,代码基本类似:

    编译运行看结果:

    从结果运行来看,通过sockpair就完成了全双工的通讯。

    学习这两个函数的目的,是为了通过UNIX域协议如何传递文件描述字,关于这个函数的使用会比较复杂,需慢慢理解。

    首先来查看一下man帮助:

    其中第二个参数是msghdr结构体,所以有必要来研究一下这个结构体:

    哇,这个结构体貌似挺复杂的,下面一一来熟悉其字段含义:

    这时,需要来看另外一个函数了,该结构体在其中有介绍到:

    那怎么理解该参数呢?这个需要从send函数来分析:

    所以iovec结构体的字段就可以从send的这两个参数来理解:

    并且,可以发现:

    下面来看一个示意图:

    从上面示意图中可以发现,如果用sendmsg函数,就可以发送多个缓冲区的数据了,而如果用send只能发送一个缓冲区,所以从这也可以看出sendmsg的强大。

    如果说要传递文件描述字,还需要发送一些辅助的数据,这些辅助数据是一些控制信息,也就是下面这些参数:

    而其中msg_control是指向一个结构体,那它长啥样呢?需要从另外一个函数的帮助文档中得知:

    那具体属性的含议是啥呢?

    实际上,在填充这些数据的时候,并没有这么简单,它还会按照一定的方式来进行对齐,接下来再来看另外一个示意图---辅助数据的示意图:

    其中可以看到定义了一些宏,这是由于:

    所以,下面来认识一下这些宏定义:

    其中"size_t CMSG_SPACE(size_t length)",结合图来说明就是:

    大致了解了以上这些数据结构,下面则可以开始编写代码来传递描述字了,但是代码会比较复杂,可以一步步来理解,下面开始。

    实际上,就是能过以下两个函数来封装发送和接收文件描述字的功能,如下:

    首先封装发送文件描述字的方法:

    下面一步步来实现该函数,首先准备第二个参数:

    所以,先声明一个该结构体:

    接下来填充里面的各个字段,还是看图说话:

    所以:

    接下来指定缓冲区:

    最后则要准备辅助数据了,因为我们是发送文件描述字,这也是最关键的:

    所以msg_control需要指向一个辅助数据的缓冲区,其大小根据发送的文件描述符来获得,如下:

    接下来,则需要准备缓冲区中cmsghdr中的数据,也就是发送文件描述字主要是靠它:

    另外关于数据的填充我们不需要关心,因为都是用系统提供的宏来操作数据的,当所有的数据都准备好之后,下面则可以开始发送了:

    接下来,则需要封装一个接收文件描述字的函数了,由于怎么发送文件描述字已经很明白了,所以接收也就很简单了,基本类似,这里面就不一一进行说明了:

    以上发送和接收文件描述字的函数都已经封装好了,接下来利用这两个函数来实现文件描述字的真正传递实验,实验的思路是这样:如果父进程打开了一个文件描述字,再fork()时,子进程是能共享父进程的文件描述字的,也就是只要在fork()之前,打开文件描述字,子进程就能共享它;但是当fork()进程之后,如果一个子进程打开一个文件描述字,父进程是无法共享获取的,所以,这里就可以利用这个原理,来将文件描述字从子进程传递给父进程,还是用sockpair函数,具体如下:

    下面编译运行看一下效果:

    另外,文件描述字的传递,只能通过UNIX域协议的套接字,当前是利用了sockpair函数来实现了父子进程文件描述字的传递,而如果要实现不相关的两个进程之间传递,就不能用socketpair了,就得用上一节中介绍的UNIX域套接字来进行传递,而普通的TCP套接字是不能传递文件描述字的,这个是需要明白了。

    最后贴出代码:

    send_fd.c:

    #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)
    {
        struct msghdr msg;
        struct iovec vec;
        struct cmsghdr *p_cmsg;
    
        char sendchar = 0;
        vec.iov_base = &sendchar;
        vec.iov_len = sizeof(sendchar);
    
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1;
    
        char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
    
        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));
        int *p_fds;
        p_fds = (int*)CMSG_DATA(p_cmsg);
        *p_fds = send_fd;
    
        int ret;
        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=%s
    ", 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;
    }

    今天学的东西有点复杂,主要是得搞清楚其结构体的填充,对照着示意图来其实也不难,需要好好消化,下节再见~~

  • 相关阅读:
    Android PopupWindow显示位置和显示大小
    线性回归与梯度下降
    nginx启动过程分析
    项目管理学习笔记之三.绩效分析
    会计总论读书笔记
    电子书阅读及工具
    mybatis-mysql小优化
    List去重
    JAVA8之lambda表达式详解,及stream中的lambda使用
    linux部署mongodb及基本操作
  • 原文地址:https://www.cnblogs.com/webor2006/p/4127627.html
Copyright © 2011-2022 走看看