技术: Unix Domain Socket

UNIX / Linux 套接字家族中的一员,AF_UNIX是它的大名, 常用于 IPC

脱离网络的套接字还是socket么? 我还是把它归类在network里面了, 但是它更多的是用在ipc方面.

本文就只说unix domain socket用于ipc的优势, 以及编程思路.

引子

本地套接字, 在socket框架上衍生出来的, 网络套接字, 指定ip为loopback 127.0.1 时, 也可以进行进程间通信; 不过进程间通信最好还是选用 unix-domain socket.

ipc方式还有哪些?

  • pipe和fifo (实现简单, fifo跨血缘)
  • mmap共享内存 (非血缘关系)
  • 信号 (开销小, 携带数据小, 不可靠)
  • 消息队列 (不可重复读, 和管道一样)

正文

简介

两个进程的pcb块儿都指向了各自的打开的socket fd, 在网络中需要借助ip + port让这两个connfd进行通信, 但是同一机器的进程, 就不需要借助网络了.可以直接让这两个socket fd 进行通信.

为什么?
因为内核区的文件描述符表时全局公用的, 即使socket fd不同, 但是大家是相互知道的.
所以这俩sockfd建立关系, 直接借助fd编号就可以了, 不需要ip, port, 并且这种方式建立的连接是全双工的通信方式(除了管道之外貌似全部都是双工).

由于socket函数的设计, 还是可以传入SOCK_DGRAM或者SOCK_STREAM, 实际上没有使用.

1
socket(AF_UNIX, SOCK_STREAM, 0);

套接字定义

并且socket结构体的定义也不一样.

网络中套接字的结构体定义有3个成员:

1
2
3
4
5
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 协议类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP 地址
};

本地套接字的定义是这样的:

1
2
3
4
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};

  • 第一个成员变量 sun_family 还是表示协议类型, 一般大小为16位, 即2字节.
    (为了跨posix系统你还是使用offsetof宏计算一下)
  • 第二个成员变量 sun_path 表示文件路径名, 全长108字节(一般用不满)
    将socket绑定到一个地址上(实际上就是一个文件, 运行时会生成这个文件; 但是这是个伪文件, 没有大小的, 不管通信双发传输了多少数据, 都不会有存留的)

可以看到sockaddr_un实际全部大小110字节Bytes.

offsetof宏

第一个成员 sun_family长度不同系统定义可能有些差别, 代码中一般借助宏函数 offsetof :

1
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)

注意 -> 优先级比取地址&高, 这个宏其实就是计算member成员在type结构体中的偏移, 如果type传入的是结构体的起始0位置, 那么偏移也就是长度.
(memeber距离0位置的偏移即长度)

这个时候求sockaddr结构体的有效长度就不能再用sizeof(sockaddr_un), 而应该求有效长度.

调用大致: (求整个结构体的有效长度: 结构体开始处到第二个成员的长度 + 套接字路径名长度)

1
2
//实际上应该是: 2Bytes + 文件名长度
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

本地套接字绑定

套接字结构体的第二个参数就是让套接字绑定到一个伪文件(client和server各自绑定一个), 并且server端 bind, client端connect之前bind, 都要把两边儿需要绑定的sock地址(文件)删除, 确保socket需要绑定的文件不存在. 如果已经存在, 会报错: Address alreay in use.

最好代码上进行 unlink("filename") 一下:

1
unlink(un.sun_path);

代码示例

server.cpp

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
* name : file_name
*/
int serv_listen(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* in case it already exists */
unlink(name);
/* fill in socket address structure */
memset(&un, 0, sizeof(un)); //bzero ok
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -3;
goto errout;
}
return(fd);
errout:
err = errno; /*tmp store in case of close error*/
close(fd);
errno = err;
return(rval);
}

/*uidptr: in/out param which shows the caller*/
int serv_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;/*display file or file system status*/
len = sizeof(un); /*watch out here*/
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1); /* often errno=EINTR, if signal caught */
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */

if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}

if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; /* not a socket */
goto errout;
}

if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
/* we're done with pathname now */
unlink(un.sun_path);
return(clifd);
errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}


int main(void)
{
int lfd, cfd, n, i;
uid_t cuid;
char buf[1024];

lfd = serv_listen("foo.socket");
if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}

cfd = serv_accept(lfd, &cuid);
if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break;
case -2:perror("a bad filename"); break;
case -1:perror("accept"); break;
}
exit(-1);
}
//watch out we get the cuid
while (1) {
r_again:
n = read(cfd, buf, 1024);
if (n == -1) {
if (errno == EINTR)
goto r_again;
}
else if (n == 0) {
printf("the other side has been closed.\n");
break;
}
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, n);
}

close(cfd);
close(lfd);
return 0;
}

client.cpp

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
* name : file_name
*/
int serv_listen(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* in case it already exists */
unlink(name);
/* fill in socket address structure */
memset(&un, 0, sizeof(un)); //bzero ok
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -3;
goto errout;
}
return(fd);
errout:
err = errno; /*tmp store in case of close error*/
close(fd);
errno = err;
return(rval);
}

/*uidptr: in/out param which shows the caller*/
int serv_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;/*display file or file system status*/
len = sizeof(un); /*watch out here*/
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1); /* often errno=EINTR, if signal caught */
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */

if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}

if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; /* not a socket */
goto errout;
}

if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
/* we're done with pathname now */
unlink(un.sun_path);
return(clifd);
errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}


int main(void)
{
int lfd, cfd, n, i;
uid_t cuid;
char buf[1024];

lfd = serv_listen("foo.socket");
if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}

cfd = serv_accept(lfd, &cuid);
if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break;
case -2:perror("a bad filename"); break;
case -1:perror("accept"); break;
}
exit(-1);
}
//watch out we get the cuid
while (1) {
r_again:
n = read(cfd, buf, 1024);
if (n == -1) {
if (errno == EINTR)
goto r_again;
}
else if (n == 0) {
printf("the other side has been closed.\n");
break;
}
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, n);
}

close(cfd);
close(lfd);
return 0;
}

尾巴

文章目录
  1. 1. 引子
  2. 2. 正文
    1. 2.1. 简介
      1. 2.1.1. 套接字定义
      2. 2.1.2. offsetof宏
      3. 2.1.3. 本地套接字绑定
    2. 2.2. 代码示例
  3. 3. 尾巴
|