Linux的I/O复用技术:select
select:
select系统调用的用途是:在一段指定时间内,监听用户所感兴趣的文件描述符上的可读、可写和异常事件
缺点:
1.所能监视的文件描述符的数量有限制,sizeof(fd_set)=128,说明能监视的描述符的最大值为128*8=1024个;
2.同时每次调用select都需要在内核遍历传递进来的所有fd,当fd很多时性能会下降;
3.由于当有事件发生时,select返回后会修改三个事件集,所以,每次都需要把fd集合从用户区拷贝到内核区,当需要监视的fd数量增多时,性能会下降;
适用场景:
适用于所监视的文件描述符数量较少的场景。
select系统调用的原型如下:
#include <sys/select.h> int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); /* nfds参数指定被监听的文件描述符的总数。它通常被设置为 select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。 readfds、 writefds和 exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select函数时,通过这3个参数传入自己感兴趣的文件描述符。 select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。 fd_set结构体仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。 fd_set能容纳的文件描述符数量由 FD_SETSIZE指定, 这就限制了select能同时处理的文件描述符的总量,最大值是1024。 应该使用下面的一系列宏来访问 fd_set结构体中的位: FD_ZERO(fd_set *fdset); //清除fdset的所有位 FD_SET(int fd, fd_set *fdset); //设置fdset的位fd FD_CLR(int fd, fd_set *fdset); //清除fdset的位fd int FD_ISSET(int fd, fd_set* fdset); //测试fdset的位fd是否被设置 timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。 timeval结构的定义如下: struct timeval { long tv_sec; //秒数 long tv_usec; // 微秒数 }; 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递NULL,则 select 将一直阻塞,直到某个文件描述符就绪。 select成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0。 select失败时返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。 */ /* 一、文件描述符的就绪条件: 哪些情况下文件描述符可以被认为是可读、可写或者出现异常,对于select的使用非常关键。 在网络编程中,下列情况下socket可读: (1) socket内核接收缓存区中的字节数大于或等于其低水位标记SO_RCVLOWAT,此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。 (2) socket通信的对方关闭连接。此时对该socket的读操作将返回0。 (3) 监听socket上有新的连接请求。 (4) socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。 下列情况下socket可写: (1) socket内核发送缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。 (2) socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。 (3) socket使用非阻塞connect连接成功或者失败(超时)之后。 (4) socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。 */
网络程序中,select 能处理的异常情况只有一种:socket 上接收到带外数据(紧急数据)。
selectserver.cpp:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> int main(int argc, char* argv[]) { if(argc <= 2) { printf("usage: %s ip_address port_number\n", basename(argv[0])); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); printf("ip is %s and port is %d\n", ip, port); int ret = 0; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); int listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(listenfd >= 0); ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); ret = listen(listenfd, 5); assert(ret != -1); struct sockaddr_in client_address; socklen_t client_addrlength = sizeof(client_address); int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); if (connfd < 0) { printf("errno is: %d\n", errno); close(listenfd); } char buf[1024]; fd_set read_fds; fd_set exception_fds; FD_ZERO(&read_fds); FD_ZERO(&exception_fds); while(1) { memset(buf, '\0', sizeof(buf)); //每次调用select前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生之后,文件描述符集合将被内核修改。 FD_SET(connfd, &read_fds); FD_SET(connfd, &exception_fds); ret = select(connfd+1, &read_fds, NULL, &exception_fds, NULL); printf("select one\n"); if (ret < 0) { printf( "selection failure\n" ); break; } if (FD_ISSET(connfd, &read_fds)) //对于可读事件,采用普通的recv函数读取数据 { ret = recv(connfd, buf, sizeof(buf)-1, 0); if(ret <= 0) { break; } printf("get %d bytes of normal data: %s\n", ret, buf); } else if(FD_ISSET(connfd, &exception_fds)) //对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据 { ret = recv( connfd, buf, sizeof(buf)-1, MSG_OOB); if(ret <= 0) { break; } printf("get %d bytes of oob data: %s\n", ret, buf); } } close(connfd); close(listenfd); return 0; }
selectclient.cpp:
#include<sys/types.h> #include<sys/msg.h> #include<sys/ipc.h> #include<sys/stat.h> #include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<iostream> #include<sys/wait.h> #include<sys/socket.h> #include<sys/epoll.h> #include<sys/ipc.h> #include<errno.h> #include<sys/shm.h> #include<fcntl.h> #include<semaphore.h> #include<arpa/inet.h> #include<iostream> #include<assert.h> #include<ctype.h> #include<time.h> using namespace std; #define MS(a,b) memset(a,b,sizeof(a)) int main(int argc, char* argv[]) { if (argc <= 2) { printf("argc error!\n"); return -1; } const char* ip = argv[1]; int port = atoi(argv[2]); int ret = 0; struct sockaddr_in addr; bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; inet_pton(AF_INET, ip, &addr.sin_addr); addr.sin_port = htons(port); int listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(listenfd >= 0); printf("select client fd: %d\n", listenfd); ret = connect(listenfd, (struct sockaddr*)&addr, sizeof(addr)); assert(ret != -1); while(1) { char buf[128] = {0x00}; int i = 0,ret; sprintf(buf, "hanyufengloveliuyifei: %d",++i); ret = send(listenfd, buf, strlen(buf), 0); printf("ret: %d\n", ret); assert(ret >= 0); MS(buf,0); ssize_t len = recv(listenfd, buf, sizeof(buf)-1, 0); assert(len >= 0); printf("select client recv data: [%s]\n", buf); sleep(2); } return 0; }