0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

并發(fā)服務(wù)器的設(shè)計與實現(xiàn)

CHANBAEK ? 來源:嵌入式攻城獅 ? 作者:嵌入式攻城獅 ? 2023-04-25 15:35 ? 次閱讀

并發(fā)服務(wù)器

1.基于多線程的并發(fā)服務(wù)器

并發(fā)服務(wù)器支持多個客戶端的連接,最大可接入的客戶端數(shù)取決于內(nèi)核控制塊的個數(shù)。 當(dāng)使用Socket API時,要使服務(wù)器能夠同時支持多個客戶端的連接,必須引入多任務(wù)機制,為每個連接創(chuàng)建一個單獨的任務(wù)來處理連接上的數(shù)據(jù),我們將這個設(shè)計方式稱作并發(fā)服務(wù)器的設(shè)計。

由于多線程并發(fā)服務(wù)器涉及到子任務(wù)的動態(tài)創(chuàng)建和銷毀,用戶需要自己完成對任務(wù)堆棧的管理和回收,因此并發(fā)服務(wù)器的設(shè)計流程也相對復(fù)雜。

以下并發(fā)服務(wù)器實例完成的功能為:服務(wù)器能夠同時支持多個客戶端的連接,并能夠?qū)⒚總€連接上接收到的小寫字母轉(zhuǎn)換成大寫字母回顯到客戶端,其實現(xiàn)步驟如下

參考Socket API編程優(yōu)化一文,在該文的工程源碼基礎(chǔ)上進行修改

在工程中創(chuàng)建socket_thread_server.c和對應(yīng)的頭文件

/******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){
  // 每一個任務(wù),都有獨立的棧空間
  int cfd = * (int *)argument;
  int n, i;
  while(1){
    //等待客戶端發(fā)送數(shù)據(jù)
    n = Read(cfd, ReadBuff, BUFF_SIZE);
    if(n <= 0){
      close(cfd);
      vTaskDelete(NULL);
    }
    //進行大小寫轉(zhuǎn)換
    for(i = 0; i < n; i++){	
      ReadBuff[i] = toupper(ReadBuff[i]);		
    }
    //寫回客戶端
    n = Write(cfd, ReadBuff, n);
    if(n < 0){
      close(cfd);
      vTaskDelete(NULL);			
    }
  }
}
/**
  * @brief  多線程服務(wù)器
  * @param  none
  * @retval none
  */
