什么是多协议服务例程呢?
简单说,一个多协议服务例程由一个单执行线程构成,这个线程既可以在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套接字版)>>
阅读(605) | 评论(0) | 转发(0) |