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》

  • 相关阅读:
    Codeforces 1485C Floor and Mod (枚举)
    CodeForces 1195D Submarine in the Rybinsk Sea (算贡献)
    CodeForces 1195C Basketball Exercise (线性DP)
    2021年初寒假训练第24场 B. 庆功会(搜索)
    任务分配(dp)
    开发工具的异常现象
    Telink MESH SDK 如何使用PWM
    Telink BLE MESH PWM波的小结
    [LeetCode] 1586. Binary Search Tree Iterator II
    [LeetCode] 1288. Remove Covered Intervals
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8472989.html
Copyright © 2011-2022 走看看