首页 > 系统服务 > 详细

八、Linux下的网络服务器模型

时间:2016-03-07 22:28:22      阅读:381      评论:0      收藏:0      [点我收藏+]

服务器设计技术有很多,按使用的协议来分有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);
        }
    
}    

八、Linux下的网络服务器模型

原文:http://www.cnblogs.com/tgycoder/p/5252066.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!