Linux網絡編程之高級併發服務器

原文:http://blog.csdn.net/chenjin_zhong/article/details/7269717    點擊打開鏈接

1. 介紹

在上一節,我們介紹了Linux簡單的併發服務器,通過在服務器端建立多個子進程,來接收客戶端的請求,實現併發處理,但這種方式明顯有缺陷,服務器並不知道客戶端請求的數量,所以事先建立的進程數不好確定。所以,這裏介紹三種高級併發服務器模式。第一種是服務器端統一accept,接收客戶端的到來,然後爲每個客戶端分配一個進程去處理. 第二種是統一accept接收請求,然後爲每個客戶端分配一個線程去處理。第三種建立多個線程去處理客戶端請求,每個線程獨自監聽客戶端的請求。顯然,第一種方案解決了簡單服務器的併發問題。第二種方案其實是對第一種方案的改進,因爲線程切換的開銷明顯要小於進程切換的開銷。第三種方案就是原來用進程去處理每個請求,現在換成用線程去處理,個人認爲改進不是很大.

2. 高級併發服務器算法流程

(1) 統一accept,多進程

  socket(...);
  bind(...);
  listen(...);
  while(1){
    accept(...);
    fork(...);//子進程
  }
  close(...);//關閉服務器套接字

子進程:

 recv(...);
 process(...);
 send(...);
 close(...);//關閉客戶端

(2) 統一accept,多線程

  socket(...);
  bind(...);
  listen(...);
  while(1){
    accept(...);
    pthread_create(....);  
  }
  close(...);//關閉服務器

線程1:

recv(....);
process(....);
send(...);
close(...);//關閉客戶端

(3) accept放入每個線程

 socket(...);
 bind(...);
 listen(...);
 pthread_create(...);
 pthread_join(...);//等待線程結束
 close(...);//關閉服務器

線程1:

Mutex_lock//互斥鎖
accept(...);
Mutex_unlock(...);
recv(...);
process(...);
send(...);
close(...);//客戶端

