服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器,按处理方式来分有循环服务器和并发服务器。
在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求,对服务端的程序就提出了特殊的要求。
目前最常用的服务器模型有:
1、循环服务器模型
1.1 UDP循环服务器的实现方法:
UDP循环服务器每次从套接字上读取一个客户端的请求->处理->然后将结果返回给客户机。
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端。只要处理过程不是死循环,服务器对于每一个客户机的请求总是能够满足。
UDP循环服务器模型为:
socket(); bind(); while(1) { recvfrom(); process(); sendto(); }
1.2 TCP循环服务器的实现方法:
TCP循环服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接。TCP循环服务器一次只能处理一个客户端的请求,只有在这个客户的所有请求满足后,服务器才可以继续后面的请求。如果有一个客户端占住服务器不放时,其它的客户机都不能工作了,因此,TCP服务器一般很少用循环服务器模型的。
TCP循环服务器模型为:
socket(); bind(); listen(); while(1){ accept(); process(); close(); }
2、三种并发服务器实现方法
一个好的服务器,一般都是并发服务器。并发服务器设计技术一般有:多进程服务器、多线程服务器、I/O复用服务器等。
2.1 多进程并发服务器
在Linux环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求,父进程继续等待其它客户的请求。这种方法的优点是当客户有请求时,服务器能及时处理客户 ,特别是在客户服务器交互系统中。对于一个 TCP服务器,客户与服务器的连接可能并不马上关闭,可能会等到客户提交某些数据后再关闭,这段时间服务器端的进程会阻塞 。所以这时操作系统可能调度其它客户服务进程,比起循环服务器大大提高了服务性能。
TCP多进程并发服务器
TCP并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。
socket(); bind(); listen(); while(1){ accept(); if(fork() == 0) { process(); close(); exit(); } close(); }
使用示例:
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/types.h> #include <arpa/inet.h> #define MY_PORT 8888 int main(int argc ,char **argv) { int listen_fd,accept_fd; struct sockaddr_in client_addr; int n; if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("Socket Error:%s\n\a",strerror(errno)); exit(1); } bzero(&client_addr,sizeof(struct sockaddr_in)); client_addr.sin_family=AF_INET; client_addr.sin_port=htons(MY_PORT); client_addr.sin_addr.s_addr=htonl(INADDR_ANY); n=1; /* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间 */ setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int)); if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0) { printf("Bind Error:%s\n\a",strerror(errno)); exit(1); } listen(listen_fd,5); while(1) { accept_fd=accept(listen_fd,NULL,NULL); if((accept_fd<0)&&(errno==EINTR)) continue; else if(accept_fd<0) { printf("Accept Error:%s\n\a",strerror(errno)); continue; } if((n=fork())==0) { /* 子进程处理客户端的连接 */ char buffer[1024]; close(listen_fd); n=read(accept_fd,buffer,1024); write(accept_fd,buffer,n); close(accept_fd); exit(0); } else if(n<0) printf("Fork Error:%s\n\a",strerror(errno)); close(accept_fd); } }
2.2 多线程服务器
多线程服务器是对多进程的服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息。这种机制又带来了同步问题。以下是多线程服务器模板:
socket(); bind(); listen(); while(1){ accept(); if((pthread_creat()) != -1) { process(); close(); exit(); } close(); }
2.3 I/O复用服务器
I/O复用技术是为了解决进程或线程阻塞到某个I/O系统调用而出现的技术,使进程不阻塞于某个特定的I/ O系统调用。它也可用于并发服务器的设计,常用函数select 或poll来实现。
select函数原型:
int select(int nfds,fd_set *readfds,fd_set *writefds, fd_set *except fds,struct timeval *timeout); void FD_SET(int fd,fd_set *fdset); void FD_CLR(int fd,fd_set *fdset); void FD_ZERO(fd_set *fdset); int FD_ISSET(int fd,fd_set *fdset);
一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足。比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读 (通信的对方还没有 发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读。如果我们不希望阻塞,我们的一个选择是用select系统调用。只要我们设置好select的各个参数,那么当文件可以读写的时候,select回"通知"我们说可以进行读写了。
readfds 所有要读的文件文件描述符的集合。
writefds 所有要的写文件文件描述符的集合。
exceptfds 其他的服要向我们通知的文件描述符。
timeout 超时设置。
nfds 所有监控的文件描述符中最大的那一个加1。
在调用select时进程会一直阻塞直到以下的一种情况发生: 1)有文件可以读;2)有文件可以写;3)超时所设置的时间到。
为了设置文件描述符我们要使用几个宏:
FD_SET 将fd加入到fdset。
FD_CLR 将fd从fdset里面清除。
FD_ZERO 从fdset中清除所有的文件描述符。
FD_ISSET 判断fd是否在fdset集合中。
使用select的示例:
int use_select(int *readfd,int n) { fd_set my_readfd; int maxfd; int i; maxfd=readfd[0]; for(i=1;i<n;i++) { if(readfd[i]>maxfd) maxfd=readfd[i]; } while(1) { /* 将所有的文件描述符加入 */ FD_ZERO(&my_readfd); for(i=0;i<n;i++) FD_SET(readfd[i],*my_readfd); /* 进程阻塞 */ select(maxfd+1,& my_readfd,NULL,NULL,NULL); /* 有东西可以读了 */ for(i=0;i<n;i++) { if(FD_ISSET(readfd[i],&my_readfd)) { /* 原来是我可以读了 */ we_read(readfd[i]); } } } }
使用select后我们的服务器程序就变成:
socket(); bind(); listen(); while(1) { 设置监听读写文件描述符(FD_*); 调用select; 如果是监听套接字就绪,说明一个新的连接请求建立 { 建立连接(accept); 加入到监听文件描述符中去; } 否则说明是一个已经连接过的描述符 { 进行操作(read或者write); } }
原文:http://www.cnblogs.com/tgycoder/p/5252066.html