系统提供select函数来实现多路复用输入/输出模型。
作用:select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。
函数原型:
参数说明:
int nfds:需要监视的最大文件描述符值+1;
fd_set *readfds & *writefds & *exceptfds:指向文件描述符的指针;这三个描述符集说明了我们关心的可读、可写或处于异常条件的各个描述符;
struct timeval *timeout:需要等待的时间,为NULL,则select一直阻塞,知道某个文件描述符发生了事件,为0,仅仅检测描述集合的状态,立即返回,不等待外部事件的发生。
struct timeval{ long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
函数返回值:
成功:返回就绪描述符的个数,0表示timeout结束,没有描述符完成就绪;
失败:-1,此时状态描述参数和timeout都变成不可预测的,错误信息存在errno中。
对fd_set数据类型可以进行的处理是:
分配一个这种类型的变量;
将这种类型的一个变量值赋予同类型的另一个变量;或对于这种类型的变量使用下列四个函数中的一个。
void FD_CLR(int fd, fd_set *set); //清除描述词组set中相关的fd的位 int FD_ISSET(int fd, fd_set *set); //测试描述词组set中相关fd的位是否为真 void FD_SET(int fd, fd_set *set); //设置描述词组set中相关的fd位 void FD_ZERO(fd_set *set); //清除描述词组set的全部位
代码:
#include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/select.h> #include <netinet/in.h> int fds[128]; const int len = 128; static void usage(const char* proc) { printf("Usage: %s [ip] [port]\n",proc); } int startup(const char *_ip, int _port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if( sock < 0){ perror("socket"); exit(2); } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(_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, 5) < 0){ perror("listen"); exit(4); } return sock; } int main(int argc, char *argv[]) { if(argc != 3){ usage(argv[0]); exit(1); } int i = 0; for(; i < len; i++){ fds[i] = -1; } int listen_sock = startup(argv[1], atoi(argv[2])); //FD_SET(listen_soxk, &rfds); //int max_fd = listen_sock; fd_set rfds;//read fd set; fds[0] = listen_sock; int done = 0; while(!done){ int max_fd = -1; FD_ZERO(&rfds); for(i = 0; i < len; i++){ if( fds[i] != -1){ FD_SET(fds[i], &rfds); } max_fd = fds[i] > max_fd?fds[i]:max_fd; } struct timeval timeout = {5, 0}; switch(select(max_fd+1, &rfds, NULL, NULL, NULL)){ case 0: printf("timeout\n"); break; case -1: perror("select"); break; default: { if( FD_ISSET(listen_sock, &rfds) ){ struct sockaddr_in peer; socklen_t len = sizeof(peer); int new_fd = accept(listen_sock, (struct sockaddr *)&peer, &len); if(new_fd > 0){ printf("get a new client-> %s:%d\n",inet_ntoa(peer.sin_addr), ntohs(peer.sin_port)); for(i - 0; i < len; i++){ if(fds[i] == -1){ fds[i] = new_fd; break; } } if( i == len){ close(new_fd); } } }else{ char buf[1024]; for( i = 0; i < len; i++){ if( i != -1 && FD_ISSET(fds[i], &rfds) ){ ssize_t _s = read(fds[i], buf, sizeof(buf)); if(_s > 0){ buf[_s] = ‘\0‘; printf("client %d is closed...\n", fds[i]); close(fds[i]); fds[i] = -1; }else{ perror("read"); } } } } } break; } } return 0; }
select的缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
select的优点:
(1)相较于之前多线程的方法,使用select不用创建线程,更方便
(2)select目前几乎在所有的平台上都支持,其良好跨平台支持也是它的一个优点
本文出自 “七百七十七快” 博客,谢绝转载!
原文地址:http://10324228.blog.51cto.com/10314228/1837181