一、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。
