服务器端的程序名为ruptimed,服务名为ruptime,为客户端提供uptime服务,由于ruptime服务是用户自定义的服务,在调用getaddrinfo时出现如下错误:
1 Servname not supported for ai_socktype
我给getaddrinfo函数提供了主机名和服务名,但需要在系统中登记,即将ruptime和端口号写入/etc/services中(注意用户自定义的端口号不能小于1024,以免和系统已经存在的端口号冲突。程序名和服务名不必相同,服务名更像是程序的别名),将
ruptime 4000/tcp
添加到/etc/services文件末尾,这样就不会出现上面的错误。
所以整个程序的执行过程是:将服务器进程初始化为一个守护进程,程序名为ruptimed,这个程序提供ruptime服务(即调用uptime函数),调用getaddrinfo时,进程去主机host查找“ruptime”在/etc/services对应的端口号,经过hint过滤addrinfo的链表中,选取一个地址,将其和一个套接字绑定,然后监听,以便服务器和客户端建立链接。客户端调用getaddrinfo函数查找服务器上的ruptime服务,建立链接。
注意:addinfo链表中的地址都是网络字节序,所以如果要在本地查看,需要调用ntohs/ntohl函数。
也可以将getaddrinfo函数的第二个参数写成端口号,这时就没有必要在/etc/services文件中登记了,但是hint的ai_flags标志位需要设定为AI_PASSIVE,表示服务器端的绑定监听。
在客户端,如果用户不知道端口号,可以用服务名代替,但是仍然需要在/etc/services文件中登记,不然依然会出现上面的错误,此时端口号不需要和服务器端登记的端口号保持一致(不知原因)。当客户端以端口号的方式连接服务器时,ai_flags不用设为AI_PASSIVE,一般为AI_CANONNAME。
相关代码如下:
客户端:
1 #include <string.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <netdb.h>
7 #include <sys/socket.h>
8
9 #define MAXADDRLEN 256
10 #define BUFLEN 128
11
12 extern int connect_retry(int, const struct sockaddr *, socklen_t); 13
14 void
15 print_uptime(int sockfd) 16 { 17 int n; 18 char buf[BUFLEN]; 19
20 while((n = recv(sockfd, buf, BUFLEN, 0)) > 0) 21 write(STDOUT_FILENO, buf, n); 22 if(n < 0) 23 printf("recv error"); 24 } 25
26 int
27 main(int argc, char *argv[]) 28 { 29 struct addrinfo *ailist, *aip; 30 struct addrinfo hint; 31
32 int sockfd, err; 33
34 if(argc != 2) { 35 printf("usage: ruptime hostname"); 36 exit(1); 37 } 38
39 hint.ai_flags = 0; 40 hint.ai_family = 0; 41 hint.ai_socktype = SOCK_STREAM; 42 hint.ai_protocol = 0; 43 hint.ai_addrlen = 0; 44 hint.ai_canonname = NULL; 45 hint.ai_addr = NULL; 46 hint.ai_next = NULL; 47
48 if((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0) { 49 printf("getaddrinfo error: %s\n", gai_strerror(err)); 50 exit(1); 51 } 52
53 for(aip = ailist; aip != NULL; aip = aip->ai_next) 54 { 55 if((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0) 56 err = errno; 57 if(connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0) 58 err = errno; 59 else { 60 print_uptime(sockfd); 61 exit(0); 62 } 63 } 64 fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err)); 65 exit(1); 66 }
服务器端:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <syslog.h>
5 #include <sys/socket.h>
6 #include <netdb.h>
7 #include <errno.h>
8
9 #define BUFLEN 128
10 #define QLEN 10
11
12 #ifndef HOST_NAME_MAX 13 #define HOST_NAME_MAX 256
14 #endif
15
16 extern int initserver(int, struct sockaddr *, socklen_t , int); 17
18 void
19 serve(int sockfd) 20 { 21 int clfd; 22 FILE *fp; 23 char buf[BUFLEN]; 24
25 for(;;) { 26 clfd = accept(sockfd, NULL, NULL); 27 if(clfd < 0) { 28 syslog(LOG_ERR, "ruptime: accept error:%s", 29 strerror(errno)); 30 exit(1); 31 } 32 if((fp = popen("/usr/bin/uptime", "r")) == NULL) { 33 sprintf(buf, "error:%s\n", strerror(errno)); 34 send(clfd, buf, strlen(buf), 0); 35 } else { 36 while(fgets(buf, BUFLEN, fp) != NULL) 37 send(clfd, buf, strlen(buf), 0); 38 pclose(fp); 39 } 40 close(clfd); 41 } 42 } 43
44 int
45 main(int argc, char *argv[]) 46 { 47 struct addrinfo *ailist, *aip; 48 struct addrinfo hint; 49
50 int sockfd, err, n; 51 char *host; 52
53 if(argc != 1) { 54 printf("usage: ruptimed"); 55 exit(1); 56 } 57
58 #ifdef _SC_HOST_NAME_MAX 59 n = sysconf(_SC_HOST_NAME_MAX); 60 if(n < 0) 61 #endif
62
63 n = HOST_NAME_MAX; 64 host = malloc(n); 65 if(host == NULL) { 66 perror("malloc error"); 67 exit(1); 68 } 69
70 if(gethostname(host, n) < 0) { 71 perror("gethostname error"); 72 exit(1); 73 } 74
75 printf("in main pid: %d\n", getpid()); 76
77 daemonize("ruptimed"); 78 hint.ai_flags = AI_CANONNAME; 79 hint.ai_family = 0; 80 hint.ai_socktype = SOCK_STREAM; 81 hint.ai_protocol = 0; 82 hint.ai_addrlen = 0; 83 hint.ai_canonname = NULL; 84 hint.ai_next = NULL; 85
86 syslog(LOG_DEBUG, "ruptimed pid: %d\n", getpid()); 87 if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) { 88 syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err)); 89 exit(1); 90 } 91 for(aip = ailist; aip != NULL; aip = aip->ai_next) { 92 if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) { 93 serve(sockfd); 94 exit(0); 95 } 96 } 97 exit(1); 98 }
服务器输出:
1 ./ruptimed 2 in main pid: 2528
3 return from daemonize pid: 2530
可以看到调用daemonize函数前后,进程已经变化,不再是同一个进程,此时服务器进程成为一个守护进程。第二行由ruptimed程序输出,第三行由daemonize程序输出。
这里将服务器端初始化为一个守护进程,所以第77行daemonize函数之后的代码都不能标准输出到终端,只能调用syslog函数查看,输出如下:
1 Oct 2 12:06:43 elvis ruptimed: ruptimed pid: 2530
用到的makefile如下:
1 CFLAGS=-I/usr/include -Wall -g 2 LDFLAGS=-L/usr/local/lib 3 all:client ruptimed 4 .PHONY:all 5
6 client:client.o connect_retry.o 7 gcc client.o connect_retry.o -o client 8 ruptimed:server.o daemonize.o initserver.o 9 gcc server.o daemonize.o initserver.o -o ruptimed 10 clean: 11 rm -i *.o