该节主要展示一个简单的[日期时间]服务器的程序示例.
§1.3 一个简单的日期时间服务器程序代码
这个服务器程序可以为上一节的客户端提供服务。
1 #include "unp.h"
2 #include <time.h>
3 int
4 main(int argc, char **argv)
5 {
6 int listenfd, connfd;
7 struct sockaddr_in servaddr;
8 char buff[MAXLINE];
9 time_t ticks;
10 listenfd = Socket(AF_INET, SOCK_STREAM, 0);
11 bzeros(&servaddr, sizeof(servaddr));
12 servaddr.sin_family = AF_INET;
13 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
14 servaddr.sin_port = htons(13); /* daytime server */
15 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
16 Listen(listenfd, LISTENQ);
17 for ( ; ; ) {
18 connfd = Accept(listenfd, (SA *) NULL, NULL);
19 ticks = time(NULL);
20 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
21 Write(connfd, buff, strlen(buff));
22 Close(connfd);
23 }
24 }
产生一个TCP套接字
10
创建一个TCP套接字,与客户端一样。
绑定服务器的广为人知的端口到该套接字
11–15
服务器通过填充网络套接字结构体中的端口域,以及服务器的网络接口(IP地址),然后进行绑定(调用bind)。在这里指定IP地址为INADDR_ANY,为了让客户端可以连接服务器的任一网络接口(因为服务器可能有多块网卡,也就对应了多个IP地址),也就是说如果服务器有两个IP地址,客户端连接任一IP地址即可。后续章节中介绍了如何限制客户端连接到一个固定的接口上。
转换为监听套接字
16
通过调用listen,一个套接字就转换为监听套接字,这就是说该套接字负责接收来自客户端的连接请求,而并不真正与客户端进行信息传输。
常量LISTENQ 是在头文件unp.h中定义的,它是指能够同时监听客户端连接的个数。不超过LISTENQ的客户端同时连接服务器,它们会在一个队列中排队,来等待服务器的处理。后续章节有更详细的讨论。
接收客户端连接,发送回复
17–21
一般地,服务器进程在调用accept之后进入到睡眠状态,等待着客户端地连接请求. 一个TCP连接通过一个称为三方握手来建立,当三方握手完成之后,accept调用返回。返回值是一个新的套接字描述符(一个整数值connfd),这个新的套接字负责与客户端进行通讯。对于每一个客户端地连接,accept都返回一个新的套接字描述符。整本书使用的无限循环风格是这样的:
for ( ; ; ) {
. . .
}
当前时间和日期通过调用库函数time来获得,并且通过调用ctime进行转换,使得我们能够直观的阅读。如下:
Mon May 26 20:58:40 2003
终止连接
22
客户端调用close之后,服务器关闭连接。这时候引起了一个TCP连接终止序列:一个FIN发送到每一端,同时每一个FIN都要被另一端确认。在后面章节中将会对TCP连接建立时候的三方握手以及TCP连接终止时候的四包交换有更详细的讨论。
以上给出的客户端和服务器版本都是协议相关的(IPv4),在后面将会给出一个协议无关的版本(IPv4和IPv6都适用,主要通过使用getaddrinfo函数)。
最后需要补充的一点是,在以上涉及到Socket API调用的时候,每个函数的第一个字母变成了大写,其意义和小写开头的是一样的,只不过多了一个错误处理罢了。