void vThreadServerTask(void){
  int sfd, cfd;
  struct sockaddr_in server_addr, client_addr;
  socklen_t	client_addr_len;
  //創(chuàng)建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));
  //監(jiān)聽socket
  Listen(sfd, 5);
  //等待客戶端連接
  client_addr_len = sizeof(client_addr);
  while(1){
    /*每創(chuàng)建一個socket,lwip都會分配一片內(nèi)存空間
    宏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文件中的默認任務(wù)里面添加代碼

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 */
}

編譯無誤下載到開發(fā)板后,打開串口助手可以看到相關(guān)調(diào)試信息,使用網(wǎng)絡(luò)調(diào)試工具可以創(chuàng)建多個PC客戶端(串口會返回對應(yīng)的cfd),輸入任意小寫字母,Server將返回對應(yīng)的大寫字母

圖片

圖片

2.基于Select的并發(fā)服務(wù)器

基于多線程的socket并發(fā)服務(wù)器,必須使用多線程的方式來實現(xiàn),即為每個連接創(chuàng)建一個單獨的任務(wù)來處理數(shù)據(jù)。 但是,這種多線程的方式是有缺陷的,在大型服務(wù)器的設(shè)計中,一個服務(wù)器上可能存在成千上萬條連接,如果為每個連接都創(chuàng)建一個線程,這對系統(tǒng)資源來說無疑是比巨大的開銷,也是種不太現(xiàn)實的做法。 事實上,在socket編程中,通常使用一種叫做Select的機制來實現(xiàn)并發(fā)服務(wù)器的設(shè)計。

Select函數(shù)實現(xiàn)的基本思想為:先構(gòu)造一張有關(guān)描述符的表,然后調(diào)用一個函數(shù)。 當(dāng)這些文件描述符中的一個或多個已準(zhǔn)備好進行I/O時函數(shù)才返回; 函數(shù)返回時告訴進程哪個描述符已就緒,可以進行I/O操作

/*****select()函數(shù)*****/
函數(shù)原型:int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
傳 入 值:maxfd 監(jiān)控的文件描述符集里最大文件描述符加1
	readfds 監(jiān)控有讀數(shù)據(jù)到達文件描述符集合,傳入傳出參數(shù)
	writefds 監(jiān)控有寫數(shù)據(jù)到達文件描述符集合,傳入傳出參數(shù)
	exceptfds 監(jiān)控異常發(fā)生達文件描述符集合,傳入傳出參數(shù)
	timeout 超時設(shè)置 
	-->NULL:一直阻塞,直到有文件描述符就緒或出錯
	-->0:僅僅檢測文件描述符集的狀態(tài),然后立即返回,輪詢
	-->不為0:在指定時間內(nèi),如果沒有事件發(fā)生,則超時返回
返 回 值:成功:所監(jiān)聽的所有監(jiān)聽集合中,滿足條件的總數(shù)!
	失?。? 超時
	錯誤:-1
//timeval結(jié)構(gòu)體
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

調(diào)用 select() 函數(shù)時進程會一直阻塞直到有文件可讀、有文件可寫或者超時時間到。 為了設(shè)置文件描述符需要使用幾個宏:

  • select能監(jiān)聽的文件描述符個數(shù)受限于FD_SETSIZE,一般為1024,單純改變進程打開的文件描述符個數(shù)并不能改變select監(jiān)聽文件個數(shù)
  • 解決1024以下客戶端時使用select是很合適的,但如果鏈接客戶端過多,select采用的是輪詢模型,會大大降低服務(wù)器響應(yīng)效率,不應(yīng)在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編程模型如下圖示

圖片

以下并發(fā)服務(wù)器實例完成的功能為:服務(wù)器能夠同時支持多個客戶端的連接,并能夠?qū)⒚總€連接上接收到的小寫字母轉(zhuǎn)換成大寫字母回顯到客戶端,其實現(xiàn)步驟如下:

參考Socket API編程優(yōu)化一文,在該文的工程源碼基礎(chǔ)上進行修改

在工程中創(chuàng)建socket_socket_server.c和對應(yīng)的頭文件

#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 并發(fā)服務(wù)器
  * @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里面包含了服務(wù)器的fd
  int clientfds[FD_SETSIZE - 1];	
  //創(chuàng)建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));
  //監(jiān)聽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時,就不能使用,
    //所以我們要保存設(shè)置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);
    }
    //判斷監(jiān)聽的套接字是否有數(shù)據(jù)
    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");
        //繼續(xù)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;		
        }
      }
      //沒有其他套接字需要處理:這里防止重復(fù)工作,就不去執(zhí)行其他任務(wù)
      if(--nready == 0){
        //繼續(xù)select
        continue;
      }	
    }
    //遍歷所有的客戶端文件描述符
    for(i = 0; i < FD_SETSIZE -1 ; i++){
      if(clientfds[i] == -1){
        //繼續(xù)遍歷
        continue;
      }
      //是否在我們fd_set集合里面
      if(FD_ISSET(clientfds[i], &read_set)){
        n = Read(clientfds[i], ReadBuff, BUFF_SIZE);
        //Read函數(shù)已經(jīng)關(guān)閉了這個客戶端的fd
        if(n <= 0){
          //從集合里面清除
          FD_CLR(clientfds[i], &all_set);
          //當(dāng)前的客戶端fd 賦值為-1
          clientfds[i] = -1;
        }else{
          //進行大小寫轉(zhuǎn)換
          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);
            //當(dāng)前的客戶端fd 賦值為-1
            clientfds[i] = -1;		
          }				
        }
      }
    }		
  }
}

