APUE中的C/S程序
服务器端的程序名为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