1. 程式人生 > >Linux網路程式設計之高階併發伺服器(轉)

Linux網路程式設計之高階併發伺服器(轉)

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複用的迴圈伺服器是對這三種高階併發伺服器的改進。