在freertos.c文件中的默認任務(wù)里面添加代碼

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 */
}

編譯無誤下載到開發(fā)板后,打開串口助手可以看到相關(guān)調(diào)試信息,使用網(wǎng)絡(luò)調(diào)試工具可以創(chuàng)建多個PC客戶端(串口會返回對應(yīng)的cfd),輸入任意小寫字母,Server將返回對應(yīng)的大寫字母

圖片

圖片

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    9160

    瀏覽量

    85428
  • API
    API
    +關(guān)注

    關(guān)注

    2

    文章

    1501

    瀏覽量

    62025
  • 調(diào)試
    +關(guān)注

    關(guān)注

    7

    文章

    578

    瀏覽量

    33943
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3616

    瀏覽量

    93738
  • 多線程
    +關(guān)注

    關(guān)注

    0

    文章

    278

    瀏覽量

    19961
收藏 人收藏

    評論

    相關(guān)推薦

    我國首款億級并發(fā)服務(wù)器系統(tǒng)實現(xiàn)量產(chǎn)

    我國高性能計算領(lǐng)軍企業(yè)中科曙光29日在天津宣布,曙光星河云服務(wù)器系統(tǒng)正式量產(chǎn)。這是我國首款億級并發(fā)服務(wù)器系統(tǒng),也是“十二五”期間國家863計劃信息技術(shù)領(lǐng)域“億級并發(fā)
    發(fā)表于 11-30 15:47 ?840次閱讀

    基于Select/Poll實現(xiàn)并發(fā)服務(wù)器(一)

    LWIP:2.0.2 ? 并發(fā)服務(wù)器支持多個客戶端的同時連接,最大可接入的客戶端數(shù)取決于內(nèi)核控制塊的個數(shù)。當(dāng)使用Socket API時,要使服務(wù)器能夠同時支持多個客戶端的連接,必須引入多任務(wù)機制,為每個
    的頭像 發(fā)表于 06-20 00:20 ?3822次閱讀
    基于Select/Poll<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>并發(fā)</b><b class='flag-5'>服務(wù)器</b>(一)

    嵌入式Linux系統(tǒng)開發(fā)學(xué)習(xí)路線

    方法和并發(fā)服務(wù)器實現(xiàn),了解HTTP協(xié)議及其實現(xiàn)方法,熟悉UDP廣播、多播的原理及編程方法,掌握混合CS架構(gòu)網(wǎng)絡(luò)通信系統(tǒng)的設(shè)計,熟悉HTML,Javascript等Web編程技術(shù)及
    發(fā)表于 09-21 10:09

    Linux基礎(chǔ)

    TCP協(xié)議服務(wù)器的編程方法和并發(fā)服務(wù)器實現(xiàn),了解HTTP協(xié)議及其實現(xiàn)方法,熟悉UDP廣播、多播的原理及編程方法,掌握混合C/S架構(gòu)網(wǎng)絡(luò)通信
    發(fā)表于 08-03 09:46

    在DragonBoard 410c上實現(xiàn)并發(fā)處理TCP服務(wù)器

    服務(wù),讓傳感和相關(guān)的控制設(shè)備接入,為此,本期blog將向大家介紹如何使用gevent高性能的并發(fā)處理庫在draognbaord 410c上來實現(xiàn)一個高性能的TCP
    發(fā)表于 09-25 15:53

    嵌入式FTP服務(wù)器實現(xiàn)什么功能?

    FTP服務(wù)是目前廣泛應(yīng)用的因特網(wǎng)應(yīng)用服務(wù)之一,為了在國產(chǎn)嵌入式實時操作系統(tǒng)平臺上開發(fā)FTP服務(wù),采用多線程并發(fā)服務(wù)器的體系結(jié)構(gòu)設(shè)計了一種嵌入
    發(fā)表于 03-11 08:27

    高性能高并發(fā)服務(wù)器架構(gòu)分享

    由于自己正在做一個高性能大用戶量的論壇程序,對高性能高并發(fā)服務(wù)器架構(gòu)比較感興趣,于是在網(wǎng)上收集了不少這方面的資料和大家分享。希望能和大家交流 msn: ——————————————————————————————————————— ? 初創(chuàng)網(wǎng)站與開源軟件 6 ? 談?wù)劥笮?/div>
    發(fā)表于 09-16 06:45

    如何利用多線程去構(gòu)建一種TCP并發(fā)服務(wù)器

    TCP并發(fā)服務(wù)器,并實現(xiàn)客戶端和服務(wù)器的傳輸(多個并發(fā)用戶同時訪問服務(wù)器)實驗原理:TCP的傳輸
    發(fā)表于 12-22 08:03

    【沁恒微CH32V307評估板試用體驗】基于LWIP實現(xiàn)并發(fā)服務(wù)器

    程,這是最常用的并發(fā)服務(wù)器設(shè)計。但是多線程/多進程消耗資源多,處理起來也比較復(fù)雜,本文將基于LWIP協(xié)議棧的Select/Poll機制實現(xiàn)并發(fā)服務(wù)器
    發(fā)表于 06-01 23:27

    Linux環(huán)境并發(fā)服務(wù)器設(shè)計技術(shù)研究

    講述并發(fā)服務(wù)器設(shè)計的主要技術(shù),包括多進程服務(wù)器、多線程服務(wù)器和I/ O 復(fù)用服務(wù)器,同時對以上服務(wù)器
    發(fā)表于 04-24 10:02 ?16次下載

    阿里云2核4G服務(wù)器租賃的并發(fā)怎樣算

    阿里云2核4G服務(wù)器租賃的并發(fā)怎樣算?我們要知道我們租用的服務(wù)器能支持多少人同時訪問,并發(fā)數(shù)是一個很重要的參考值。很多人不了解服務(wù)器
    的頭像 發(fā)表于 07-07 17:19 ?1983次閱讀

    單臺服務(wù)器支持的TCP并發(fā)連接數(shù)

    總之,65535只是Linux系統(tǒng)中可使用端口port數(shù)量的上限,端口port數(shù)量與TCP連接數(shù)量并非完全一一對應(yīng)的關(guān)系,服務(wù)器支持的TCP并發(fā)連接數(shù)量主要跟服務(wù)器的內(nèi)存以及允許單個進程同時打開
    的頭像 發(fā)表于 11-06 19:36 ?1624次閱讀

    服務(wù)器的高并發(fā)能力如何提升?

    服務(wù)器的高并發(fā)能力如何提升? 服務(wù)器并發(fā)能力體現(xiàn)著服務(wù)器在單位時間內(nèi)的很強數(shù)據(jù)處理能力,一般來說,如果企業(yè)的互聯(lián)網(wǎng)業(yè)務(wù)需要面對大量的同時在
    的頭像 發(fā)表于 03-17 17:07 ?1027次閱讀

    網(wǎng)站服務(wù)器并發(fā)數(shù)的計算方法是什么?

    并發(fā)數(shù)也就是指同時訪問服務(wù)器站點的連接數(shù),所以站長為了后期避免主機服務(wù)器等資源出現(xiàn)過剩浪費及資源不足等問題的出現(xiàn),都會對服務(wù)器并發(fā)數(shù)進行計
    的頭像 發(fā)表于 04-12 15:22 ?3253次閱讀

    服務(wù)器并發(fā)的概念

    自己調(diào)整系統(tǒng)的相關(guān)參數(shù) 并發(fā)的概念是什么?什么是并發(fā)? 對于服務(wù)器并發(fā)的概念,下面幾點是錯誤的定義 ①服務(wù)器處理客戶端請求的數(shù)量:沒有時間、
    的頭像 發(fā)表于 11-10 10:05 ?5049次閱讀
    <b class='flag-5'>服務(wù)器</b><b class='flag-5'>并發(fā)</b>的概念