Linux网络编程之高级并发服务器

本文介绍了三种高级并发服务器模式:统一accept多进程、统一accept多线程及多线程独立accept。并通过具体示例展示了每种模式下服务器端的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:http://blog.youkuaiyun.com/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复用的循环服务器是对这三种高级并发服务器的改进。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值