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; }