socket 通信如何在使用select處理与使用时机

Select 是什么? 

Socket 函數中的 select() 是一個多工函數,可以同時處理多個通信端口。它可以讓您的應用程式在處理網路通信時保持輪詢狀態,同時也可以讓您的應用程式同時處理多個通信端口。

通常,當您的應用程式執行時,它會一直處於等待狀態,等待某個事件發生。例如,當網路服務器接收到來自客戶端的請求時,它會處理請求並返回響應。這個過程可能需要花費相當長的時間,因此服務器無法同時處理多個請求。

Select() 函數可以讓您的應用程式同時處理多個通信端口,而不用擔心阻塞問題。它會監控所有指定的通信端口,並在任何一個通信端口有數據可讀時立即返回。這樣,您的應用程式就可以同時處理多個通信端口,並確保處理的效率。

此外,Select() 函數還可以指定超時時間,以便在指定的時間內沒有任何數據可讀時仍然可以返回。這對於實現心跳檢測或定時更新等功能非常有用。

在使用 Select() 函數時,您需要傳遞三個參數:

nfds:指定監控的文件描述符的個數。
readfds:指定要監控的讀取文件描述符集合。
writefds:指定要監控的寫入文件描述符集合。
在呼叫 Select() 函數後,它會阻塞直到任何一個指定的文件描述符有數據可讀或可寫,或者超時。如果有數據可讀或可寫,則返回已經可讀或可寫的文件描述符的個數。如果在超時時間內沒有數據可讀或可寫,則返回 0。

範例1,展示如何使用 select() 在多個 socket 之間做 I/O 多工:

select() 是一個系統呼叫(system call),它可以用來監測多個文件描述符(file descriptor)的狀態,並在其中一個或多個文件描述符就緒時返回。它常被用來實現複雜的 I/O 多工(I/O multiplexing),在 socket 網路程序中也很常見。

#include 
#include 

#define MAX_CLIENTS 10

int main() {
  int i, maxfd, client_sockets[MAX_CLIENTS];
  fd_set readfds;  // 用於 select 的文件描述符集合
  struct timeval timeout;  // select 的超時時間

  // 初始化 client_sockets 數組和 maxfd
  for (i = 0; i < MAX_CLIENTS; i++) {
    client_sockets[i] = -1;
  }
  maxfd = 0;

  while (1) {
    // 初始化 readfds 和 timeout
    FD_ZERO(&readfds);
    for (i = 0; i < MAX_CLIENTS; i++) {
      if (client_sockets[i] > 0) {
        FD_SET(client_sockets[i], &readfds);
      }
      maxfd = (client_sockets[i] > maxfd) ? client_sockets[i] : maxfd;
    }
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    // 調用 select
    int nready = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
    if (nready < 0) {
      perror("select error");
      break;
    } else if (nready == 0) {
      printf("select timeout\n");
      continue;
    }

    // 檢查哪個文件描述符就緒
    for (i = 0; i < MAX_CLIENTS; i++) {
      if (client_sockets[i] > 0 && FD_ISSET(client_sockets[i], &readfds)) {
        // 對於就緒的 socket
        // 處理就緒的 socket
        char buffer[1024];
        int n = recv(client_sockets[i], buffer, sizeof(buffer), 0);
        if (n < 0) {
          perror("recv error");
          close(client_sockets[i]);
          client_sockets[i] = -1;
        } else if (n == 0) {
          printf("client disconnected\n");
          close(client_sockets[i]);
          client_sockets[i] = -1;
        } else {
          printf("received %d bytes: %s\n", n, buffer);
          // 將收到的資料原樣傳回客戶端
          send(client_sockets[i], buffer, n, 0);
        }
      }
    }
  }

  // 清理
  for (i = 0; i < MAX_CLIENTS; i++) {
    if (client_sockets[i] > 0) {
      close(client_sockets[i]);
    }
  }

  return 0;
}

在這個範例中,我們使用 select() 監測 client_sockets 數組中的所有 socket,如果其中任意一個 socket 就緒,就會接收其中的資料並回傳。

注意,這只是一個簡單的範例,實際的程式可能會更複雜。例如,你可能需要考慮如何處理新的連接請求、如何管理多個連接、如何避免資料碎片(fragmentation)等問題。


範例2,展示如何使用 select 來監聽 socket 的讀寫狀態:

select 通常是用來監聽多個 socket 的讀寫狀態,當有 socket 可以進行讀寫操作時 select 會返回。下面是一個簡單的 C++ 範例,展示如何使用 select 來監聽 socket 的讀寫狀態。

#include 
#include 
#include 

#include 
#include 
#include 

