I/O多路转接----select
2017-06-30 16:36
281 查看
我们知道系统IO中有五种模型:阻塞I/O, 非阻塞I/O,信号驱动I/O,以及多路复用(select,poll,epoll)这四种是同步模型,还有一种是异步I/O模型;在I/O当中,我们知道它包含两件事情,一件事叫做等,另一件就是数据搬迁;其中这五种模型就是对事件进行等的操作;其中,多路复用(多路转接)中的三种服务器是最高效的等,所以,我们来对他们进行一个研究:
其中最后四个即是输入型参数也是输出型参数,四个函数的输入与输出的含义不一样:输入表示关心那些文件描述符对应的特定事件发生;输出表示那些所关心的那些文件描述符已经就绪;
select()对于超时值提供了更好的精度:微秒,而poll是毫秒
同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select支持的文件描述符数量太小了,默认是1024;
select介绍
1、select函数介绍int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 第一个参数nfds:所有文件描述符中最大值加1; 第二个参数readfds:可读事件的文件描述符集合; 第三个参数writefds:可写事件的文件描述符集合; 第四个参数exceptfds:异常事件的文件描述符集合; 第五个参数timeout:设定超市时间;
其中最后四个即是输入型参数也是输出型参数,四个函数的输入与输出的含义不一样:输入表示关心那些文件描述符对应的特定事件发生;输出表示那些所关心的那些文件描述符已经就绪;
三种事件处理的宏函数
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的全部位
timeout参数介绍:
NULL: 则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件; 0: 仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生; 特定的时间值:如果在指定的时间段里没有发生事件,则超时返回;
select服务器端的实现
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/select.h> int fds_array[sizeof(fd_set)*8]; static void usage(const char* proc) { printf("Usage: %s[local_ip] [local_port]\n", proc); } int startup(const char *_ip, int _port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); return 3; } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//解决server bind error的问题; 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"); return 4; } if(listen(sock, 10) < 0) { perror("listen"); return 5; } return sock; } int main(int argc, char *argv[]) { if(argc != 3) { usage(argv[0]); return 1; } int listen_sock = startup(argv[1], atoi(argv[2])); int nums = sizeof(fds_array)/sizeof(fds_array[0]); int i = 0; for(;i < nums; i++)//将所有元素设置为无效 { fds_array[i] = -1; } fds_array[0] = listen_sock; //将listen_sock加入到数组中; while(1) { int max_fd = -1; fd_set rfds; //创建文件描述符集合 FD_ZERO(&rfds); //将所有的元素清空 for(i = 0; i < nums; i++) { if(fds_array[i] == -1) { continue; } FD_SET(fds_array[i], &rfds); if(fds_array[i] > max_fd) //寻找select中第一个参数; max_fd = fds_array[i]; } struct timeval timeout = {5,0}; switch(select(max_fd+1, &rfds, NULL, NULL, /*&timeout*/NULL)) { 4000 case -1: { perror("select"); } break; case 0: { printf("time out\n"); } break; default : { for(i = 0; i < nums; ++i) { if(fds_array[i] < 0) continue; if(i == 0 && FD_ISSET(fds_array[i], &rfds)) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len); if(new_sock < 0) { perror("accept"); continue; } printf("new client: [%s:%d]\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); int j = 1; for(; j < nums; j++) //找到第一个无效的位置 { if(fds_array[j] < 0) break; } if(j == nums) { printf("server is full\n"); close(new_sock); break; } else { fds_array[j] = new_sock; } } else if(i != 0 && FD_ISSET(fds_array[i], &rfds)) { char buf[1024]; ssize_t s = read(fds_array[i], buf, sizeof(buf)-1); if(s > 0) { buf[s] = 0; printf("client #:%s", buf); } else if(s == 0) { printf("client quit...\n"); close(fds_array[i]); fds_array[i] = -1; } else { perror("read"); close(fds_array[i]); return 7; } } } } break; } } close(listen_sock); return 0; }
利用一个dup实现客户端
#include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <sys/types.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> static void usage(const char* proc) { printf("Usage: %s[local_ip] [local_port]\n", proc); } int main(int argc, char *argv[]) { if(argc != 3) { usage(argv[0]); return 1; } int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr= inet_addr(argv[1]); server.sin_port = htons(atoi(argv[2])); socklen_t len = sizeof(struct sockaddr_in); if(connect(sock, (struct sockaddr *)&server, len) < 0) { perror("connect"); return 2; } printf("connect success...\n"); char buf[1024]; close(1); dup(sock); while(1) { // printf("send###"); // fflush(stdout); ssize_t _r = read(0, buf, sizeof(buf)-1); if(_r > 0) { buf[_r] = 0; printf("%s\n", buf); fflush(stdout); } // write(1, buf, strlen(buf)); } close(sock); return 0; }
select的优缺点
优点
select()的可移植性更好,在某些UNIX系统上不支持pollselect()对于超时值提供了更好的精度:微秒,而poll是毫秒
缺点
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select支持的文件描述符数量太小了,默认是1024;
相关文章推荐
- I/O多路转接(select、pselect和poll)使用
- 【Linux】I/O多路转接select
- linux 多路转接之select
- 高级I/O之多路转接select
- I/O多路转接之select
- apue I/O多路转接,select与poll
- linux的I/O多路转接select的fd_set数据结构和相应FD_宏的实现分析
- 多路复用 I/O 多路转接 select / poll / epoll
- I/O多路转接(一)之select_server
- Linux【网络编程】——I/O多路转接之Select服务器
- Linux下I/O多路转接之select --fd_set
- Linux的I/O多路转接模型和select()
- 服务器与客户端的模型之select的多路转接
- 高级I/O之I/O多路转接——pool、select
- 多路I/O转接之select模型
- UNIX环境高级编程——I/O多路转接(select、pselect和poll)
- I/O的多路转接: select
- 多路转接之poll和select
- 多路I/O转接之select模型
- I/O多路转接之select