I/O多路复用之select函数
来源:互联网 发布:无线游戏鼠标 知乎 编辑:程序博客网 时间:2024/06/04 19:51
select介绍
我们先来看一下select的接口。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
从上面的这些接口我们应该能有写认识,首先我么来看select系统调用的参数的含义。
值得注意的是,后面的三个参数即是输入型参数,又是输出型参数,输入表示关心那些文件描述符对应的特定事件发生。输出表示的是那些文件描述符对应的事件就绪。当就绪后,内核将会去修改这些文件描述符的集合。这三个参数都是fd_set结构体类型
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以看出fd_set就是一个结构体数组,这个结构体数组的每一位就是一个文件描述符的标记,配套提供了一些对于fd_set操作的宏。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
最后要说一下的就是timeout参数,这个参数用来设置超时时间,它也是一个结构体,它用来告诉应用程序select等待多久,这里的单位是微秒级别的。
select调用时内核级别的,select的轮询方式是和非阻塞轮询方式是不同的,select的轮询方式是同时可以对多个I/O端口进行监听,任何一个端口数据好了,这个时候就可以读了。然后通过系统调用,就可以把数据从内核拷贝到用户进程。
select缺点
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看,有宏FD_SETSIZE进行限制fd的数量。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
select代码示例:
#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<string.h>#include<sys/time.h>static void Usage(const char *str){printf("usage: %s [server_ip][server_port]\n",str);}static int startup(const char *ip,int port){int new_socket = socket(AF_INET,SOCK_STREAM,0);if(new_socket < 0){perror("socket");exit(2);}int op = 1;int ret = setsockopt(new_socket,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(op));if(ret < 0){perror("setsockopt");exit(3);}struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);serv_addr.sin_addr.s_addr = inet_addr(ip);ret = bind(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr));if(ret < 0){perror("bind");exit(4);}ret = listen(new_socket,128);if(ret < 0){perror("listen");exit(5);}return new_socket;}int array_fds[1024]; //定义一个全局数组。这个全局变量存放的是文件描述符。int max_fd; //最大的文件描述符。int main(int argc ,char *argv[]){if(argc != 3){Usage(argv[0]);exit(1);}int i = 0;for(; i < 1024; ++i)array_fds[i] = -1; //将数组中每一个元素都置为-1.int listen_sock = startup(argv[1],atoi(argv[2]) );array_fds[0] = listen_sock; //将监听套件字放在数组中0号位置。fd_set reads; //创建只读集合。fd_set writes; //创建只写集合。struct timeval timeout; while(1) //服务器一直处于服务状态。{FD_ZERO(&reads); //因为select是输入输出参数,当select返回时,已经改变了read这个集合了,下次还需要监听read这个集合中的可读事件的话就需要重新赋值。FD_ZERO(&writes); //初始化。max_fd = -1; //每次这个得重新初始化timeout.tv_sec = 10; //定时10秒,这个参数也是输入输出参数timeout.tv_usec = 0;for(i = 0; i < 1024; ++i){if(array_fds[i] >= 0){FD_SET(array_fds[i], &reads); //将监听套接字加入到可读事件中。FD_SET(array_fds[i],&writes);//将监听套接字加入到可写事件中。if(array_fds[i] > max_fd)max_fd = array_fds[i];}}//准备工作做好后,开始真正的监听了。int j = 0;switch(select(max_fd+1 ,&reads,&writes,NULL,&timeout)){case 0:printf("time out....\n");break;case -1:perror("select");exit(6);default: //有可读事件发生,但是不知道是那一个可读事件,需要遍历数组,查看数组中存放的描述符那一个可读了。for(; j < 1024; ++j){if(array_fds[j] < 0) //-1表示这个文件描述符没有可读事件发生。continue;char buf[BUFSIZ]; //接收数据缓冲区。if(j== 0 && FD_ISSET(array_fds[0],&reads)) //监听套接字有可读事件发生,表示有客户连接了。{struct sockaddr_in clie_addr;socklen_t len = sizeof(clie_addr);int connect_fd = accept(array_fds[0],(struct sockaddr*)&(clie_addr),&len);if(connect_fd < 0){perror("accept");continue; //这次连接失败,让它下次连接。}printf("get a new client :(%s:%d)\n",inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port));//连接套接字后继续监听,看这个套接字是不是有数据发送。int k = 0;for(; k < 1024; ++k){if(array_fds[k] == -1){array_fds[k] = connect_fd;break;}}}else if(j != 0 && FD_ISSET(array_fds[j],&reads)){printf("======================read start==========\n");ssize_t s = read(array_fds[j],buf,sizeof(buf) - 1);if(s < 0){perror("read");close(array_fds[j]);array_fds[j] = -1; //数组重新利用break;}else if(s == 0){printf("clinet quit\n");close(array_fds[j]);array_fds[j] = -1; //数组重新利用break;}else{buf[s] = 0;printf("clinet say:%s\n",buf);//if(FD_ISSET(array_fds[j],&writes))//{//write(array_fds[j],buf,strlen(buf));//}}}// if(j != 0 && FD_ISSET(array_fds[j],&writes)) //套接字可写事件满足,说明可以将套接字中的数据发送出去了。//{//printf("-------------------write j = %d ----------------start\n",j);//sleep(1);//write(array_fds[j],buf,strlen(buf));//}}//for结束} //switch结束} //while(1)死循环return 0;}
#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<string.h>#include<sys/time.h>#include<fcntl.h>#include<sys/stat.h>static void Usage(const char *str){printf("usage: %s [server_ip][server_port]\n",str);}int main(int argc, char*argv[] ){if(argc != 3){Usage(argv[0]);exit(1);}int new_socket = socket(AF_INET,SOCK_STREAM,0);if(new_socket < 0){perror("socket");exit(2);}struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(atoi (argv[2]) );serv_addr.sin_addr.s_addr = inet_addr(argv[1]); int ret = connect(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr));if(ret < 0){perror("connect");exit(3);}char buf[BUFSIZ];while(1){printf("please enter#:");fflush(stdout);ssize_t s = read(0,buf,sizeof(buf) - 1);if(s > 0){int outfd = dup(1); //outfd 指向标志输出(保存标准输出好恢复)。buf[s-1] = 0; //去掉换行符。//write(new_socket,buf,strlen(buf));dup2(new_socket, 1); //1号文件描述符去指向new_socket指向的内容了。printf("%s",buf); //本来是将buf中的东西写入到标准输出中,但是现在1号描述符已经重新定向了,指向套接字的缓冲区,所以现在就是讲buf中的东西写入到套接字缓冲区了。fflush(stdout);dup2(outfd, 1); //让1重新指向标准输出。}elsebreak;//ssize_t s2 = read(new_socket,buf,sizeof(buf) - 1);//buf[s2] = 0;//printf("sever echo # %s\n",buf);}close(new_socket);return 0;}
阅读全文
0 0
- I/O多路复用之select函数分析
- I/O多路复用之select函数
- select函数:I/O多路复用
- I/O多路复用- select函数
- I/O多路复用之select
- I/O 多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O 多路复用之select
- socket I/O多路复用--select函数
- I/O多路复用select
- 多路复用I/O--select
- scrollview 嵌套gridview 和 listview 会有冲突
- Spring RestTemplate关联HttpClient4.5的配置HttpClient和自身的BUG
- maven项目读取resources目录下文件
- 通过反射得到一个类的对象,如何对该对象对应的类的父类中的变量赋值
- Android多分辨率布局,多个layout
- I/O多路复用之select函数
- Python数据采集之Scrapy框架
- 嵌入式Linux中的根文件系统
- 三种广播,以及广播接收器的注册方式
- eclipse设置Ctrl+H打开File Search
- hdoj1067 Gap(bfs+hash)
- 阿里巴巴最新面试经验-天猫进出口
- 气体传感器2
- mysql客户端调用load data infile报Error Code: 1045. Access denied for user错误