int main() {
  // 建立 socket
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    std::cerr << "socket error" << std::endl;
    return 1;
  }

  // 設置 socket 連線資訊
  struct sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(8080);
  serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

  // 連線到 server
  if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
    std::cerr << "connect error" << std::endl;
    return 1;
  }

  // 使用 select 監聽 socket 的讀寫狀態
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(sockfd, &readfds);

  int n = sockfd + 1;
  while (true) {
    fd_set tmpfds = readfds;
    int ret = select(n, &tmpfds, nullptr, nullptr, nullptr);
    if (ret < 0) {
      std::cerr << "select error" << std::endl;
      break;
    }

    // 如果有資料可以讀取
    if (FD_ISSET(sockfd, &tmpfds)) {
      // 讀取資料
      std::vector buf(1024);
      int r = read(sockfd, buf.data(), buf.size());
      if (r <= 0) {
        break;
      }
      std::cout.write(buf.data(), r);
    }
  }

  close(sockfd);
  return 0;
}

上面的程式碼會建立一個 socket,然後連線到本機的 8080 port。

接著程式碼會使用 select 監聽 socket 的讀寫狀態,並在有資料可以讀取時輸出到標準輸出。

需要注意的是,在設置 select 監聽的描述符集合時,需要使用 FD_ZERO 和 FD_SET 將描述符加入集合中。在呼叫 select 時,第一個參數是描述符的最大值加一,第二個參數是讀取描述符集合,第三個參數是寫入描述符集合,第四個參數是錯誤描述符集合,第五個參數是超時時間。如果 select 返回大於 0 的值,則表示有至少一個描述符可以進行讀寫操作,可以使用 FD_ISSET 判斷哪個描述符可以進行讀寫操作。

希望這個範例能幫助您了解如何使用 select 監聽 socket 的讀寫狀態。


使用select好 還是多線程去處理好?

使用 select 和多線程的方式都可以用來監聽多個 socket 的讀寫狀態,因此選擇哪種方式取決於你的需求和優化目標。

使用 select 的優點在於簡單易用,它可以同時監聽多個 socket 和檔案描述符,並且在大多數系統中都有實作。但是,select 的缺點在於當監聽的描述符數量非常多時,效率會非常低下,而且它不能很好地支援超大量的描述符。

相反的,使用多線程的方式可以很好地支援超大量的描述符,並且在描述符數量較多時效率比較高。但是,它的缺點在於較難維護,因為需要管理多個執行緒和鎖定資源。

總結來說,如果你的應用程式需要同時監聽大量的描述符,或者你需要使用多個執行緒來平衡負載,則使用多線程的方式可能是更好的選擇。相反的,如果你的應用程式只需要監聽少量的描述符,或者你希望保持簡單易用,則使用 select 的方式可能是更好的選擇。

希望這些資訊能幫助您作出選擇。


這個監聽數量是多少時候 select 就不適合使用?

監聽數量達到幾個時 select 不再適合使用,並不是一個確切的數字,因為這取決於系統的實際情況。在大多數系統中,select 的設計目的是用來監聽少量的描述符,因此當描述符數量達到數千個時,其效率會大幅下降。

然而,也有一些實現方式可以支援較大的描述符數量。例如,Linux 的實現方式可以支援超過 1024 個描述符,但是它是透過在底層使用 epoll 來實現的。因此,如果你的程式需要監聽較大的描述符數量,則可以考慮使用 epoll 或其他類似的技術來實現。

總的來說,當描述符數量達到數千個時,使用 select 的方式就不再是最佳選擇。在這種情況下,可以考慮使用多個執行緒或其他類似的技術來實現。


可以考慮使用多執行緒下的單一執行緒使用select嗎? 

是的,你可以在多執行緒的應用程式中使用單一執行緒來使用 select 監聽 socket 的讀寫狀態。這種方式的優點在於可以利用多個執行緒來平衡負載,同時又可以使用 select 的方式來監聽 socket。

然而,使用多個執行緒並使用單一執行緒來使用 select 的方式,可能會帶來一些維護上的麻煩,因為需要在多個執行緒之間同步資料和管理鎖定。此外,還需要注意多個執行緒之間的競爭,以避免競爭帶來的效率問題。

總的來說,使用多個執行緒並使用單一執行緒來使用 select 的方式,是一種可行的選擇,但是需要注意一些細節。

使用多個執行緒並使用單一執行緒來使用 select 的方式,需要注意以下幾點細節:

  • 同步資料: 在多個執行緒之間共用資料時,需要使用鎖定機制或其他方式來同步資料,以避免競爭帶來的效率問題。
  • 管理鎖定: 在使用鎖定機制時,需要注意鎖定的範圍和次數,以避免死結或無限等待的問題。
  • 競爭問題: 在多個執行緒之間競爭資源時,需要注意競爭帶來的效率問題,並慎選合適的鎖定機制。

這些細節都是在使用多個執行緒並使用單一執行緒來使用 select 的方式時,需要特別注意的地方。希望這些資訊能幫助您了解這種方式的特性。

你可能感兴趣的:(C++,SOCKET,c++,websocket)