大神求教系列,又遇到想不通的地方了
前段時間寫一個傳遞檔案句柄的小 demo,有 server 端、有 client 端,之間通過 Unix Domain Socket 通訊。
在普通模式下,雙方可以正常建立連接配接,當server端作為daemon啟動時,則第一次啟動成功,之後再啟動, listen 會連接配接報 ENOTSUPP 錯誤,導緻啟動失敗。
spipe.c
1 int cli_conn(const char *name)
2 {
3 int fd, len, err, rval;
4 struct sockaddr_un un;
5
6 if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) {
7 printf ("create socket failed\n");
8 return -1;
9 }
10
11 printf ("create socket ok\n");
12 memset (&un, 0, sizeof (un));
13 un.sun_family = AF_UNIX;
14 strcpy (un.sun_path, name);
15 len = offsetof (struct sockaddr_un, sun_path) + strlen (name);
16 if (connect (fd, (struct sockaddr *)&un, len) < 0) {
17 err = errno;
18 printf ("connect failed\n");
19 rval = -4;
20 goto errout;
21 }
22
23 printf ("connect to server ok\n");
24 return fd;
25 errout:
26 close (fd);
27 errno = err;
28 return rval;
29 }
30
31
32 int serv_listen (const char *name)
33 {
34 int fd, len, err, rval;
35 struct sockaddr_un un;
36
37 if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) {
38 printf ("socket failed\n");
39 return -1;
40 }
41
42 printf ("create socket ok\n");
43 unlink (name);
44 memset (&un, 0, sizeof(un));
45 un.sun_family = AF_UNIX;
46 strcpy (un.sun_path, name);
47 len = offsetof (struct sockaddr_un, sun_path) + strlen (name);
48
49 if (bind (fd, (struct sockaddr *)&un, len) < 0) {
50 err = errno;
51 printf ("bind failed\n");
52 rval = -2;
53 goto errout;
54 }
55
56 printf ("bind socket to path ok\n");
57 if (listen (fd, QLEN) < 0) {
58 err = errno;
59 printf ("listen failed, errno %d\n", errno);
60 rval = -3;
61 goto errout;
62 }
63
64 printf ("start listen on socket ok\n");
65 return fd;
66 errout:
67 close (fd);
68 errno = err;
69 return rval;
70 }
71
72 int serv_accept (int listenfd, uid_t *uidptr)
73 {
74 int clifd, err, rval;
75 time_t staletime;
76 struct sockaddr_un un;
77 struct stat statbuf;
78
79 size_t len = sizeof (un);
80 if ((clifd = accept (listenfd, (struct sockaddr *)&un, &len)) < 0) {
81 printf ("accept failed\n");
82 return -1;
83 }
84
85 len -= offsetof (struct sockaddr_un, sun_path);
86 un.sun_path[len] = 0;
87 printf ("accept %s ok\n", un.sun_path);
88
89 unlink (un.sun_path);
90 return clifd;
91
92 errout:
93 close (clifd);
94 errno = err;
95 return rval;
96 }
出錯的位置在 serv_listen (line 57) 處,出錯時的 server 端輸出為:
Jan 17 00:24:44 localhost opend: create socket ok
Jan 17 00:24:44 localhost opend: bind socket to path ok
Jan 17 00:24:44 localhost opend: listen failed, errno 95
Jan 17 00:24:44 localhost opend: serv_listen error: Operation not supported
errno 95 為 ENOTSUPP。不以 daemon 運作時正常的輸出如下:
create socket ok
bind socket to path ok
start listen on socket ok
accept ok
new connection: uid 0, fd 4
可能細心的讀者會覺得,以 daemon 方式運作 printf 怎麼還可以輸出呢,是有以下宏定義做了處理:
1 #ifdef USE_APUE
2 #include "../apue.h"
3 #define printf log_msg
4 #endif
以 daemon 運作時會定義 USE_APUE 宏,進而将 printf 重定義為 log_msg 輸出到 syslog。
下面是 server 端的代碼:
csopend2.c
1 int main (int argc, char *argv[])
2 {
3 int c = 0;
4 log_open ("open.serv", LOG_PID, LOG_USER);
5
6 opterr = 0; // don't want getopt() writting to stderr !
7 while ((c = getopt (argc, argv, "d")) != EOF) {
8 switch (c) {
9 case 'd':
10 debug = log_to_stderr = 1;
11 break;
12 case '?':
13 err_quit ("unrecongnized option: -%c", optopt);
14 }
15 }
16
17 if (debug == 0)
18 {
19 log_to_stderr = 0;
20 daemonize ("opend");
21 }
22
23 loop ();
24 return 0;
25 }
不使用 -d 時表示 daemon 運作(與常識相反?),上面标黃的代碼就是。
對應的 client 端代碼:
csopenc.c
一開始懷疑是用于 listen 的本地 socket 檔案已經存在,于是去 /tmp 目錄看了下,果然有 opend 這個檔案,删除之,再運作,不行;
然後懷疑是沒有複用端口(?)所緻,于是在 listen 之前添加了以下代碼段:
1 int opt = 1;
2 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) < 0) {
3 err = errno;
4 printf ("setsockopt failed\n");
5 rval = -3;
6 goto errout;
7 }
設定端口複用。編譯、運作,輸出如下:
Jan 17 00:43:11 localhost opend: create socket ok
Jan 17 00:43:11 localhost opend: bind socket to path ok
Jan 17 00:43:11 localhost opend: set socket option ok
Jan 17 00:43:11 localhost opend: listen failed, errno 95
Jan 17 00:43:11 localhost opend: serv_listen error: Operation not supported
設定成功了,但還是不行
難道 daemon 與普通程序使用 Unix 域套接字還有什麼差別麼?
暫時存疑……