man select:
#include <sys/select.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the
file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is
considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without block‐
ing.
最后一个参数,他告知内核等待所指定描述字中的任何一个就绪可花多长时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
这里第一个域的单位为秒,第二个域的单位为微秒。
nfds
需要检查的文件描述字个数(即检查到fd_set 的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset, writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查 fd_set的所有1024位。
readset
来检查可读性的一组文件描述字。
writeset
用来检查可写性的一组文件描述字。
exceptset
用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout
有三种可能:
1. timeout="NULL"(阻塞:直到有一个fd位被置为1函数才返回)
2. timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
select函数作用:在timeout时间内,不断测试不超过nfds 的所有fd,对于每一个接受到外部事件的fd, 将其在fd_set中的位置置1, 其余的没有接收到外部条件的fd位置置0,通外接下来的FD_ISSET进行测试,找到满足条件的所有fd。所以每次在调用select函数之前,要重新对fd_set进行赋值。
select函数返回值:
如果在timeout时间内,有fd满足条件,返回对应位仍然为1的fd的总数。
如果timeout时间用完了,也没有fd接收到外部事件,则返回0
出错的情况返回负数。
四个宏来操作: 完全一点 从accept开始.
fd_set set;
FD_ZERO(&set); /* 将set清零使集合中不含任何fd*/
FD_SET(fd, &set); /* 将fd加入set集合 */
FD_CLR(fd, &set); /* 将fd从set集合中清除 */
FD_ISSET(fd, &set); /* 测试fd是否在set集合中*/
过去,一个fd_set通常只能包含<32的fd(文件描述 字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量 FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实 现,我们可以重新理解操作fd_set的四个宏:
fd_set set;
FD_ZERO(&set); /*将set的所有位置0,如set在内存中占8位则将set置为
00000000*/
FD_SET(0, &set); /* 将set的第0位置1,如set原来是00000000,则现在变为10000000,这样fd==1的文件描述字就被加进set中了 */
FD_CLR(4, &set); /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了 */
FD_ISSET(5, &set); /* 测试set的第5位是否为1,如果set原来是10000100,则返回非零,表明fd==5的文件描述字在set中;否则返回0*/
在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。
例子:
main()
{
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0
char buffer[256]={0}; //256字节的接收缓冲区
while(1)
{
FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sock,&fds); //添加描述符
FD_SET(fp,&fds); //同上
maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用
{
case -1: exit(-1);break; //select错误,退出程序
case 0:break; //再次轮询
default:
if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
{
recvfrom(sock,buffer,256,.....);//接受网络数据
if(FD_ISSET(fp,&fds)) //测试文件是否可写
fwrite(fp,buffer...);//写入文件
buffer清空;
}// end if break;
}// end switch
}//end while
}//end main
参考:
http://blog.sina.com.cn/s/blog_5c8d13830100pwaf.htm
将标准输入keyboard作为fd加入到fd_set中去。
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #include <stdlib.h> #include <fcntl.h> #include <assert.h> using namespace std; int main () { int keyboard; int ret,i; char c; fd_set readfd; struct timeval timeout; keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK); assert(keyboard>0); printf("fd_set size = %d ", sizeof(fd_set)); FD_ZERO(&readfd); while(1) { timeout.tv_sec=2; timeout.tv_usec=0; FD_SET(keyboard,&readfd); ret=select(keyboard+1,&readfd,NULL,NULL,&timeout); //ret=select(keyboard+1,&readfd,NULL,NULL,NULL); printf("FD_ISEET before = %d, tv_sec =%d, tv_usec =%d", FD_ISSET(keyboard, &readfd), timeout.tv_sec, timeout.tv_usec); if(FD_ISSET(keyboard,&readfd)) { read(keyboard,&c,1); if(' '== c) continue; printf("hehethe input is %c ",c); if ('q'==c) break; } } }
例子2:
使用select函数可以以非阻塞的方式和多个socket通信。程序只是演示select函数的使用,功能非常简单,即使某个连接关闭以后也不会修改当前连接数,连接数达到最大值后会终止程序。
1. 程序使用了一个数组fd_A,通信开始后把需要通信的多个socket描述符都放入此数组。
2. 首先生成一个叫sock_fd的socket描述符,用于监听端口。
3. 将sock_fd和数组fd_A中不为0的描述符放入select将检查的集合fdsr。
4. 处理fdsr中可以接收数据的连接。如果是sock_fd,表明有新连接加入,将新加入连接的socket描述符放置到fd_A。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MYPORT 1234 // the port users will be connecting to #define BACKLOG 5 // how many pending connections queue will hold #define BUF_SIZE 200 int fd_A[BACKLOG]; // accepted connection fd int conn_amount; // current connection amount void showclient() { int i; printf("client amount: %d ", conn_amount); for (i = 0; i < BACKLOG; i++) { printf("[%d]:%d ", i, fd_A[i]); } printf(" "); } int main(void) { int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd struct sockaddr_in server_addr; // server address information struct sockaddr_in client_addr; // connector's address information socklen_t sin_size; int yes = 1; char buf[BUF_SIZE]; int ret; int i; if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } server_addr.sin_family = AF_INET; // host byte order server_addr.sin_port = htons(MYPORT); // short, network byte order server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP memset(server_addr.sin_zero, '