这次我们讨论多协议服务器
设计初衷:
每种协议使用一个服务器会使代码重复,另外,UDP和TCP都是用相同的基本算法来计算响应,他们都包含执行计算所需要的代码,这样的话管理程序就变得冗长和乏味。
设计:
一个多协议服务器由一个单执行线程构成,这个线程既可以在TCP也可以在UDP上使用异步I/O来处理通信。服务器最初打开2个套接字,一个UDP,一个TCP。接着服务器使用异步I/O等待两个套接字之一就绪。如果TCPsocket就绪,说明是一个TCP连接,使用accept获得新的连接,并在这个链接上与客户通信。如果UDPsocket就绪,说明客户以UDP数据报形式发来一个请求。服务器就用recvfrom读取该请求,并记录此发送者的端点地址。当服务器计算出响应后,就调用sendto将响应发回给客户。
进程结构:
代码:
extern int errno;
int daytime(char buf[]);
int errexit(const char *format, ...);
int passiveTCP(const char *service, int qlen);
int passiveUDP(const char *service);
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define QLEN 32
#define LINELEN 128
/*------------------------------------------------------------------------
* main - Iterative server for DAYTIME service
*------------------------------------------------------------------------
*/
int
main(int argc, char *argv[])
{
char *service = "daytime"; /* service name or port number */
char buf[LINELEN+1]; /* buffer for one line of text */
struct sockaddr_in fsin; /* the request from address */
unsigned int alen; /* from-address length */
int tsock; /* TCP master socket */
int usock; /* UDP socket */
int nfds;
fd_set rfds; /* readable file de script ors */
switch (argc) {
case 1:
break;
case 2:
service = argv[1];
break;
default:
errexit("usage: daytimed [port]/n");
}
tsock = passiveTCP(service, QLEN);
usock = passiveUDP(service);
服务器创建主套接字后,便准备使用select,以便等待其中之一或两者同时I/O准备就绪。首先,他把变量nfds设置为两个套接字中较大的那个,以此作为描述符比特屏蔽码的索引,他还把比特屏蔽码(遍历那个rfds)清零。接着,服务器进入一个无限循环之中。在每次循环中,使用宏FD_SET构造比特屏蔽码,其置1的比特对应于两个主套接字的描述符。接着使用select等待这二者之一的输入激活。
nfds = MAX(tsock, usock) + 1; /* bit number of max fd */
FD_ZERO(&rfds);
while (1) {
FD_SET(tsock, &rfds);
FD_SET(usock, &rfds);
if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0,
(struct timeval *)0) < 0)
errexit("select error: %s/n", strerror(errno));
if (FD_ISSET(tsock, &rfds)) {
int ssock; /* TCP slave socket */
alen = sizeof(fsin);
ssock = accept(tsock, (struct sockaddr *)&fsin,
&alen);
if (ssock < 0)
errexit("accept failed: %s/n",
strerror(errno));
daytime(buf);
(void) write(ssock, buf, strlen(buf));
(void) close(ssock);
}
if (FD_ISSET(usock, &rfds)) {
alen = sizeof(fsin);
if (recvfrom(usock, buf, sizeof(buf), 0,
(struct sockaddr *)&fsin, &alen) < 0)
errexit("recvfrom: %s/n",
strerror(errno));
daytime(buf);
(void) sendto(usock, buf, strlen(buf), 0,
(struct sockaddr *)&fsin, sizeof(fsin));
}
}
}
这段代码很简单,他们都是用单线程来解决并发问题,基本上就是循环的无连接服务器+循环的面向连接的服务器的集合版本。
这里有个小的调整就是daytime函数,因为他要既要适合tcp也要适合udp,由于udp是不用write函数,而是拿到了数据之后直接sendto,而tcp拿到数据之后先要write然后再关闭连接,所以daytime函数的设计更显得一般化。Time函数返回一个时间。
/*------------------------------------------------------------------------
* daytime - fill the given buffer with the time of day
*------------------------------------------------------------------------
*/
int
daytime(char buf[])
{
char *ctime();
time_t now;
(void) time(&now);
sprintf(buf, "%s", ctime(&now));
}