美文网首页
linux用户空间 - 多进程编程(四)

linux用户空间 - 多进程编程(四)

作者: 404Not_Found | 来源:发表于2021-06-12 15:26 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20210608 23:23
    • 目的:Unix domain Socket and Socket pair
    # Unix domain
      ## 关于STREAM 和 DGRAM的 Connect
      ## unix domain 的文件路径
         ## 面向流代码举例
         ## 面向数据报代码举例
      ## asbtract path name
      ## uname path name
    #lsof 工具简介
    

    Unix domain

    • Unix domain socket 不同于 internet (address family)domain socket(AF_INET), 他没有ip地址和端口号,而是靠一个文件路径来区分地址的。
      unix socket domain 使用结构体 sockaddr_un
    • 文件路径有三种 man unix 即可查阅
      1. pathname 正常路径
      2. unname 无名 used for socket pair
      3. abstract name 抽象 与2相同,不会与文件系统产生关联
    • 面向流 与 面向数据报
      unix domain 的 面向数据包是可靠的, 保序的,也不会乱序。unix domain 是本机的。


      面向流.png

      面向流的 对于 服务来说,每次connect 一次,就会新生成一个fd。

    面向数据报.png

    关于 STREAM 和 DRAM 的 connect

    • 面向流的 connect 有个 三次握手的概念,另一端的Server 在accept 等,C端 则取 connect
    • 面向数据报的 connect 并没有三次握手的概念,而是default 的去连谁,报默认发给谁。无需事先建立连接
      • 只要知道路径,大家都可以互相通信
      • connect + send 指名了发给默认
      • 如果没有connect 需要调用 sendto system call

    man connect 即可查阅相关区别
    If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type
    SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by addr.

    pathname 文件路径

    面向流的 代码举例:

    /*server.c */
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    //定义用于通信的文件名
    
    #define UNIX_DOMAIN "/tmp/UNIX.domain"
    
    int main()
    {
    
        socklen_t clt_addr_len;
        int listen_fd;
        int com_fd;
        int ret;
        int i;
        static char recv_buf[1024];
        int len;
        struct sockaddr_un clt_addr;
        struct sockaddr_un srv_addr;
    
        //创建用于通信的套接字,通信域为UNIX通信域
    
        listen_fd=socket(PF_UNIX,SOCK_STREAM,0);
        if(listen_fd<0){
            perror("cannot create listening socket");
            return 1;
        }
    
        /*
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_prot = htons(SERV_PORT);
        */
        //设置服务器地址参数
        srv_addr.sun_family=AF_UNIX;
        strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);
    
        //绑定套接字与服务器地址信息
        ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
        if(ret==-1){
            perror("cannot bind server socket");
            close(listen_fd);
            unlink(UNIX_DOMAIN);
            return 1;
    
        }
    
        //对套接字进行监听,判断是否有连接请求
        ret=listen(listen_fd,1);
        if(ret==-1){
            perror("cannot listen the client connect request");
            close(listen_fd);
            unlink(UNIX_DOMAIN);
            return 1;
    
        }
    
        //当有连接请求时,调用accept函数建立服务器与客户机之间的连接
        len=sizeof(clt_addr);
        com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
        if(com_fd<0){
            perror("cannot accept client connect request");
            close(listen_fd);
            unlink(UNIX_DOMAIN);
    
            return 1;
        }
    
        //读取并输出客户端发送过来的连接信息
        printf("\n=====info=====\n");
        for(i=0;i<4;i++){
            memset(recv_buf,0,1024);
            int num=read(com_fd,recv_buf,sizeof(recv_buf));
            printf("Message from client (%d)) :%s\n",num,recv_buf);
        }
    
        close(com_fd);
        close(listen_fd);
    
        unlink(UNIX_DOMAIN);
    
        return 0;
    }
    
    
    /*client.c*/
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    //定义用于通信的文件名
    #define UNIX_DOMAIN "/tmp/UNIX.domain"
    int main(void)
    {
        int connect_fd;
        int ret;
        char snd_buf[1024];
        int i;
    
        static struct sockaddr_un srv_addr;
        //创建用于通信的套接字,通信域为UNIX通信域
    
        connect_fd=socket(PF_UNIX,SOCK_STREAM,0);
        if(connect_fd<0){
            perror("cannot create communication socket");
            return 1;
        }
    
        srv_addr.sun_family=AF_UNIX;
        strcpy(srv_addr.sun_path,UNIX_DOMAIN);
    
        //连接服务器
        ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
        if(ret==-1){
            perror("cannot connect to the server");
            close(connect_fd);
    
            return 1;
        }
    
    
        memset(snd_buf,0,1024);
        strcpy(snd_buf,"message from client");
    
        //给服务器发送消息
        for(i=0;i<4;i++)
            write(connect_fd,snd_buf,sizeof(snd_buf));
    
        close(connect_fd);
        return 0;
    }
    
    

    面向数据报的代码举例

    /*server*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    char * server_filename = "/tmp/socket-server";
    
    int main(void)
    {
        int s;
        struct sockaddr_un srv_un = {0};
    
        if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
            perror("socket server");
            exit(1);
        }
    
        srv_un.sun_family = AF_UNIX;
        strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));
        /*If you leave the file behind when you're finished, or perhaps crash after binding, the next bind will fail
        / with "address in use". Which just means, the file is already there.*/
        unlink(srv_un.sun_path);
    
        if (bind(s, (struct sockaddr *)&srv_un, sizeof(srv_un)) == -1) {
            perror("bind server");
            exit(1);
        }
    
        for(;;) {
    
            char buf[1024] = {0};
            read(s, buf, sizeof(buf));
            printf("RECEIVED: %s", buf);
    
        }
    
        close(s);
    
        return 0;
    }
    
    
    /*client*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    char * server_filename = "/tmp/socket-server";
    char * client_filename = "/tmp/socket-client";
    
    int main(void)
    {
    
        int s;
        char obuf[100];
        struct sockaddr_un srv_un, cli_un = { 0 };
        
        srv_un.sun_family = AF_UNIX;
        strncpy(srv_un.sun_path, server_filename, sizeof(srv_un.sun_path));
    
        cli_un.sun_family = AF_UNIX;
        strncpy(cli_un.sun_path, client_filename, sizeof(cli_un.sun_path));
        unlink(cli_un.sun_path);
    
        if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
            perror("socket server");
            exit(1);
        }
    
        /* (http://stackoverflow.com/questions/3324619/unix-domain-socket-using-datagram-communication-between-one-server-process-and)
        Here, we bind to our client node, and connect to the server node. As Unix domain sockets need to have endpoints on either end
        of the connection. For more info, visit the URL.*/
        if (bind(s, (struct sockaddr *)&cli_un, sizeof(cli_un)) == -1) {
            perror("bind client");
            exit(1);
        }
    
        if (connect(s, (struct sockaddr *) &srv_un, sizeof(srv_un)) == -1) {
            perror("connect client");
            exit(1);
        }
    
        //printf("Connected.\n");
    
        while(printf("> "), fgets(obuf, 100, stdin), !feof(stdin)) {
            /*send 函数并没指名发给谁,因为上面已经有了 connect 默认的 发送对象*/
            if (send(s, obuf, strlen(obuf), 0) == -1) {
                perror("send");
                exit(1);
            }
            break;
        }
    
        //printf("Sent successfully.\n");
    
        close(s);
    
        return 0;
    }
    
    
    1. 编译运行 server.c
      可以看到 /tmp/UNIX.domain, 文件类型是socket


      UNIX.domain.png
    2. netstat -anp|grep UNIX


      netstat.png
    3. 编译运行 client.c
      随着 server.c 的关闭,/tmp/UNIX.domain 消失, 文件也没了,netstat 也没了


      图片.png
    4. 查看更多信息
      pidof server => lsof pid


      lsof.png

    一个socket 对于 一个 fd来讲,无非也就是一个fd

    abstract path 文件路径

    C/S 都应该使用抽象的路径
    实际上就是把第一个字符修改成 '0' 即可。


    lsof.png

    从路径来看抽象的path 在路径里换成了@

    这条路径是与文件系统没有关系的。所以 在/tmp 路径下也看不到 unix.domain

    unnmae 无名 即socketpair

    int s[2]; /*pair of sockets*/
    z = socketpair(AF_LOCAL, SOCKET_STREAM, o, s);
    

    socket pair 与 pipe 相似,但最大不同是 socketpair 是 双工的,pipe 是单工的。
    工程中经常约定,一个进程在s[0] 读写,另一个在s[1] 读写

    #include <stdio.h> 
    #include <string.h> 
    #include <unistd.h> 
    #include <sys/types.h> 
    #include <errno.h> 
    #include <sys/socket.h> 
    #include <stdlib.h> 
    
    const char* str = "Hello World";
    
    int main(int argc, char* argv[]){
        char buf[128] = {0};
        int fd[2]; 
        pid_t pid; 
    
        if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1 ) { 
            return EXIT_FAILURE; 
        } 
    
        pid = fork();
        if(pid < 0) {
            return EXIT_FAILURE;
        } else if(pid > 0) {
            close(fd[1]);
    
            write(fd[0], str, strlen(str));
            read(fd[0], buf, sizeof(buf));
            printf("parent received: %s\n", buf);
        } else if(pid == 0) {
            close(fd[0]);
    
            read(fd[1], buf, sizeof(buf));  
            write(fd[1], str, strlen(str));
            printf("child received: %s\n",buf);
        }
    
        while(1);
    } 
    
    

    父子进程彼此发送了 hello world

    进程间的通讯,只要是有血缘关系,通信起来就简单很多

    lsof 工具简介

    • 看进程打开的文件
    • 看文件被什么进程打开 lsof + 文件名
    • 看IPC
      1. ipcs
      2. lsof | grep shmid
    • 看 socket
      lsof -p 进程ID

    相关文章

      网友评论

          本文标题:linux用户空间 - 多进程编程(四)

          本文链接:https://www.haomeiwen.com/subject/ptdseltx.html