一、select介绍
函数原型:
#include <sys/select.h> int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr); // 返回值:准备就绪的描述符数,若超时返回0,若出错返回-1
参数说明:
maxfd:是需要监视的最大的文件描述符值+1;readfds/writefds/exceptfds分别对应需要检测的可读文件描述符的集合、可写描述符集合以及异常描述符集合。
tvptr:说明愿意等待多久。
有三种情况:
tvptr=NULL 永远等待。
tvptr->tv_sec==0 && tvptr->tv_usec=0 完全不等待。
tvptr->tv_sec!=0 || tvptr->tv_usec != 0 等待指定时间。若超时,则返回0。
POSIX允许实现中修改tvptr的值,所以在每次select开始时都需要重新设置该值。
对fd_set类型的处理有四个专有函数:
#include <sys/select.h> int FD_ISSET(int fd, fd_set *fdset); // 测试某一个描述符 返回值:若fd在描述符集中则返回非0,否则返回0 void FD_CLR(int fd, fd_set *fdset); // 删除某一个描述符 void FD_SET(int fd, fd_set *fdset); // 设置某一个描述符 void FD_ZERO(fd_sete *fdset); // 清空
返回值:
select有三个可能的返回值:
返回-1表示出错。
返回0表示没有描述符准备好。
返回正值表示已经准备好的描述符数。该值是三个描述符集中已准备好的描述符的和。
select函数的中间三个参数的任意一个或全部都可以为NULL。如果三个都是NULL,则select提供一个高精度的计时器。
二、select使用
示例1:回显服务器
/******************************************************************************* * File Name : select.cpp * Author : zjw * Email : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode) * Create Time : 2015年07月15日 星期三 11时52分07秒 *******************************************************************************/ #include <sys/types.h> #include <sys/socket.h> #include <sys/select.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> #include <map> using namespace std; const int SERVER_PORT = 8080; const int FD_SIZE = 1024; const int RECV_SIZE = 1024; const int SEND_SIZE = 1040; int main(int argc, char **argv) { int server = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr_server; addr_server.sin_family = AF_INET; addr_server.sin_port = htons(SERVER_PORT); addr_server.sin_addr.s_addr = INADDR_ANY; memset(addr_server.sin_zero, 0, sizeof(addr_server.sin_zero)); if (bind(server, (struct sockaddr*)&addr_server, sizeof(addr_server))) { perror("bind failed!"); return -1; } listen(server, 5); fd_set fdsr; int fd[FD_SIZE] = { 0 }; int maxsock = server; char recvBuf[RECV_SIZE + 1]; char sendBuf[SEND_SIZE + 1]; map<int, sockaddr_in> mapClients; while (1) { FD_ZERO(&fdsr); FD_SET(server, &fdsr); struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; for (int i = 0; i < 1024; i++) { if (fd[i] != 0) { FD_SET(fd[i], &fdsr); } } int ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv); if (ret == -1) { perror("select error!"); return -1; } else if (ret == 0) { cout << "timeout!" << endl; continue; } // check every fd in the fdset for (int i = 0; i < FD_SIZE; i++) { if (FD_ISSET(fd[i], &fdsr)) { memset(recvBuf, 0, RECV_SIZE + 1); memset(sendBuf, 0, SEND_SIZE + 1); ret = recv(fd[i], recvBuf, RECV_SIZE, 0); sprintf(sendBuf, "Your said:%s", recvBuf); send(fd[i], sendBuf, SEND_SIZE, 0); if (ret <= 0) { cout << "client " << fd[i] << " close" << endl; mapClients.erase(fd[i]); close(fd[i]); FD_CLR(fd[i], &fdsr); fd[i] = 0; } else { cout << "client " << fd[i] << " ip[" << inet_ntoa(mapClients[fd[i]].sin_addr) << "]" << " said:" << recvBuf << endl; } } } // check server socket if (FD_ISSET(server, &fdsr)) { int client; sockaddr_in addr_client; socklen_t len; client = accept(server, (struct sockaddr*)&addr_client, &len); if (client == -1) { perror("accept error!"); return -1; } for (int i = 0; i < FD_SIZE; i++) { if (fd[i] == 0) { fd[i] = client; mapClients.insert(make_pair<int, sockaddr_in>(client, addr_client)); break; } } } maxsock = server; for (int i = 0; i < FD_SIZE; i++) { if (fd[i] > maxsock) { maxsock = fd[i]; } } } return 0; }
Makefile:
echo: select.cpp g++ -o $@ $< clean: rm -rf echo
执行结果:
server端:echo
client端:telnet
提示:使用telnet测试非常方便,但是退出telnet有点麻烦,输出ctrl+],然后输入quit即可退出。
示例2:定时器
/******************************************************************************* * File Name : timer.cpp * Author : zjw * Email : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode) * Create Time : 2015年07月21日 星期二 16时45分16秒 *******************************************************************************/ #include <sys/select.h> #include <unistd.h> #include <iostream> using namespace std; int main(int argc, char **argv) { struct timeval tv; while (1) { tv.tv_sec = 5; tv.tv_usec = 0; select(0, NULL, NULL, NULL, &tv); cout << "Five second have passed." << endl; } return 0; }
三、参考
http://blog.csdn.net/turkeyzhou/article/details/8609360
四、疑问
select是如何判断每个描述符可读、可写或者有异常的?
每个描述符没有设置为非阻塞的,那么如果select使用recv来尝试读,它是如何做到不阻塞的?或者说,select将这些描述符设置为非阻塞的了?还是它又另外的方法来测试可读?
select为什么有最大连接限制?不理解所谓的FD_SETSIZE。