0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

并发服务器的设计与实现

CHANBAEK 来源:嵌入式攻城狮 作者:嵌入式攻城狮 2023-04-25 15:35 次阅读

并发服务器

1.基于多线程的并发服务器

并发服务器支持多个客户端的连接,最大可接入的客户端数取决于内核控制块的个数。 当使用Socket API时,要使服务器能够同时支持多个客户端的连接,必须引入多任务机制,为每个连接创建一个单独的任务来处理连接上的数据,我们将这个设计方式称作并发服务器的设计。

由于多线程并发服务器涉及到子任务的动态创建和销毁,用户需要自己完成对任务堆栈的管理和回收,因此并发服务器的设计流程也相对复杂。

以下并发服务器实例完成的功能为:服务器能够同时支持多个客户端的连接,并能够将每个连接上接收到的小写字母转换成大写字母回显到客户端,其实现步骤如下

参考Socket API编程优化一文,在该文的工程源码基础上进行修改

在工程中创建socket_thread_server.c和对应的头文件

/******socket_thread_server.c******/
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#include "ctype.h"

static char ReadBuff[BUFF_SIZE];
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used 
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void vNewClientTask(void const * argument){
  // 每一个任务,都有独立的栈空间
  int cfd = * (int *)argument;
  int n, i;
  while(1){
    //等待客户端发送数据
    n = Read(cfd, ReadBuff, BUFF_SIZE);
    if(n <= 0){
      close(cfd);
      vTaskDelete(NULL);
    }
    //进行大小写转换
    for(i = 0; i < n; i++){	
      ReadBuff[i] = toupper(ReadBuff[i]);		
    }
    //写回客户端
    n = Write(cfd, ReadBuff, n);
    if(n < 0){
      close(cfd);
      vTaskDelete(NULL);			
    }
  }
}
/**
  * @brief  多线程服务器
  * @param  none
  * @retval none
  */
void vThreadServerTask(void){
  int sfd, cfd;
  struct sockaddr_in server_addr, client_addr;
  socklen_t	client_addr_len;
  //创建socket
  sfd = Socket(AF_INET, SOCK_STREAM, 0);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port   = htons(SERVER_PORT);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  //绑定socket
  Bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
  //监听socket
  Listen(sfd, 5);
  //等待客户端连接
  client_addr_len = sizeof(client_addr);
  while(1){
    /*每创建一个socket,lwip都会分配一片内存空间
    宏NUM_SOCKETS就定义了一共支持多少个socket,即能分配多少fd
    #define NUM_SOCKETS		MEMP_NUM_NETCONN
    #define MEMP_NUM_NETCONN	8		
    */
    cfd = Accept(sfd,(struct sockaddr *)&client_addr, &client_addr_len);
    printf("client is connect cfd = %d\\r\\n",cfd);
    if(xTaskCreate((TaskFunction_t) vNewClientTask,
		   "Client",
		   128,//1k
		   (void *)&cfd,
		   osPriorityNormal,
		   NULL) != pdPASS){	
      printf("create task fail!\\r\\n");		
    }
  }									
}

在freertos.c文件中的默认任务里面添加代码

void StartDefaultTask(void const * argument){
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN StartDefaultTask */
  printf("TCP thread server started!\\r\\n",cfd);
  /* Infinite loop */
  for(;;){
    vThreadServerTask();
    osDelay(100);
  }
  /* USER CODE END StartDefaultTask */
}

编译无误下载到开发板后,打开串口助手可以看到相关调试信息,使用网络调试工具可以创建多个PC客户端(串口会返回对应的cfd),输入任意小写字母,Server将返回对应的大写字母

图片

图片

2.基于Select的并发服务器

基于多线程的socket并发服务器,必须使用多线程的方式来实现,即为每个连接创建一个单独的任务来处理数据。 但是,这种多线程的方式是有缺陷的,在大型服务器的设计中,一个服务器上可能存在成千上万条连接,如果为每个连接都创建一个线程,这对系统资源来说无疑是比巨大的开销,也是种不太现实的做法。 事实上,在socket编程中,通常使用一种叫做Select的机制来实现并发服务器的设计。

Select函数实现的基本思想为:先构造一张有关描述符的表,然后调用一个函数。 当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回; 函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作

/*****select()函数*****/
函数原型:int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
传 入 值:maxfd 监控的文件描述符集里最大文件描述符加1
	readfds 监控有读数据到达文件描述符集合,传入传出参数
	writefds 监控有写数据到达文件描述符集合,传入传出参数
	exceptfds 监控异常发生达文件描述符集合,传入传出参数
	timeout 超时设置 
	-->NULL:一直阻塞,直到有文件描述符就绪或出错
	-->0:仅仅检测文件描述符集的状态,然后立即返回,轮询
	-->不为0:在指定时间内,如果没有事件发生,则超时返回
返 回 值:成功:所监听的所有监听集合中,满足条件的总数!
	失败:0 超时
	错误:-1
//timeval结构体
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

调用 select() 函数时进程会一直阻塞直到有文件可读、有文件可写或者超时时间到。 为了设置文件描述符需要使用几个宏:

  • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
  • 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
