初始select
- 系统提供select函数来实现多路复用输入/输出模型
- select系统调用是用来监视多个文件描述符的状态变化的
- 程序会停在select这里等待,直到被监视的文件描述符有一个或者多个发生了变化.
select函数原型
- selsect函数原型
#include<sys/select.h> int select(int nfds,fd_set *readfds,fd_set*writefds,fd_set*exceptfds,struct timerval*timeout)
参数解释:
a>nfds:需要监视的最大的文件描述符+1;
b>readfds:可读事件文件描述符集合
c>writefds:可写事件文件描述符集合
d>exceptfds:异常事件文件描述符集合
readfds writefds exceptfds都是输入输出型参数,输入时表示:要关注的文件描述符指定的事件,输出时表示,就绪的文件描述符状态
e>timout:
time>0:等待一段固定时间之后,select() 调用会返回,即使没有一个文件描述符处于I/O就绪状态
timeout=0:非阻塞轮询方式,不断的去检测描述符集合的状态,然后立即返回
timeout=NULL:阻塞的方式等待事件发生
select()调用成功时,返回三个集合中I/O就绪的文件描述符总数;返回0代表在描述符词状态改变前已超过
timeout,没 有返回;出错返回-1
select函数原型中fd_set结构
- fd_set是一个结构体,也就是一个数组,底层是一个位图,位图的每一位表示一个文件描述符的状态,输出时1表示需要监听的文件描述符,输入时1表示就绪的文件描述符.
- 系统提供了一组操作位图的接口.
void FD_CLR(int fd,fd_set* set); //清除描述符词组set中对应的fd位 int FD_ISSET(int fd,fd_set* set); //判断相应的fd位是否为真 void FD_SET(int fd,fd_set* set); //设置描述符词组set中对应的fd位 void FD_ZERO(fd_set *set); //清除描述符词组set的全部位
select函数的特点
- 可监控的文件描述符个数取决于sizeof(fdset)的值.
- 将fd加入select监控的同时,还需要使用一个数据结构array来保存放到select监控的fd
一是因为select返回后,array作为元数据和fdset进行FDISSET判断
二是因为select返回会把以前加入到但并无事件发生的fd清空,则每次开始select都要重新从array取fd逐一加入.
select函数的缺点(相对于poll和epoll而言)
- 每次调用select,都需要手动的设置fd集合,从接口使用角度更方便.
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,开销大
- 在内核需要遍历传递进来的fd,开销大
- 支持的文件描述符太小
select函数的优点(相对于多线程,多进程而言)
- 可移植性好
- 免去了系统调度的开销
select---检测标准输入输出
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/select.h> 4 //系统提供select函数来实现多路转接输入输出模型 5 //int select(int nfds,fd_set *readfds,fd_set * writefds,fd_set * exceptfds,struct timeval*timeout); 6 //fd_set是一个结构体,也就是一个数组,更严格的说是一个"位图",用位图的每一位来标识文件描述符 7 int main(){ 8 while(1){ 9 fd_set read_size; 10 FD_ZERO(&read_size);//将底层的位图全部置0; 11 FD_SET(0,&read_size);//将0号对应的文件描述符置1, 12 //第一个NULL不关注哪些文件写就绪, 13 //第二个NULL不关注哪些文件异常就绪, 14 //第三个NULL,select阻塞形式来执行, 15 int ret = select(1,&read_size,NULL,NULL,NULL); 16 if(ret<0){ 17 perror("select"); 18 return 1; 19 } 20 //执行read的时候,就说明数据都已经准备 好了,直接进行读就可以了. 21 char buf[1024] = {0}; 22 read(0,buf,sizeof(buf)-1); 23 printf("%s\n",buf); 24 } 25 return 0; 26 }
select---网络服务器
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<sys/socket.h> 6 #include<netinet/in.h> 7 #include<arpa/inet.h> 8 #include<sys/select.h> 9 typedef struct sockaddr sockaddr; 10 typedef struct sockaddr_in sockaddr_in; 11 typedef struct FdSet{ 12 fd_set set; 13 int max_fd;//set中最大的文件描述符 14 }FdSet; 15 int ProcessRequest(int new_sock){ 16 //循环从socket中读写数据 17 char buf[1024] = {0}; 18 ssize_t read_size = read(new_sock,buf,sizeof(buf)-1); 19 if(read_size<0){ 20 perror("read"); 21 return -1; 22 } 23 if(read_size==0){ 24 printf("read done!\n"); 25 return 0; 26 } 27 buf[read_size] = '\0'; 28 printf("[client %d] say %s\n",new_sock,buf); 29 write(new_sock,buf,strlen(buf)); 30 return 1; 31 } 32 void FdSetInit(FdSet *fdset){ 33 FD_ZERO(&fdset->set); 34 fdset->max_fd = 0; 35 } 36 void FdSetAdd(FdSet * fdset,int fd){ 37 FD_SET(fd,&fdset->set);//将位图中fd位置1; 38 //维护位图中的最大文件描述符 39 if(fdset->max_fd<fd){ 40 fdset->max_fd = fd; 41 } 42 } 43 void FdSetDel(FdSet *fdset,int fd){ 44 FD_CLR(fd,&fdset->set); 45 //删除有可能删掉最大的文件描述符,所以要时刻维护 46 int max_fd = -1; 47 int i=0; 48 for(i=0;i<=fdset->max_fd;i++){ 49 if(!FD_ISSET(i,&fdset->set)){ 50 continue; 51 } 52 if(i>max_fd){ 53 max_fd = i; 54 } 55 } 56 fdset->max_fd = max_fd; 57 } 58 int Server_Init(char* ip,short port){ 59 //创建监听套接字 60 int _listen_sock = socket(AF_INET,SOCK_STREAM,0); 61 if(_listen_sock<0){ 62 perror("socket"); 63 return -1; 64 } 65 struct sockaddr_in server; 66 server.sin_family = AF_INET; 67 server.sin_addr.s_addr = inet_addr(ip); 68 server.sin_port = htons(port); 69 //需要对服务器进行绑定,使得客户端能够找到自己 70 int ret = bind(_listen_sock,(struct sockaddr*)&server,sizeof(server)); 71 if(ret<0){ 72 perror("bind"); 73 return -1; 74 } 75 76 if(listen(_listen_sock,5)){ 77 perror("listen"); 78 return -1; 79 } 80 return _listen_sock; 81 } 82 int main(int argc,char *argv[]){ 83 if(argc!=3){ 84 printf("usage: ./server [ip] [port]\n"); 85 return 1; 86 } 87 //初始化服务器,创建监听套接字,绑定监听 88 int listen_sock = Server_Init(argv[1],atoi(argv[2])); 89 if(listen_sock<0){ 90 printf("Server_Init failed\n"); 91 return 1; 92 } 93 printf("Server_Init ok!\n"); 94 //把listen_sock添加到对应的文件描述符位图中 95 FdSet input_fds; 96 FdSetInit(&input_fds); 97 FdSetAdd(&input_fds,listen_sock); 98 while(1){ 99 //这里需注意我们必须保证每次循环调用select的时候,传的参数必须包括listen_sock和new_sock,否则,如果listen_sock丢了 100 //就会导致,后续有任何客户端尝试和服务器建立连接,就都处理不了了. 101 //select监视多个文件描述符的变化 102 //所以这里我们用input_fds来保存select输入时的参数 103 //input_fds :添加内容 :有新的客户端链接进来 104 //output_fds:删除内容:客户端断开连接才删除 105 //用output_fds来保存select输出时的参数. 106 FdSet output_fds = input_fds; 107 int ret = select(output_fds.max_fd+1,&output_fds.set,NULL,NULL,NULL); 108 if(ret<0){ 109 perror("select"); 110 continue; 111 } 112 if(FD_ISSET(listen_sock,&output_fds.set)){ 113 //如果有新的客户端建立连接,那么listen_sock就在此位图中读就绪,此时就可以直接调用accept获取到这个链接 114 sockaddr_in client; 115 socklen_t len = sizeof(client); 116 int new_sock = accept(listen_sock,(sockaddr *)&client,&len); 117 if(new_sock<0){ 118 perror("accept"); 119 continue; 120 } 121 FdSetAdd(&input_fds,new_sock); 122 printf("[client %d]\n",new_sock); 123 } 124 //有可能是new_sock就绪也有可能是listen_sock就绪,以下处理new_sock就绪的情况 125 else{ 126 //进行数据的读写 127 int i=0; 128 for(i=0;i<=output_fds.max_fd;i++){ 129 if(!FD_ISSET(i,&output_fds.set)){ 130 continue; 131 } 132 //对当前就绪的进行处理 133 int ret = ProcessRequest(i); 134 //当ret==0时说明客户端已经断开连接(即关闭了socket) 135 if(ret ==0){ 136 close(i); 137 FdSetDel(&input_fds,i); 138 } 139 } 140 141 } 142 } 143 return 0; 144 }