3. 相關例子(TCP服務器

(1) 統一accept多進程

服務器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
 高級併發服務器
 TCP統一accept
 當有客戶端到來時,爲每個客戶端建立進程,然後每個進程處理客戶端的請求,動態的建立進程
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2

static void handle(int sc){//處理客戶端的請求
  char buffer[BUFFERSIZE];
  time_t now;
  int size;
  memset(buffer,0,BUFFERSIZE);
  size=recv(sc,buffer,BUFFERSIZE,0);
  if(size>0&&!strncmp(buffer,"TIME",4)){//時間服務器,當客戶端請求時間就把時間發送給客戶端
      memset(buffer,0,BUFFERSIZE);
      now=time(NULL);
      sprintf(buffer,"%24s\r\n",ctime(&now));
     send(sc,buffer,strlen(buffer),0);
  }
  close(sc);
}
int main(int argc,char*argv[]){
  int ret;
  int s;
  int sc;//用於服務器與客戶端進行數據傳輸的套接字
  struct sockaddr_in server_addr;
  struct sockaddr_in client_addr;
  int len;
  len=sizeof(client_addr);
  //建立流式套接字
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  //將地址結構綁定到套接字描述符上去
  memset(&server_addr,0,sizeof(server_addr));
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(PORT);
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  ret=bind(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
  if(ret==-1){
    perror("bind error");
    return -1;
  }
  ret=listen(s,BACKLOG);
  if(ret<0){
    perror("listen error");
    return -1;
  }

  while(1){
    sc=accept(s,(struct sockaddr*)&client_addr,&len);
    if(sc<0){
      continue;
    }
    if(fork()==0){//子進程
      handle(sc);
      close(s);//子進程關閉用於監聽的套接字
    }else{
      close(sc);//父進程關閉客戶端套接字
    }
  }
}
客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
  int s;
  int ret;
  int size;
  struct sockaddr_in server_addr;
  char buffer[BUFFERSIZE];
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  bzero(&server_addr,sizeof(server_addr));
  //將地址結構綁定到套接字
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(PORT);
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  //連接服務器
  ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
  if(ret==-1){
    perror("connect error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  strcpy(buffer,"TIME");
  size=send(s,buffer,strlen(buffer),0);
  if(size<0){
    perror("send error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  size=recv(s,buffer,BUFFERSIZE,0);
  if(size<0){
    perror("recv error");
    return;
  }

  printf("%s",buffer);
  close(s);
  return 0;
}

(2) 統一accept多線程

服務器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
  TCP併發服務器,採用多線程,每次客戶端發送請求,主線程建立一個子線程,用於處理客戶端的請求
  線程具有速度快,佔用資源少,數據可以共享等優點 統一accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2

static void handle(void* sc1){
  int sc;
  time_t now;
  char buffer[BUFFERSIZE]; 
  int size;
  sc=*((int*)sc1);//轉換成int指針,然後取值,sc1本身就是一個指針
  memset(buffer,0,BUFFERSIZE);
  size=recv(sc,buffer,BUFFERSIZE,0);
  if(size>0&&!strncmp(buffer,"TIME",4)){//請求服務器的時間 
     memset(buffer,0,BUFFERSIZE);//清0
     now=time(NULL);
     sprintf(buffer,"%24s\r\n",ctime(&now));
     send(sc,buffer,strlen(buffer),0);//向客戶端發送數據
  }

  close(sc);//關閉客戶端
}
int main(int argc,char*argv[]){
  int ret;
  int s;
  int sc;
  int len;
  pthread_t thread1;//定義線程名
  struct sockaddr_in server_addr,client_addr;
  len=sizeof(client_addr);
  //建立流式套接字
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  //將服務器端的地址結構綁定到套接字描述符
  server_addr.sin_family=AF_INET;
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  server_addr.sin_port=htons(PORT);
  ret=bind(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
  if(ret<0){
    perror("bind error");
    return -1;
  }
  //監聽
  ret=listen(s,BACKLOG);
  if(ret<0){
    perror("listen error");
    return -1;
  }

  //接收客戶端的請求
  for(;;){
    sc=accept(s,(struct sockaddr*)&client_addr,&len);
    if(sc<0){
      continue;
    } else {
      pthread_create(&thread1,NULL,handle,(void*)&sc);//建立線程,讓線程去處理,最後一個字段是傳遞給線程處理函數handle的參數
    }
  }

  close(s);
}

客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
  int s;
  int ret;
  int size;
  struct sockaddr_in server_addr;
  char buffer[BUFFERSIZE];
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  bzero(&server_addr,sizeof(server_addr));
  //將地址結構綁定到套接字
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(PORT);
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  //連接服務器
  ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
  if(ret==-1){
    perror("connect error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  strcpy(buffer,"TIME");
  size=send(s,buffer,strlen(buffer),0);
  if(size<0){
    perror("send error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  size=recv(s,buffer,BUFFERSIZE,0);
  if(size<0){
    perror("recv error");
    return;
  }

  printf("%s",buffer);
  close(s);
  return 0;
}

(3) 單獨線程accept

服務器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>
/**
  多線程TCP併發服務器
  主線程創建多個線程,然後每個線程獨立的accept和進行數據的發送與接收
  多線程,獨立accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2
#define CLIENTNUM 3
static void *handle(void* s1){
  int s;
  int len;
  int sc;
  pthread_mutex_t alock=PTHREAD_MUTEX_INITIALIZER;
  char buffer[BUFFERSIZE];
  int size;
  struct sockaddr_in client_addr;
  s=*((int*)s1);//得到服務器端的套接字描述符
  //等待客戶端連接
  len=sizeof(client_addr);
  for(;;){//不停的循環等待客戶端的連接
    time_t now;
    //進入互斥區,每次一個線程處理客戶端
    pthread_mutex_lock(&alock); 
    sc=accept(s,(struct sockaddr*)&client_addr,&len);
    pthread_mutex_unlock(&alock);
    memset(buffer,0,BUFFERSIZE);
    size=recv(sc,buffer,BUFFERSIZE,0);
    if(size>0&&!strncmp(buffer,"TIME",4)){
      memset(buffer,0,BUFFERSIZE);
      now=time(NULL);
      sprintf(buffer,"%24s\r\n",ctime(&now));
      send(sc,buffer,strlen(buffer),0);
    }
    close(sc);//關閉客戶端
  } 
}
int main(int argc,char*argv[]){
   int ret;
   int s;
   int len;
   int i;
   pthread_t thread[CLIENTNUM];
   struct sockaddr_in server_addr;
   //建立流式套接字
   s=socket(AF_INET,SOCK_STREAM,0);
   if(s<0){
      perror("socket error");
      return -1;
   }
   //將地址結構綁定到套接字上
   server_addr.sin_family=AF_INET;
   server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
   server_addr.sin_port=htons(PORT);
   ret=bind(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
   if(ret==-1){
      perror("bind error");
      return -1;
   }
   //監聽
   ret=listen(s,BACKLOG);
   if(ret==-1){
      perror("listen error");
      return -1;
   }

   //建立3個線程,每個線程獨立的accept
   for(i=0;i<CLIENTNUM;i++){
      pthread_create(&thread[i],NULL,handle,(void*)&s);//線程的處理函數爲handle,傳遞的參數爲套接字描述符s    
   }
   //while(1);
   //等待線程結束
   for(i=0;i<CLIENTNUM;i++){
     pthread_join(thread[i],NULL);
   }
   //關閉套接字
   close(s);

   return 0;
}
客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
  int s;
  int ret;
  int size;
  struct sockaddr_in server_addr;
  char buffer[BUFFERSIZE];
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  bzero(&server_addr,sizeof(server_addr));
  //將地址結構綁定到套接字
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(PORT);
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  //連接服務器
  ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
  if(ret==-1){
    perror("connect error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  strcpy(buffer,"TIME");
  size=send(s,buffer,strlen(buffer),0);
  if(size<0){
    perror("send error");
    return -1;
  }
  memset(buffer,0,BUFFERSIZE);
  size=recv(s,buffer,BUFFERSIZE,0);
  if(size<0){
    perror("recv error");
    return;
  }

  printf("%s",buffer);
  close(s);
  return 0;
}


總結:

統一accept,多進程服務器是對簡單併發服務器的改進,而由於進程的切換開銷比較大,所以又有了統一accept,多線程的併發服務器。而單獨線程的accept是完全用線程來處理請求。這些都是TCP服務器,由於UDP是突發的數據流,沒有三次握手,所以服務器不能檢測到客戶端什麼時候發送數據。以上三種高級併發服務器仍然存在着性能問題,下一節介紹的I/O複用的循環服務器是對這三種高級併發服務器的改進。


發佈了28 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章