前段時間寫一個傳遞文件句柄的小 demo,有 server 端、有 client 端,之間經過 Unix Domain Socket 通信。git
在普通模式下,雙方能夠正常創建鏈接,當server端做爲daemon啓動時,則第一次啓動成功,以後再啓動, listen 會鏈接報 ENOTSUPP 錯誤,致使啓動失敗。github
spipe.cbash
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 端輸出爲:socket
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 運行時正常的輸出以下:oop
create socket ok bind socket to path ok start listen on socket ok accept ok new connection: uid 0, fd 4
可能細心的讀者會以爲,以 daemon 方式運行 printf 怎麼還能夠輸出呢,是有如下宏定義作了處理:ui
1 #ifdef USE_APUE 2 #include "../apue.h" 3 #define printf log_msg 4 #endif
以 daemon 運行時會定義 USE_APUE 宏,從而將 printf 重定義爲 log_msg 輸出到 syslog。spa
下面是 server 端的代碼:debug
csopend2.ccode
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 運行(與常識相反?),上面標黃的代碼就是。server
對應的 client 端代碼:
一開始懷疑是用於 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 域套接字還有什麼區別麼?
暫時存疑……