1. Linux下的五種I/O模型
數組
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O複用(select 和poll) (I/O multiplexing)
4)信號驅動I/O (signal driven I/O (SIGIO))
5)異步I/O (asynchronous I/O (the POSIX aio_functions))瀏覽器
(前四種都是同步,只有最後一種纔是異步IO。)服務器
2.多路複用--select網絡
系統提供select函數來實現多路複用輸入/輸出模型。select系統調用是用來讓咱們的程序監視多個文件句柄的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有一個或多個發生了狀態改變。關於文件句柄,其實就是一個整數,咱們最熟悉的句柄是0、一、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、一、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr。數據結構
select函數異步
int select(int maxfd,fd_set *rdset,fd_set *wrset, \ fd_set *exset,struct timeval *timeout);
參數說明:socket
參數maxfd是須要監視的最大的文件描述符值+1;rdset,wrset,exset分別對應於須要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。struct timeval結構用於描述一段時間長度,若是在這個時間內,須要監視的描述符沒有事件發生則函數返回,返回值爲0。async
下面的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否爲真
FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的所有位ide
參數timeout爲結構timeval,用來設置select()的等待時間,其結構定義以下:函數
struct timeval { time_t tv_sec;//second time_t tv_usec;//minisecond };
若是參數timeout設爲:
NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件。
0:僅檢測描述符集合的狀態,而後當即返回,並不等待外部事件的發生。
特定的時間值:若是在指定的時間段裏沒有事件發生,select將超時返回。
函數返回值:
執行成功則返回文件描述詞狀態已改變的個數,若是返回0表明在描述詞狀態改變前已超過timeout時間,沒有返回;當有錯誤發生時則返回-1,錯誤緣由存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。
理解select模型:
理解select模型的關鍵在於理解fd_set,爲說明方便,取fd_set長度爲1字節,fd_set中的每一bit能夠對應一個文件描述符fd。則1字節長的fd_set最大能夠對應8個fd。
(1)執行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。
(2)若fd=5,執行FD_SET(fd,&set);後set變爲0001,0000(第5位置爲1)
(3)若再加入fd=2,fd=1,則set變爲0001,0011
(4)執行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變爲0000,0011。注意:沒有事件發生的fd=5被清空。
基於上面的討論,能夠輕鬆得出select模型的特色:
可監控的文件描述符個數取決與sizeof(fd_set)的值。
將fd加入select監控集的同時,還要再使用一個數據結構array保存放到select監控集中的fd,一是用於再select 返回後,array做爲源數據和fd_set進行FD_ISSET判斷。二是select返回後會把之前加入的但並沒有事件發生的fd清空,則每次開始 select前都要從新從array取得fd逐一加入(FD_ZERO最早),掃描array的同時取得fd最大值maxfd,用於select的第一個 參數。
可見select模型必須在select前循環array(加fd,取maxfd),select返回後循環array(FD_ISSET判斷是否有時間發生)。
select缺點:
(1)每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大
(2)同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大
(3)select支持的文件描述符數量過小,默認是1024
3.多路複用--poll
poll與select很是類似,不一樣之處在於,select使用三個位圖來表示三種不一樣的事件,而poll使用一個 pollfd的指針實現。
poll函數
#include <poll.h> int poll (struct pollfd *fds, unsigned int nfds, int timeout);
參數說明:
fds:是一個struct pollfd結構類型的數組,其結構以下:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };
該結構用於存放須要檢測其狀態的Socket描述符;每當調用這個函數以後,系統不會清空這個數組,操做起來比較方便;特別是對於 socket鏈接比較多的狀況下,在必定程度上能夠提升處理的效率;這一點與select()函數不一樣,調用select()函數以後,select() 函數會清空它所檢測的socket描述符集合,致使每次調用select()以前都必須把socket描述符從新加入到待檢測的集合中;因 此,select()函數適合於只檢測一個socket描述符的狀況,而poll()函數適合於大量socket描述符的狀況;
nfds:nfds_t類型的參數,用於標記數組fds中的結構體元素的總數量;
timeout:是poll函數調用阻塞的時間,單位:毫秒;若是timeout>0那麼poll()函數會阻塞timeout所指定的毫秒時間長度以後返回。若是timeout==0,那麼 poll() 函數當即返回而不阻塞,若是timeout==INFTIM,那麼poll() 函數會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發 生是才返回,若是感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去;
返回值:
>0:數組fds中準備好讀、寫或出錯狀態的那些socket描述符的總數量;
==0:數組fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,若是所檢測的 socket描述符上沒有任何事件發生的話,
-1: poll函數調用失敗,同時會自動設置全局變量errno;
poll() 函數的功能和返回值的含義與 select() 函數的功能和返回值的含義是徹底同樣的,二者之間的差異就是內部實現方式不同。
4.select實例之網絡服務器(poll實現相似)
服務器端
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/select.h> #include <string.h> #define _MAX_LISTEN_ 5 #define _MAX_SIZE_ 10 #define _BUF_SIZE_ 1024 int fd_arr[_MAX_SIZE_]; int max_fd = -1; static void init_fd_arr() { int i = 0; for(; i < _MAX_SIZE_; ++i) { fd_arr[i] = -1; } } static int add_fd_arr(int fd) { int i = 0; for(; i < _MAX_SIZE_; ++i) { if(fd_arr[i] == -1) { fd_arr[i] = fd; return 0; } } return 1; } static void remove_fd_arr(int fd) { int i = 0; for(; i < _MAX_SIZE_; ++i) { if(fd_arr[i] == fd) { fd_arr[i] = -1; break; } } } static void reload_fd_arr(fd_set* pset) { int i = 0; max_fd = -1; for(; i < _MAX_SIZE_; ++i) { if(fd_arr[i] != -1) { FD_SET(fd_arr[i], pset); if(fd_arr[i] > max_fd) max_fd = fd_arr[i]; } } } static printf_msg(int i, const char* msg) { printf("client %d # %s\n", fd_arr[i], msg); } void Usage(const char* proc) { printf("%s usage: [ip] [port]\n", proc); } int startup(const char* _ip, const char* _port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); exit(1); } int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt"); exit(2); } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(atoi(_port)); local.sin_addr.s_addr = inet_addr(_ip); if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) { perror("bind"); exit(3); } if(listen(sock, _MAX_LISTEN_) < 0) { perror("listen"); exit(4); } return sock; } int main(int argc, char* argv[]) { if(argc != 3) { Usage(argv[0]); return 1; } int listen_sock = startup(argv[1], argv[2]); init_fd_arr(); add_fd_arr(listen_sock); fd_set rfds; FD_ZERO(&rfds); struct timeval tv = {5, 0}; while(1) { reload_fd_arr(&rfds); int fds = select(max_fd+1, &rfds, NULL, NULL, NULL); switch(fds) { case -1: perror("select"); exit(5); break; case 0: printf("time out\n"); break; default: { int index = 0; for(; index < _MAX_SIZE_; ++index) { if(fd_arr[index] == listen_sock && FD_ISSET(fd_arr[index], &rfds)) //new accept { struct sockaddr_in peer; socklen_t len = sizeof(peer); int new_fd = accept(listen_sock, (struct sockaddr* )&peer, &len); if(new_fd < 0) { perror("accept"); exit(6); } printf("get a new client %d -> ip: %s port: %d\n", new_fd, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); if(1 == add_fd_arr(new_fd)) { perror("fd_arr is full\n"); close(new_fd); exit(7); } continue; } if(fd_arr[index] != -1 && FD_ISSET(fd_arr[index], &rfds)) //new read fd { char buf[_BUF_SIZE_]; memset(buf, '\0', sizeof(buf)); ssize_t _s = read(fd_arr[index], buf, sizeof(buf)-1); if(_s > 0) { buf[_s] = '\0'; printf_msg(index, buf); } else if(_s == 0) //client closed { printf("client %d is closed...\n", fd_arr[index]); FD_CLR(fd_arr[index], &rfds); close(fd_arr[index]); // must before remove!!! remove_fd_arr(fd_arr[index]); } else {} } } } break; } } return 0; }
客戶端
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> void Usage(const char* proc) { printf("usage: %s [ip] [port]\n", proc); } int main(int argc, char* argv[]) { if(argc != 3) { Usage(argv[0]); exit(1); } int conn_sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in conn; conn.sin_family = AF_INET; conn.sin_port = htons(atoi(argv[2])); conn.sin_addr.s_addr = inet_addr(argv[1]); if(connect(conn_sock, (const struct sockaddr*)&conn, sizeof(conn)) < 0) { perror("connect"); exit(2); } char buf[1024]; memset(buf, '\0', sizeof(buf)); while(1) { printf("please enter # "); fflush(stdout); ssize_t _s = read(0, buf, sizeof(buf)-1); if(_s > 0) { buf[_s-1] = '\0'; write(conn_sock, buf, strlen(buf)); } } return 0; }
程序演示
使用Telnet測試
使用客戶端測試
使用瀏覽器測試