#include 
int FD_ZERO(fd_set *fdset);		//从fdset中清除所有的文件描述符
int FD_CLR(int fd,fd_set *fdset);	//将fd从fdset中清除
int FD_SET(int fd,fd_set *fdset);	//将fd加入到fdset
int FD_ISSET(int fd,fd_set *fdset);	//判断fd是否在fdset集合中
/*例如*/
fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd,&rset);
FD_SET(stdin,&rset);
//在select返回之后,可以使用FD_ISSET(fd,&rset)测试给定的位置是否置位。
if(FD_ISSET(fd,&rset))
{......}

select编程模型如下图示

图片

以下并发服务器实例完成的功能为:服务器能够同时支持多个客户端的连接,并能够将每个连接上接收到的小写字母转换成大写字母回显到客户端,其实现步骤如下:

参考Socket API编程优化一文,在该文的工程源码基础上进行修改

在工程中创建socket_socket_server.c和对应的头文件

#include "socket_wrap.h"
#include "socket_select_server.h"
#include "socket_tcp_server.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "ctype.h"

static char ReadBuff[BUFF_SIZE];
/**
  * @brief  select 并发服务器
  * @param  none
  * @retval none
  */
void vSelectServerTask(void){
  int sfd, cfd, maxfd, i, nready, n, j;
  struct sockaddr_in server_addr, client_addr;
  socklen_t	client_addr_len;
  fd_set all_set, read_set;
  //FD_SETSIZE里面包含了服务器的fd
  int clientfds[FD_SETSIZE - 1];	
  //创建socket
  sfd = Socket(AF_INET, SOCK_STREAM, 0);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVER_PORT);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  //绑定socket
  Bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
  //监听socket
  Listen(sfd, 5);	
  client_addr_len = sizeof(client_addr);
  //初始化 maxfd 等于 sfd
  maxfd = sfd;	
  //清空fdset
  FD_ZERO(&all_set);	
  //把sfd文件描述符添加到集合中	
  FD_SET(sfd, &all_set);
  //初始化客户端fd的集合
  for(i = 0; i < FD_SETSIZE -1 ; i++){
    //初始化为-1
    clientfds[i] = -1;
  }
  while(1){
    //每次select返回之后,fd_set集合就会变化,再select时,就不能使用,
    //所以我们要保存设置fd_set 和 读取的fd_set
    read_set = all_set;
    nready = select(maxfd + 1, &read_set, NULL, NULL, NULL);
    //没有超时机制,不会返回0
    if(nready < 0){
      printf("select error \\r\\n");
      vTaskDelete(NULL);
    }
    //判断监听的套接字是否有数据
    if(FD_ISSET(sfd, &read_set)){	
      //有客户端进行连接了
      cfd = accept(sfd, (struct sockaddr *)&client_addr, &client_addr_len);
      if(cfd < 0){
        printf("accept socket error\\r\\n");
        //继续select
        continue;
      }
      printf("new client connect fd = %d\\r\\n", cfd);
      //把新的cfd 添加到fd_set集合中
      FD_SET(cfd, &all_set);
      //更新要select的maxfd
      maxfd = (cfd > maxfd)?cfd:maxfd;
      //把新的cfd 保存到cfds集合中
      for(i = 0; i < FD_SETSIZE -1 ; i++){
        if(clientfds[i] == -1){
          clientfds[i] = cfd;
          //退出,不需要添加
          break;		
        }
      }
      //没有其他套接字需要处理:这里防止重复工作,就不去执行其他任务
      if(--nready == 0){
        //继续select
        continue;
      }	
    }
    //遍历所有的客户端文件描述符
    for(i = 0; i < FD_SETSIZE -1 ; i++){
      if(clientfds[i] == -1){
        //继续遍历
        continue;
      }
      //是否在我们fd_set集合里面
      if(FD_ISSET(clientfds[i], &read_set)){
        n = Read(clientfds[i], ReadBuff, BUFF_SIZE);
        //Read函数已经关闭了这个客户端的fd
        if(n <= 0){
          //从集合里面清除
          FD_CLR(clientfds[i], &all_set);
          //当前的客户端fd 赋值为-1
          clientfds[i] = -1;
        }else{
          //进行大小写转换
          for(j = 0; j < n; j++){		
            ReadBuff[j] = toupper(ReadBuff[j]);		
          }
          //写回客户端
          n = Write(clientfds[i], ReadBuff, n);
          if(n < 0){
            //从集合里面清除
            FD_CLR(clientfds[i], &all_set);
            //当前的客户端fd 赋值为-1
            clientfds[i] = -1;		
          }				
        }
      }
    }		
  }
}

在freertos.c文件中的默认任务里面添加代码

void StartDefaultTask(void const * argument){
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN StartDefaultTask */
  printf("TCP thread server started!\\r\\n",cfd);
  /* Infinite loop */
  for(;;){
    vSocketServerTask();
    osDelay(100);
  }
  /* USER CODE END StartDefaultTask */
}

