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

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

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

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

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

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

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

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

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

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

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

在工程中創(chuàng)建socket_thread_server.c和對(duì)應(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){
  // 每一個(gè)任務(wù),都有獨(dú)立的??臻g
  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);
    }
    //進(jìn)行大小寫(xiě)轉(zhuǎn)換
    for(i = 0; i < n; i++){	
      ReadBuff[i] = toupper(ReadBuff[i]);		
    }
    //寫(xiě)回客戶端
    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)聽(tīng)socket
  Listen(sfd, 5);
  //等待客戶端連接
  client_addr_len = sizeof(client_addr);
  while(1){
    /*每創(chuàng)建一個(gè)socket,lwip都會(huì)分配一片內(nèi)存空間
    宏NUM_SOCKETS就定義了一共支持多少個(gè)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文件中的默認(rèn)任務(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 */
}

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

圖片

圖片

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

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

Select函數(shù)實(shí)現(xiàn)的基本思想為:先構(gòu)造一張有關(guān)描述符的表,然后調(diào)用一個(gè)函數(shù)。 當(dāng)這些文件描述符中的一個(gè)或多個(gè)已準(zhǔn)備好進(jìn)行I/O時(shí)函數(shù)才返回; 函數(shù)返回時(shí)告訴進(jìn)程哪個(gè)描述符已就緒,可以進(jìn)行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ù)到達(dá)文件描述符集合,傳入傳出參數(shù)
	writefds 監(jiān)控有寫(xiě)數(shù)據(jù)到達(dá)文件描述符集合,傳入傳出參數(shù)
	exceptfds 監(jiān)控異常發(fā)生達(dá)文件描述符集合,傳入傳出參數(shù)
	timeout 超時(shí)設(shè)置 
	-->NULL:一直阻塞,直到有文件描述符就緒或出錯(cuò)
	-->0:僅僅檢測(cè)文件描述符集的狀態(tài),然后立即返回,輪詢
	-->不為0:在指定時(shí)間內(nèi),如果沒(méi)有事件發(fā)生,則超時(shí)返回
返 回 值:成功:所監(jiān)聽(tīng)的所有監(jiān)聽(tīng)集合中,滿足條件的總數(shù)!
	失?。? 超時(shí)
	錯(cuò)誤:-1
//timeval結(jié)構(gòu)體
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

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

  • select能監(jiān)聽(tīng)的文件描述符個(gè)數(shù)受限于FD_SETSIZE,一般為1024,單純改變進(jìn)程打開(kāi)的文件描述符個(gè)數(shù)并不能改變select監(jiān)聽(tīng)文件個(gè)數(shù)
  • 解決1024以下客戶端時(shí)使用select是很合適的,但如果鏈接客戶端過(guò)多,select采用的是輪詢模型,會(huì)大大降低服務(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)測(cè)試給定的位置是否置位。
if(FD_ISSET(fd,&rset))
{......}

select編程模型如下圖示

圖片

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

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

在工程中創(chuàng)建socket_socket_server.c和對(duì)應(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)聽(tīng)socket
  Listen(sfd, 5);	
  client_addr_len = sizeof(client_addr);
  //初始化 maxfd 等于 sfd
  maxfd = sfd;	
  //清空f(shuō)dset
  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集合就會(huì)變化,再select時(shí),就不能使用,
    //所以我們要保存設(shè)置fd_set 和 讀取的fd_set
    read_set = all_set;
    nready = select(maxfd + 1, &read_set, NULL, NULL, NULL);
    //沒(méi)有超時(shí)機(jī)制,不會(huì)返回0
    if(nready < 0){
      printf("select error \\r\\n");
      vTaskDelete(NULL);
    }
    //判斷監(jiān)聽(tīng)的套接字是否有數(shù)據(jù)
    if(FD_ISSET(sfd, &read_set)){	
      //有客戶端進(jìn)行連接了
      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;		
        }
      }
      //沒(méi)有其他套接字需要處理:這里防止重復(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)閉了這個(gè)客戶端的fd
        if(n <= 0){
          //從集合里面清除
          FD_CLR(clientfds[i], &all_set);
          //當(dāng)前的客戶端fd 賦值為-1
          clientfds[i] = -1;
        }else{
          //進(jìn)行大小寫(xiě)轉(zhuǎn)換
          for(j = 0; j < n; j++){		
            ReadBuff[j] = toupper(ReadBuff[j]);		
          }
          //寫(xiě)回客戶端
          n = Write(clientfds[i], ReadBuff, n);
          if(n < 0){
            //從集合里面清除
            FD_CLR(clientfds[i], &all_set);
            //當(dāng)前的客戶端fd 賦值為-1
            clientfds[i] = -1;		
          }				
        }
      }
    }		
  }
}

在freertos.c文件中的默認(rèn)任務(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 */
}

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

圖片

圖片

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

    關(guān)注

    12

    文章

    8700

    瀏覽量

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

    關(guān)注

    2

    文章

    1461

    瀏覽量

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

    關(guān)注

    7

    文章

    551

    瀏覽量

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

    關(guān)注

    88

    文章

    3521

    瀏覽量

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

    關(guān)注

    0

    文章

    275

    瀏覽量

    19850
收藏 人收藏

    評(píng)論

    相關(guān)推薦

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

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

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

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

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

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

    Linux基礎(chǔ)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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