zoukankan      html  css  js  c++  java
  • UNIX域套接字编程和socketpair 函数

    一、UNIX Domain Socket IPC

    socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。


    使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

    UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

    #define UNIX_PATH_MAX    108

    struct sockaddr_un {
    sa_family_t sun_family;               /* AF_UNIX */
    char sun_path[UNIX_PATH_MAX];  /* pathname */
    };


    二、回射/客户服务器程序

    通信的流程跟前面说过的tcp/udp 是类似的,下面直接来看程序:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
     
    /*************************************************************************
        > File Name: echoser_tcp.c
        > Author: Simba
        > Mail: dameng34@163.com
        > Created Time: Sun 03 Mar 2013 06:13:55 PM CST
     ************************************************************************/

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<sys/un.h>

    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while (0)

    void echo_ser(int conn)
    {
        char recvbuf[1024];
        int n;
        while (1)
        {

            memset(recvbuf, 0, sizeof(recvbuf));
            n = read(conn, recvbuf, sizeof(recvbuf));
            if (n == -1)
            {
                if (n == EINTR)
                    continue;

                ERR_EXIT("read error");
            }

            else if (n == 0)
            {
                printf("client close ");
                break;
            }

            fputs(recvbuf, stdout);
            write(conn, recvbuf, strlen(recvbuf));
        }

        close(conn);
    }

    /* unix domain socket与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。*/
    int main(void)
    {
        int listenfd;
        if ((listenfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
            ERR_EXIT("socket error");

        unlink("/tmp/test socket"); //地址复用
        struct sockaddr_un servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sun_family = AF_UNIX;
        strcpy(servaddr.sun_path, "/tmp/test socket");

        if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("bind error");

        if (listen(listenfd, SOMAXCONN) < 0)
            ERR_EXIT("listen error");

        int conn;
        pid_t pid;

        while (1)
        {

            conn = accept(listenfd, NULL, NULL);
            if (conn == -1)
            {

                if (conn == EINTR)
                    continue;
                ERR_EXIT("accept error");
            }

            pid = fork();
            if (pid == -1)
                ERR_EXIT("fork error");
            if (pid == 0)
            {
                close(listenfd);
                echo_ser(conn);
                exit(EXIT_SUCCESS);
            }

            close(conn);
        }

        return 0;
    }


     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
     
    /*************************************************************************
        > File Name: echocli_tcp.c
        > Author: Simba
        > Mail: dameng34@163.com
        > Created Time: Sun 03 Mar 2013 06:13:55 PM CST
     ************************************************************************/

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<sys/un.h>

    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while (0)

    void echo_cli(int conn)
    {
        char sendbuf[1024] = {0};
        char recvbuf[1024] = {0};
        while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {


            write(conn, sendbuf, strlen(sendbuf));
            read(conn, recvbuf, sizeof(recvbuf));
            fputs(recvbuf, stdout);
            memset(recvbuf, 0, sizeof(recvbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }

        close(conn);
    }


    int main(void)
    {
        int sock;
        if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
            ERR_EXIT("socket error");

        struct sockaddr_un servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sun_family = AF_UNIX;
        strcpy(servaddr.sun_path, "/tmp/test socket");

        if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("connect error");

        echo_cli(sock);

        return 0;
    }


    server 使用fork 的形式来接受多个连接,server调用bind 会创建一个文件,如下所示:

    simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ls -l /tmp/test socket 
    srwxrwxr-x 1 simba simba 0 Jun 12 15:27 /tmp/test socket

    即文件类型为s,表示SOCKET文件,与FIFO(命名管道)文件,类型为p,类似,都表示内核的一条通道,读写文件实际是在读写内核通道。程序中调用unlink(解除硬链接) 是为了在开始执行程序时删除以前创建的文件,以便在重启服务器时不会提示address in use。其他方面与以前说过的回射客户服务器程序没多大区别,不再赘述。


    三、UNIX域套接字编程注意点

    1、bind成功将会创建一个文件,权限为0777 & ~umask
    2、sun_path最好用一个绝对路径
    3、UNIX域协议支持流式套接口与报式套接口
    4、UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN。


    四、socketpair 函数

    功能:创建一个全双工的流管道
    原型 int socketpair(int domain, int type, int protocol, int sv[2]);
    参数
    domain: 协议家族
    type: 套接字类型
    protocol: 协议类型
    sv: 返回套接字对
    返回值:成功返回0;失败返回-1

    实际上socketpair 函数跟pipe 函数是类似的,也只能在同个主机上具有亲缘关系的进程间通信,但pipe 创建的匿名管道是半双工的,而socketpair 可以认为是创建一个全双工的管道。

    可以使用socketpair 创建返回的套接字对进行父子进程通信:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
     
    /*************************************************************************
        > File Name: echoser.c
        > Author: Simba
        > Mail: dameng34@163.com
        > Created Time: Fri 01 Mar 2013 06:15:27 PM CST
     ************************************************************************/

    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<string.h>

    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while (0)


    int main(void)
    {
        int sockfds[2];

        if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)
            ERR_EXIT("sockpair");

        pid_t pid;
        pid = fork();
        if (pid == -1)
            ERR_EXIT("fork");

        if (pid > 0)
        {
            int val = 0;
            close(sockfds[1]);
            while (1)
            {

                ++val;
                printf(" sending data: %d ", val);
                write(sockfds[0], &val, sizeof(val));
                read(sockfds[0], &val, sizeof(val));
                printf("recv data : %d ", val);
                sleep(1);
            }

        }

        else if (pid == 0)
        {

            int val;
            close(sockfds[0]);
            while (1)
            {

                read(sockfds[1], &val, sizeof(val));
                ++val;
                write(sockfds[1], &val, sizeof(val));
            }
        }

        return 0;
    }


    输出如下:

    simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./socketpair 
     sending data: 1
    recv data : 2
     sending data: 3
    recv data : 4
     sending data: 5
    recv data : 6
     sending data: 7
    recv data : 8
     sending data: 9
    recv data : 10
     sending data: 11
    recv data : 12
     sending data: 13
    recv data : 14
     sending data: 15
    recv data : 16
    ...................................


    即父进程持有sockfds[0] 套接字进行读写,而子进程持有sockfds[1] 套接字进行读写。


    参考:

    《Linux C 编程一站式学习》

    《TCP/IP详解 卷一》

    《UNP》

  • 相关阅读:
    vue3父组件方法之间方法的互相调用
    vue3获取数据的注意点
    2021牛客暑期多校训练营5 D. Double Strings(DP/排列组合)
    2021牛客暑期多校训练营8 K. Yet Another Problem About Pi(几何)
    2021牛客暑期多校训练营8 D. OR(位运算/结论)
    2021牛客暑期多校训练营5 J. Jewels(二分图最大权匹配)
    关于C++ STL中对于set使用lower_bound进行二分查找的效率问题
    CSP202012-4 食材运输(70分)
    【k8s】Volume-pv-local
    【k8s】Volume-pvc
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8472989.html
Copyright © 2011-2022 走看看