您的位置:首页 > 理论基础 > 计算机网络

linux网络编程之socket(十五):UNIX域套接字编程和socketpair 函数

2013-06-12 16:19 435 查看
一、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\n");

            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\n", val);

            write(sockfds[0], &val, sizeof(val));

            read(sockfds[0], &val, sizeof(val));

            printf("recv data : %d\n", 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》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