Tuesday, January 25, 2011

Linux下的網絡編程8

轉:http://cyuyanbiancheng.blog.hexun.com.tw/57207300_d.html
Linux下的c語言網絡編程--服務器模型

9. 服務器模型
  在網絡程序裏面,一般的來說都是許多Client對應一個Server.為了處理 Client的請求對Server的程序就提出了特殊的要求.我們學習一下目前最常用的Server 模型.
    循環Server:循環服務器在同一個時刻只可以響應一個Client的請求.
    並發Server:並發服務器在同一個時刻可以響應多個Client的請求.

9.1 循環Server:UDP服務器
  UDP循環Server的實現非常簡單:UDP Server每次從套接字上讀取一個Client的請求,處理然後將結果返回給Client. 可以用下面的算法來實現

    socket(...);
    bind(...);
    while(1) {
      recvfrom(...);
      process(...);
      sendto(...);
    }

因為UDP是非面向連接的,沒有一個Client可以老是占住Server只要處理過程不是死循 , Server對於每一個Client的請求總是能夠滿足.

9.2 
循環Server: TCP Server
  TCP循環Server的實現也不難:TCP Server接受一個Client的連接,然後處理,完成了這個Client的所有請求後,斷開連接. 算法如下

    socket(...);
    bind(...);
    listen(...);
    while(1) {
      accept(...);
      while(1) {
        read(...);
        process(...);
        write(...);
      }


      close(...);
     
TCP循環Server一次只能處理一個Client的請求.只有在這個Client的所有請求都滿足後,  Server才可以繼續後面的請求.這樣如果有一個Client占住Server不放時,其它的Client 都不能工作了.因此,TCP Server一般很少用循環Server模型的.

9.3 
並發Server: TCP服務器
  為了彌補循環TCP Server的缺陷,人們又想出了並發Server的模型並發Server的思想是 每一個Client的請求並不由Server直接處理,而是Server創建一個 子進程來處理. 算法如下

    socket(...);
    bind(...);
    listen(...);
    while(1) {
      accept(...);
      if(fork(..)==0) {
        while(1) {
          read(...);
          process(...);
          write(...);
        }
        close(...);
        exit(...);
      }
      close(...);
    }

TCP
並發Server可以解決TCP循環Server被Client獨占Server的情況不過也同時帶來了一 個不小的問題.為了響應Client的請求,Server要創建子進程來處理而創建子進程是一 種非常消耗資源的操作.

9.4 
並發Server: 多路復用I/O
  為了解決創建子進程帶來的系統資源消耗,人們又想出了多路復用I/O模型. 首先介紹一個函數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_SETfd加入到fdset
  • FD_CLR: fdfdset裏面清除
  • 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>;maxfd) 
      maxfd=readfd;

  while(1) {
  /* 將所有的文件描述符加入 */
    FD_ZERO(&my_readfd);
    for(i=0;i<n;i++)
      FD_SET(readfd,*my_readfd);
 
    /* 進程阻塞 */
    select(maxfd+1,& my_readfd,NULL,NULL,NULL);
    /* 有東西可以讀了 */
    for(i=0;i<n;i++)
      if(FD_ISSET(readfd,&my_readfd)) {
      /* 原來是我可以讀了 */
        we_read(readfd);
      }
    }
 

使用select後我們的Server程序就變成了


  初始話(socket,bind,listen);
 
  while(1) {
    設置監聽讀寫文件描述符(FD_*); 
    調用select; 
    如果是傾聽套接字就緒,說明一個新的連接請求建立    
      建立連接(accept); 加入到監聽文件描述符中去;
   
    否則說明是一個已經連接過的描述符 
    {
       進行操作(read或者write);
    }
  } 

 多路復用I/O可以解決資源限制的問題.著模型實際上是將UDP循環模型用在了TCP上面這也就帶來了一些問題.如由於Server依次處理Client的請求,所以可能會導致有的Client會等待很久.

9.5 
並發Server: UDP Server 
  人們把並發的概念用於UDP就得到了並發UDP Server模型並發UDP Server模型其實是簡單 .和並發的TCP Server模型一樣是創建一個子進程來處理的 算法和並發的TCP模型一樣..  除非Server在處理Client的請求所用的時間比較長以外,人們實際上很少用這種模型.

9.6 
一個並發TCP Server實例
 
#include <sys/socket.h>;
#include <sys/types.h>;
#include <netinet/in.h>;
#include <string.h>;
#include <errno.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); 
 
 }

你可以用我們前面寫Client程序來調試著程序,或者是用來telnet調試

No comments:

Post a Comment