https://blog.csdn.net/men_wen/article/details/53456435
Linux網絡編程—I/O復用模型之select
1. IO復用模型
- IO復用能夠預先告知內核,一旦發現進程指定的一個或者多個IO條件就緒,它就通知進程。
- IO復用阻塞在select或poll系統調用上,而不是阻塞在真正的IO系統調用上。
2. 函數select
select函數能夠告知內核對哪些描述符(不局限于套接字)感興趣以及等待多長事件
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//返回值:返回就緒描述符數目,若超時則為0, 若出錯則為-1
- timeout用來指定內核等待所指定描述符的任何一個就緒花多長事件。timeval結構用于指定這段事件的秒數和微妙數
struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */
};
//當timeout為NULL,則永遠等待。
//當timeout為timeval,等待固定時間。
//當timeout為timeval,但timeval時間設置為0,則檢查描述符字立即返回,稱為輪詢。
- 中間的三個參數readfds、writefds、exceptfds指定內核測試讀、寫、異常條件的描述符,這三個參數都是fd_set結構的指針類型,fd_set結構實現如下:
#define __FD_SETSIZE 1024typedef struct {unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
select函數使用描述符集,通常是一個整數數組,其中每一個整數中的每一位對應一個描述符。如何操作這些描述符則系統提供了四個宏
void FD_CLR(int fd, fd_set *set);
//把文件描述符集合里fd清零
int FD_ISSET(int fd, fd_set *set);
//測試文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);
//把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set);
//把文件描述符集合里所有位清0
- 如果對應哪一個條件不感興趣,則可以將它設置為空指針。
- nfds參數指定待測試的描述符個數,它的值是待測試的最大描述符加1,描述符0到nfds-1都會被測試。
- select能監聽的文件描述符個數受限于FD_SETSIZE,一般為1024,單純改變進程打開的文件描述符個數并不能改變select監聽文件個數。解決1024以下客戶端時使用select是很合適的,但如果鏈接客戶端過多,select采用的是輪詢模型,會大大降低服務器響應效率。
- select函數修改由指針readfds、writefds、exceptfds指向的描述符集,調用函數時,我們指定所關心的描述符的值,函數返回時,結果將指示哪些描述符已就緒,函數返回后,使用FD_ISSET宏來測試fd_set數據類型中的描述符,描述符集內任何與未就緒描述符對應的位均清成0.為此,每次重新調用select函數時都要將描述符集內所關心的位置為1。
3. select模型實現
3.1 服務器端
#include "wrap.h"#define MAXLINE 1024int start_ser(char *ipaddr, char *port)
{int sock = Socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(port));inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));Listen(sock, 128);return sock;
}int main(int argc, char *argv[])
{int i, maxi, maxfd, listenfd, connfd, sockfd;int nready, client[FD_SETSIZE];ssize_t n;fd_set readset, allset;char buf[MAXLINE];socklen_t clilen;struct sockaddr_in clientaddr;listenfd = start_ser(argv[1], argv[2]); //監聽文件描述符maxfd = listenfd;//最大的文件描述符maxi = -1;//數組中最大文件描述符下標for(i = 0; i < FD_SETSIZE; i++){client[i] = -1;}FD_ZERO(&allset);//初始化allset集合FD_SET(listenfd, &allset);//添加監聽文件描述符到集合allset中while(1){readset = allset;//每次select都要初始化集合,結構體可以直接賦值nready = select(maxfd+1, &readset, NULL, NULL, NULL);//組攝等待連接或請求if(FD_ISSET(listenfd, &readset)){//當監聽文件描述符相應有新的客戶端連接時clilen = sizeof(clientaddr);connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);//接收該客戶端if(connfd < 0){perr_exit("accept err");break;}for(i = 0; i < FD_SETSIZE; i++){if(client[i] < 0){client[i] = connfd; //保存文件描述符到數組break;}}if(i == FD_SETSIZE){ //連接個數不能大于內核規定的FD_SETSIZEperr_exit("too many clients");}FD_SET(connfd, &allset);//添加新描述符到allset集合中if(connfd > maxfd){maxfd = connfd; //更新最大文件描述符}if(i > maxi){maxi = i; //更新最大文件描述符下標}if(--nready == 0){ //只有一個listenfd響應則直接跳過下面的語句continue;}}for(i = 0; i <= maxi; i++){ //處理已連接客戶端的請求if((sockfd = client[i]) < 0){continue;}if(FD_ISSET(sockfd, &readset)){ //sockfd是否在readset集合中memset(buf, '\0', MAXLINE);if((n = Read(sockfd, buf, MAXLINE-1)) == 0){Close(sockfd);FD_CLR(sockfd, &allset);client[i] = -1;}else{printf("client:%s\n", buf);}if(--nready == 0){//判斷是否查找完break;}}}}return 0;
}
3.2 客戶端
#include "wrap.h"int main(int argc, char *argv[])
{int connfd;struct sockaddr_in serveraddr;char buf[1024];connfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));while(fgets(buf, 1024, stdin) != NULL){Write(connfd, buf, strlen(buf));}Close(connfd);return 0;
}