精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

    文章

    9029

    瀏覽量

    85207
  • API
    API
    +關注

    關注

    2

    文章

    1487

    瀏覽量

    61833
  • 調試
    +關注

    關注

    7

    文章

    574

    瀏覽量

    33901
  • 編程
    +關注

    關注

    88

    文章

    3596

    瀏覽量

    93610
  • 多線程
    +關注

    關注

    0

    文章

    277

    瀏覽量

    19923
收藏 人收藏

    評論

    相關推薦

    我國首款億級并發服務器系統實現量產

    我國高性能計算領軍企業中科曙光29日在天津宣布,曙光星河云服務器系統正式量產。這是我國首款億級并發服務器系統,也是“十二五”期間國家863計劃信息技術領域“億級并發
    發表于 11-30 15:47 ?830次閱讀

    基于Select/Poll實現并發服務器(一)

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

    嵌入式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 ?1950次閱讀

    單臺服務器支持的TCP并發連接數

    總之,65535只是Linux系統中可使用端口port數量的上限,端口port數量與TCP連接數量并非完全一一對應的關系,服務器支持的TCP并發連接數量主要跟服務器的內存以及允許單個進程同時打開
    的頭像 發表于 11-06 19:36 ?1566次閱讀

    服務器的高并發能力如何提升?

    服務器的高并發能力如何提升? 服務器并發能力體現著服務器在單位時間內的很強數據處理能力,一般來說,如果企業的互聯網業務需要面對大量的同時在
    的頭像 發表于 03-17 17:07 ?989次閱讀

    網站服務器并發數的計算方法是什么?

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

    服務器并發的概念

    自己調整系統的相關參數 并發的概念是什么?什么是并發? 對于服務器并發的概念,下面幾點是錯誤的定義 ①服務器處理客戶端請求的數量:沒有時間、
    的頭像 發表于 11-10 10:05 ?4974次閱讀
    <b class='flag-5'>服務器</b><b class='flag-5'>并發</b>的概念