select基本介紹

  • 2019 年 10 月 4 日
  • 筆記

select基本介紹

1、前言

首先說一下觀點:人人都應該懂:select、poll、epoll。

無論是做web開發的,還是做大數據開發,都應該懂這些。

這三個知識點,很重要,原因有三條:(1) 面試中經常被問到。尤其是面試高級工程師,架構師的時候。(2) 一切高並發框架的基礎技術支撐。Nginx,Redis,kafka等框架都立足於這些基 礎技術。(3)select、poll、epoll代表著一種數據結構,代表著一種演算法思想,應該好好的去學 習,去體會。其實很多人對數據結構和演算法不了解,投入力度不大,覺得工作中用不到, 其實,其實這個觀點要糾正一下。

2、文件與監控

在linux裡面,一切都是文件,鍵盤,顯示器等等,一切都是文件。每個文件都有一個id 來標識,被稱為文件描述符。每個文件都有可讀,可寫,異常三大事件。

類似: 在Java裡面,除了基本類型,其他的一切都是對象,每個對象都有一個ID,也就是哈希 值。

那如何監控這些事件呢?很簡單,就是把它放在系統內核裡面,讓系統去監控。

可是放進去之後,事件沒有發生怎麼辦?可以一直等著,那就是堵塞;如果不等,馬上返 回,那就是非堵塞了。

這些文件事件的監控的具體實現就是select函數調用。所以說select函數非常重要啊。

3、select介紹

select本質就是文件事件的監控機制,是linux最本質,最核心的東西。

整個監控的流程分為幾個基本步驟:第一步:確定監控的文件是什麼,定義其文件id 第二步:把文件放到一個集合裡面,可以放多個文件,也就可以監控多個文件。具體怎麼放置,類似於bitmap,一個bit位表示一個文件。第四步:將文件集合,複製三份,並分別監控可讀,可寫,發生錯誤三種事件。第五步:將文件集合,放到系統內核,並設置過期時間。到一定時間,如果沒有發生事件 就返回。

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

下面說一下對函數的解釋:

返回結果是int類型的 maxfd就是要監控的文件的集合 第二個參數readset,是第一個參數的複製,對可讀事件的監控 第三個參數writeset,是第一個參數的複製,對可寫事件的監控 第四個參數exceptset,是第一個參數的複製,對異常事件的監控 timeout,表示準備監控多久

4、select源碼例子

//客戶端  #include <sys/types.h>  #include <sys/socket.h>  #include <stdio.h>  #include <netinet/in.h>  #include <arpa/inet.h>  #include <unistd.h>  #include <stdlib.h>  #include <sys/time.h>    int main()  {      //第一步:建立客戶端socket      int client_sockfd = socket(AF_INET, SOCK_STREAM, 0);      //第二步:規劃地址,    struct sockaddr_in address;      address.sin_family = AF_INET;      address.sin_addr.s_addr = inet_addr("127.0.0.1");      address.sin_port = htons(9999);      int len = sizeof(address);      //第三步:建立連接      int result = connect(client_sockfd, (struct sockaddr *)&address, len);      if(result == -1)      {           perror("oops: client2");           exit(1);      }      char buffer = '@';        write(client_sockfd, &buffer, 1);      read(client_sockfd, &buffer, 1);      printf("收到伺服器的返回:%cn", buffer);        close(client_sockfd);      printf("關閉鏈接n");      return 0;  }
#include <sys/types.h>  #include <sys/socket.h>  #include <stdio.h>  #include <netinet/in.h>  #include <sys/time.h>  #include <sys/ioctl.h>  #include <unistd.h>  #include <stdlib.h>    #define FALSE   0  #define TRUE    1    int main()  {      //第一步:建立一個socket      int server_sockfd  = socket(AF_INET, SOCK_STREAM, 0);      //第二步:建立一個地址,好比是規劃局,規划出一塊兒地    struct sockaddr_in server_address ;        server_address.sin_family = AF_INET;      server_address.sin_addr.s_addr = htonl(INADDR_ANY);      server_address.sin_port = htons(9999);      int server_len = sizeof(server_address);      //第三步:將socket綁定到地址    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);      //第四步:建立一個監聽隊列,監聽隊列最多容納5個鏈接    listen(server_sockfd, 5);      //第五步:建立兩個文件集合,將伺服器socket加入到集合中,類似於一個哈希表,元素是socket,然後socket帶著一個隊列,隊列裡面放在它監聽的隊列,長度是5    fd_set originalfds, forkfds;    FD_ZERO(&originalfds);      FD_SET(server_sockfd, &originalfds);      //總結一下:上面的五步,其實就是一個哈希結構,      while(TRUE)      {          forkfds = originalfds;//將需要監視的描述符集copy到select查詢隊列中,select會對其修改,所以一定要分開使用變數          printf("server開啟等待模式n");            /*無限期阻塞,一直等待事件的發生*/          int result = select(FD_SETSIZE, &forkfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0);      //FD_SETSIZE:系統默認的最大文件描述符,為什麼是所有呢?留作一個疑問題          if(result < 1)          {              perror("server is error");              exit(1);          }          int fd = 0;          for(fd = 0; fd < FD_SETSIZE; fd++)          {              if(FD_ISSET(fd,&forkfds))              {                  if(fd == server_sockfd)                  {//判斷是否為伺服器套接字,否則表示為客戶請求連接。雖然在整個linux系列裡面同時存在了n個文件,但是在此進程裡面,就兩種類型的文件:伺服器端的socket,客戶端的socket                      struct sockaddr_in client_address;            int client_len = sizeof(client_address);                      int client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);                      FD_SET(client_sockfd, &originalfds);                      printf("添加監控client端的socket fd: %dn", client_sockfd);                  }                  else                  {            int readcount;                      ioctl(fd, FIONREAD, &readcount);                      if(readcount == 0)                      { //客戶數據請求完畢,關閉套接字,從集合中清除相應描述符                          close(fd);                          FD_CLR(fd, &originalfds);                          printf("移除監控client端的socket fd: %dn", fd);                      }                      else                      { //處理客戶數據請求              char buffer;                          read(fd, &buffer, 1);                          printf("處理client端的socket fd: %dn", fd);              printf("收到客戶端發送的數據:%cn", buffer);                          write(fd, &buffer, 1);                      }                  }              }          }      }      return 0;  }

Tags: