网络服务器的类型分析和比较匡文亚2010032020015 集成电路设计与集成系统邮箱:1070817439@摘要:本文介绍了服务器的概念进行详细说明,然后说明了它在现代社会按不同类别的分类,并以循环服务器和并发服务器为例,详细介绍了循环服务器和并发服务器,以及其工作原理和它们的应用范围,最后将它们进行比较。
比较从它们的各自的特点,服务器算法,分类,结构进程以及它们的应用场合入手一层一层的分析,最后以一个单进程并发服务器实例为例,说明了它们编程方式和内容以及在现实的应用。
一·网络服务器的类型分析服务器是一种高性能计算机,作为网络的节点,存储、处理网络上80%的数据、信息,因此也被称为网络的灵魂。
做一个形象的比喻:服务器就像是邮局的交换机,而微机、笔记本、PDA、手机等固定或移动的网络终端,就如散落在家庭、各种办公场所、公共场所等处的电话机。
我们与外界日常的生活、工作中的电话交流、沟通,必须经过交换机,才能到达目标电话;同样如此,网络终端设备如家庭、企业中的微机上网,获取资讯,与外界沟通、娱乐等,也必须经过服务器,因此也可以说是服务器在“组织”和“领导”这些设备。
服务器发展到今天,适应各种不同功能、不同环境的服务器不断地出现,分类标准也多种多样。
按应用层次可以分为入门级服务器,工作组级服务器,部门级服务器,企业级服务器;按服务器的处理器架构划分把服务器分为CISC架构服务器、RISC架构服务器和VLIW架构服务器三种;按服务器按用途划分为通用型服务器和专用型服务器两类;按服务器的机箱结构来划分,可以把服务器划分为“台式服务器”、“机架式服务器”、“机柜式服务器”和“刀片式服务器”四类。
本文则讲诉最基本的分类—循环服务器和并发服务器。
(一)循环服务器的工作原理及应用范围●循环服务器又分为循环面向连接和无连接服务器。
循环服务器是最简单的,客户按照顺序等待。
是否能够满足要求取决于所需的反应时间●观测响应时间:客户发送请求到服务器相应之间的全部时延。
●请求处理时间:服务器处理单个孤立的请求所花费的时间。
●循环服务器一次处理一个请求。
如果N代表请求的平均长度,观测响应时间大约是N/2+1服务请求处理时间●如果一个服务器设计处理K个客户,每个客户每秒发送R个请求,服务器请求处理时间必须小于美请求1/KR秒。
否则请求队列将溢出。
这是设计者必须考虑并发实现(三)并发服务器的工作原理及应用范围不同于顺序服务器,并发服务器就要能在一个时间为多个客户端提供服务。
例如,一个聊天服务器可能服务一个特定的客户端数小时──在停止为这个客户端服务之前服务器不能等待,除非是在等待一下个客户端到来之前的间隙才能等待。
我们将提供服务从守护进程移至它自己的服务进程。
然而,因为每个子进程都继承所有打开的文件(套接字被像文件一样处理),新进程不仅继承“accept()返回的句柄,”那是指调用accept返回的套接字;新进程也继承顶级套接字,这是顶级进程一开始打开的套接字。
然而,服务进程不需要这个套接字,应该立即关闭(close)它。
同样的,守护进程不再需要accept()返回的套接字,不仅应该,还必须关闭(close)它──否则,那迟早会耗尽可用的文件描述符。
在服务进程完成服务之后,它将关闭accept()返回的套接字。
它不会返回到accept,而是退出进程。
在UNIX®上,一个进程并不真正的退出,而是返回至父进程。
典型情况中,父进程等待(wait)子进程,并取得一个返回值。
但是,我们的守护进程不能简单的停止或等待,那有违建立其它进程的整个目的。
但是如果从不使用wait,它的子进程可能会成为僵尸──不再有功用可仍然徘徊着。
出于那样的原因,守护进程需要在初始化守护进程阶段设置信号处理程序。
至少要处理信号SIGCHLD,这样守护进程可以从系统清除僵尸返回值并释放僵尸占用的系统资源。
这是现在我们的流程图包含一个进程信号框的原因,它不与任何其它框相连接。
顺便说一句,许多服务器程序也处理SIGHUP,作为超级用户发出的要求重读配置文件的信号。
这允许我们不必终止或重启服务器程序就改变设置。
二·网络服务器的类型比较以及实例介绍1 循环服务器和并发服务器都要分为无连接和面向连接的。
2 循环服务器:每次处理一个客户请求, 处理完一个客户请求后才处理下一个客户请求.并发性服务器:同时处理多个客户请求.3循环服务器的特点:每次处理时间都很少;服务器实现简单并发性服务器的特点:开销少;共享存储器;可以监控;增加了编程的复杂性;必须使用同步机制协调线程对全局变量和一些库程序的访问;必须弄清一些可能影响整个进程的系统函数。
4(1)循环无连接服务器算法创建套接字并将其绑定到所提供服务的熟知端口上;重复读取来自客户的请求,构造响应,按照应用协议向客户发回响应。
(2)循环面向连接服务器算法·创建套接字并将其绑定到它所提供服务的熟知端口上;将该端口设置为被动模式,使其准备为服务器所用;从该套接字上接收下一个连接请求,获得该连接的新的套接字;重复地读取来自客户的请求,构造响应,按照应用协议向客户发回响应;当某个特定客户完成交互时,关闭连接,并返回步骤3以接受新的连接(3)并发无连接服务器算法a)主1、创建套接字并将其绑定到所提供服务的熟知地址上。
让该套接字保持为未连接的b)主2、反复调用recvfrom接收来自客户的下一个请求,创建一个新的从线程来处理响应c)从1、从来自主进程的特定请求以及到该套接字的访问开始d)从2、根据应用协议构造应答,并用sendto将该应答发回给客户e)从3、退出(即:从线程处理完一个请求后就终止)(4)并发面向连接服务器算法a)主1、创建套接字并将其绑定到所提供服务的熟知地址上。
让该套接字保持为面向连接b)主2、将该端口设置为被动模式c)主3、反复调用accept以便接收来自客户的下一个连接请求,并创建新的从线程或者进程来处理响应d)从1、由主线程传递来的连接请求开始e)从2、用该连接与客户进行交互;读取请求并发回响应f)从3、关闭连接并退出5循环服务器的进程结构循环的无连接的服务器进程结构————只需要一个执行线程循环的面向连接的服务器进程结构————使用一个单执行线程;使用两个套接字:一个套接字处理请求,另外一个套接字处理和客户的通信(临时的)。
6适用场合(1)循环的和并发的:⏹如果循环方案产生的响应时间对应用来说足够,就可以使用循环;否则需要并发(2)真正的和表面上的并发性:⏹线程或切换环境的开销大,服务器需要在多个连接之间共享或者交换数据,用单线程;⏹使用线程开销不大或者要得到最大并发性,使用多进程(3)面向连接的和无连接的:⏹应用协议处理了可靠性问题,或者应用在局域网环境内,使用无连接的传输。
7应用实例*单进程并发服务器实例。
该程序采用单进程并发服务器算法实现的。
*/#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/time.h>#include <stdlib.h>#define PORT 1234 //服务器端口#define BACKLOG 5 //listen队列中等待的连接数#define MAXDATASIZE 1024 //缓冲区大小typedef struct _CLIENT{ //客户端结构体int fd; //客户端socket描述符char* name;struct sockaddr_in addr; //客户端地址信息结构体char* data;} CLIENT;void process_cli(CLIENT *client, char* recvbuf, int len); //客户请求处理函数void savedata(char* recvbuf, int len, char* data);main(){int i, maxi, maxfd,sockfd;int nready;ssize_t n;fd_set rset, allset; //select所需的文件描述符集合int listenfd, connectfd; //socket文件描述符struct sockaddr_in server; //服务器地址信息结构体CLIENT client[FD_SETSIZE]; //FD_SETSIZE为select函数支持的最大描述符个数char recvbuf[MAXDATASIZE]; //缓冲区int sin_size; //地址信息结构体大小if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { //调用socket创建用于监听客户端的socketperror("Creating socket failed.");exit(1);}int opt = SO_REUSEADDR;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //设置so cket属性bzero(&server,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(PORT);server.sin_addr.s_addr = htonl (INADDR_ANY);if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { //调用bind绑定地址perror("Bind error.");exit(1);}if(listen(listenfd,BACKLOG) == -1){ //调用listen开始监听perror("listen() error\n");exit(1);}sin_size=sizeof(struct sockaddr_in);//初始化selectmaxfd = listenfd;maxi = -1;for (i = 0; i < FD_SETSIZE; i++) {client[i].fd = -1;}FD_ZERO(&allset); //清空FD_SET(listenfd, &allset); //将监听socket加入select检测的描述符集合while(1){struct sockaddr_in addr;rset = allset;nready = select(maxfd+1, &rset, NULL, NULL, NULL); //调用selectprintf("select saw rset actions and the readfset num is %d. \n",nready );if (FD_ISSET(listenfd, &rset)) { //检测是否有新客户端请求printf("accept a connection.\n");//调用accept,返回服务器与客户端连接的socket描述符if ((connectfd = accept(listenfd,(struct sockaddr *)&addr,(socklen_t *)&sin_siz e))==-1) {perror("accept() error\n");continue;}//将新客户端的加入数组for (i = 0; i < FD_SETSIZE; i++){if (client[i].fd < 0) {client[i].fd = connectfd; //保存客户端描述符client[i].name = new char[MAXDATASIZE];client[i].addr = addr;client[i].data = new char[MAXDATASIZE];client[i].name[0] = '\0';client[i].data[0] = '\0';printf("You got a connection from %s. ",inet_ntoa(client[i].addr.sin_addr) ); break;}}printf("add new connect fd.\n");if (i == FD_SETSIZE)printf("too many clients\n");FD_SET(connectfd, &allset); //将新socket连接放入select监听集合if (connectfd > maxfd)maxfd = connectfd; //确认maxfd是最大描述符if (i > maxi) //数组最大元素值maxi = i;if (--nready <= 0)continue; //如果没有新客户端连接,继续循环}for (i = 0; i <= maxi; i++) {if ( (sockfd = client[i].fd) < 0) //如果客户端描述符小于0,则没有客户端连接,检测下一个continue;if (FD_ISSET(sockfd, &rset)) { //检测此客户端socket是否有数据printf("recv occured for connect fd[%d].\n",i);if ( (n = recv(sockfd, recvbuf, MAXDATASIZE,0)) == 0) { //从客户端socket读数据,等于0表示网络中断close(sockfd); //关闭socket连接printf("Client( %s ) closed connection. User's data: %s\n",client[i].name,clien t[i].data);FD_CLR(sockfd, &allset); //从监听集合中删除此socket连接client[i].fd = -1; //数组元素设初始值,表示没客户端连接delete client[i].name;delete client[i].data;} elseprocess_cli(&client[i], recvbuf, n); //接收到客户数据,开始处理if (--nready <= 0)break; //如果没有新客户端有数据,跳出for循环回到while循环}}}close(listenfd); //关闭服务器监听socket}void process_cli(CLIENT *client, char* recvbuf, int len){char sendbuf[MAXDATASIZE];recvbuf[len-1] = '\0';if (strlen(client->name) == 0) {memcpy(client->name,recvbuf, len);printf("Client's name is %s.\n",client->name);return;}printf("Received client( %s ) message: %s\n",client->name, recvbuf);savedata(recvbuf,len, client->data);for (int i1 = 0; i1 < len - 1; i1++) {sendbuf[i1] = recvbuf[len - i1 -2];}sendbuf[len - 1] = '\0';send(client->fd,sendbuf,strlen(sendbuf),0);}void savedata(char* recvbuf, int len, char* data){int start = strlen(data);for (int i = 0; i < len; i++) {data[start + i] = recvbuf[i];}参考文献:[1]计算机与网络/高殿武.—北京:机械工业出版社,2010.6[2]Netscape FastTrack服务器入门到精通,[美]Robert P.Lipschutz John Garris 著,邱仲潘译[3]/Linux/2011-02/32030.htm[4]网络编程技术/【美】Anthony jones Jim Ohlund著京京工作室译【5】C程序设计/谭浩强著.—3版. —北京:清华大学出版社,2005(2007重印)。