Chinaunix首页 | 论坛 | 博客
  • 博客访问: 259427
  • 博文数量: 74
  • 博客积分: 1470
  • 博客等级: 上尉
  • 技术积分: 793
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-25 21:01
文章分类

全部博文(74)

文章存档

2011年(1)

2010年(32)

2009年(32)

2008年(9)

我的朋友

分类: LINUX

2010-05-01 21:25:15

什么是多协议服务例程呢?
简单说,一个多协议服务例程由一个单执行线程构成,这个线程既可以在TCP也可以在UDP之上使用异步I/O来进行通信。服务器最初打开两个套接字:一个使用无连接的传输(udp),一个使用面向连接的传输(tcp)。接着,服务器使用异步I/O等待两个套接字之一就绪。如果TCP套接字就绪,就说明客户请求了一个TCP连接,服务器就使用accept获得新的连接,并在这个新的连接上和客户通信。如果UDP套接字就绪,就说明客户以UDP数据报的形式发来一个请求,服务器就用recvfrom读取这个请求,并记录此发送者的端点地址,当服务器计算出响应后,服务器就用sendto将响应发回给客户。
当然,也可以针对不同的协议,提供多个服务器例程,一个用来处理来自TCP的请求,一个用来处理来自UDP的请求。但相比之下,多协议服务例程有至少以下几点好处:
1. 减少重复的代码,每种协议使用一个服务器例程主要的缺点就是重复,这样就使软件管理和排错变的冗长乏味。而多协议服务例程,很显然可以使代码维护起来更为方便。
2. 为一个协议运行一个服务例程的另一个缺点来自于对资源的使用,多个服务例程不必要的消耗了进程表的许多表相以及其他系统资源。

下面给出一个例子,它由一个线程构成,这个线程可以同时为udp和tcp提供DAYTIME服务。
Makefile
----------------

OBJ=passivesock.c passiveTCP.c passiveUDP.c errexit.o daytimed.o
daytime:$(OBJ)
    gcc -g -o $@ $(OBJ)

clean:
    -rm -f *.o daytime



passiveTCP.c
---------------

/* passiveTCP.c - passiveTCP */

int passivesock(const char *service, const char *transport,
        int qlen);

/*------------------------------------------------------------------------
 * passiveTCP - create a passive socket for use in a TCP server
 *------------------------------------------------------------------------
 */

int
passiveTCP(const char *service, int qlen)
/*
 * Arguments:
 * service - service associated with the desired port
 * qlen - maximum server request queue length
 */

{
    return passivesock(service, "tcp", qlen);
}


passiveUDP.c
-------------------

/* passiveUDP.c - passiveUDP */

int passivesock(const char *service, const char *transport,
        int qlen);

/*------------------------------------------------------------------------
 * passiveUDP - create a passive socket for use in a UDP server
 *------------------------------------------------------------------------
 */

int
passiveUDP(const char *service)
/*
 * Arguments:
 * service - service associated with the desired port
 */

{
    return passivesock(service, "udp", 0);
}


passivesock.c
---------------------

/* passivesock.c - passivesock */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>

int errexit(const char *format, ...);

unsigned short portbase = 0; /* port base, for non-root servers */

/*------------------------------------------------------------------------
 * passivesock - allocate & bind a server socket using TCP or UDP
 *------------------------------------------------------------------------
 */

int
passivesock(const char *service, const char *transport, int qlen)
/*
 * Arguments:
 * service - service associated with the desired port
 * transport - transport protocol to use ("tcp" or "udp")
 * qlen - maximum server request queue length
 */

{
    struct servent *pse; /* pointer to service information entry */
    struct protoent *ppe; /* pointer to protocol information entry*/
    struct sockaddr_in sin; /* an Internet endpoint address */
    int s, type; /* socket descriptor and socket type */

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;

    /* Map service name to port number */
    if ( pse = getservbyname(service, transport) )
        sin.sin_port = htons(ntohs((unsigned short)pse->s_port)
            + portbase);
    else if ((sin.sin_port=htons((unsigned short)atoi(service))) == 0)
        errexit("can't get \"%s\" service entry\n", service);

    /* Map protocol name to protocol number */
    if ( (ppe = getprotobyname(transport)) == 0)
        errexit("can't get \"%s\" protocol entry\n", transport);

    /* Use protocol to choose a socket type */
    if (strcmp(transport, "udp") == 0)
        type = SOCK_DGRAM;
    else
        type = SOCK_STREAM;

    /* Allocate a socket */
    s = socket(PF_INET, type, ppe->p_proto);
    if (s < 0)
        errexit("can't create socket: %s\n", strerror(errno));

    /* Bind the socket */
    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
        errexit("can't bind to %s port: %s\n", service,
            strerror(errno));
    if (type == SOCK_STREAM && listen(s, qlen) < 0)
        errexit("can't listen on %s port: %s\n", service,
            strerror(errno));
    return s;
}


errexit.c
---------------

/* errexit.c - errexit */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

/*------------------------------------------------------------------------
 * errexit - print an error message and exit
 *------------------------------------------------------------------------
 */

int
errexit(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(1);
}


daytimed.c
------------------

/* daytimed.c - main */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>


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 descriptors */

    switch (argc) {
    case 1:
        break;
    case 2:
        service = argv[1];
        break;
    default:
        errexit("usage: daytimed [port]\n");
    }

    tsock = passiveTCP(service, QLEN);
    usock = passiveUDP(service);
    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 - 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));
}


在有root权限的shell中运行(因为要打开的是13端口)
# ./daytime
然后在另外一个shell窗口中
$ netstat -anp | grep 0.0.0.0:13
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN      -              
tcp        0      0 0.0.0.0:13              0.0.0.0:*               LISTEN      -              
udp        0      0 0.0.0.0:137             0.0.0.0:*                           -              
udp        0      0 0.0.0.0:138             0.0.0.0:*                           -              
udp        0      0 0.0.0.0:13              0.0.0.0:*                           -
可以看到upd和tcp的13端口都打开了。如果你想验证DAYTIME服务的正确性,可以很容易的写一个客户端程序试试。

我们的多协议DAYTIME服务器例子使用了循环的方法来处理请求。之所以采用这种循环的方案,其理由是:对每个请求,DAYTIME服务器所执行的计算很少,大部分时间都用在同客户端的通信上了。若每个请求要求更多的计算量,那循环的实现方案就不够了,这用情况下可将这用多协议设计扩展为并发的处理请求。关于并发的处理请求,读者可以参考后面的关于处理并发请求的文章。

reference:
<<用TCP/IP进行网际互联/客户-服务器编程与应用(linux/posix套接字版)>>

阅读(725) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~