这次我们讨论多协议服务器

设计初衷:

每种协议使用一个服务器会使代码重复,另外,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));
}