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复用的循环服务器是对这三种高级并发服务器的改进。


Logo

更多推荐