编译无误下载到开发板后,打开串口助手可以看到相关调试信息,使用网络调试工具可以创建多个PC客户端(串口会返回对应的cfd),输入任意小写字母,Server将返回对应的大写字母

图片

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    8125

    浏览量

    82539
  • API
    API
    +关注

    关注

    2

    文章

    1384

    浏览量

    60997
  • 调试
    +关注

    关注

    7

    文章

    527

    浏览量

    33625
  • 编程
    +关注

    关注

    88

    文章

    3441

    浏览量

    92415
  • 多线程
    +关注

    关注

    0

    文章

    271

    浏览量

    19726
收藏 人收藏

    评论

    相关推荐

    我国首款亿级并发服务器系统实现量产

    我国高性能计算领军企业中科曙光29日在天津宣布,曙光星河云服务器系统正式量产。这是我国首款亿级并发服务器系统,也是“十二五”期间国家863计划信息技术领域“亿级并发
    发表于 11-30 15:47 758次阅读

    基于Select/Poll实现并发服务器(一)

    LWIP:2.0.2   并发服务器支持多个客户端的同时连接,最大可接入的客户端数取决于内核控制块的个数。当使用Socket API时,要使服务器能够同时支持多个客户端的连接,必须引入多任务机制,为每个
    的头像 发表于 06-20 00:20 3364次阅读
    基于Select/Poll<b class='flag-5'>实现</b><b class='flag-5'>并发</b><b class='flag-5'>服务器</b>(一)

    iLink服务器软件

    iLink商用服务器特点:1、支持高并发,高稳定的单机和集群部署,单台支持50万在线,2万并发,消息毫秒级延迟;2、服务器支持SSL,实现
    发表于 07-07 10:56

    嵌入式Linux系统开发学习路线

    方法和并发服务器实现,了解HTTP协议及其实现方法,熟悉UDP广播、多播的原理及编程方法,掌握混合CS架构网络通信系统的设计,熟悉HTML,Javascript等Web编程技术及
    发表于 09-21 10:09

    Linux基础

    TCP协议服务器的编程方法和并发服务器实现,了解HTTP协议及其实现方法,熟悉UDP广播、多播的原理及编程方法,掌握混合C/S架构网络通信
    发表于 08-03 09:46

    在DragonBoard 410c上实现并发处理TCP服务器

    服务,让传感和相关的控制设备接入,为此,本期blog将向大家介绍如何使用gevent高性能的并发处理库在draognbaord 410c上来实现一个高性能的TCP
    发表于 09-25 15:53

    嵌入式FTP服务器实现什么功能?

    FTP服务是目前广泛应用的因特网应用服务之一,为了在国产嵌入式实时操作系统平台上开发FTP服务,采用多线程并发服务器的体系结构设计了一种嵌入
    发表于 03-11 08:27

    高性能高并发服务器架构分享

    由于自己正在做一个高性能大用户量的论坛程序,对高性能高并发服务器架构比较感兴趣,于是在网上收集了不少这方面的资料和大家分享。希望能和大家交流 msn: ———————————————————————————————————————  初创网站与开源软件 6  谈谈大型
    发表于 09-16 06:45

    如何利用多线程去构建一种TCP并发服务器

    TCP并发服务器,并实现客户端和服务器的传输(多个并发用户同时访问服务器)实验原理:TCP的传输
    发表于 12-22 08:03

    【沁恒微CH32V307评估板试用体验】基于LWIP实现并发服务器

    程,这是最常用的并发服务器设计。但是多线程/多进程消耗资源多,处理起来也比较复杂,本文将基于LWIP协议栈的Select/Poll机制实现并发服务器
    发表于 06-01 23:27

    Linux环境并发服务器设计技术研究

    讲述并发服务器设计的主要技术,包括多进程服务器、多线程服务器和I/ O 复用服务器,同时对以上服务器
    发表于 04-24 10:02 16次下载

    阿里云2核4G服务器租赁的并发怎样算

    阿里云2核4G服务器租赁的并发怎样算?我们要知道我们租用的服务器能支持多少人同时访问,并发数是一个很重要的参考值。很多人不了解服务器
    的头像 发表于 07-07 17:19 1720次阅读

    服务器的高并发能力如何提升?

    服务器的高并发能力如何提升? 服务器并发能力体现着服务器在单位时间内的很强数据处理能力,一般来说,如果企业的互联网业务需要面对大量的同时在
    的头像 发表于 03-17 17:07 734次阅读

    网站服务器并发数的计算方法是什么?

    并发数也就是指同时访问服务器站点的连接数,所以站长为了后期避免主机服务器等资源出现过剩浪费及资源不足等问题的出现,都会对服务器并发数进行计
    的头像 发表于 04-12 15:22 1929次阅读

    服务器并发的概念

    自己调整系统的相关参数 并发的概念是什么?什么是并发? 对于服务器并发的概念,下面几点是错误的定义 ①服务器处理客户端请求的数量:没有时间、
    的头像 发表于 11-10 10:05 1375次阅读
    <b class='flag-5'>服务器</b><b class='flag-5'>并发</b>的概念