我们先来看一段阻塞的read
代码:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
int fd;
char buf[11];
int ret;
fd=0;
while(1){
//set the buf to 0
memset((void *)buf,0,11);
ret=read(fd,(void *)buf,10);
printf("ret=%d\n",ret);
if(ret!=-1){
buf[10]='\0';
printf(" buf=%s\n",buf);
}
}
return 0;
}
我们先用memset
初始化数组:
void *memset(void *s, int c, size_t n);
DESCRIPTION
The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.
也就是说,我们把buf
全部设置为0。
然后使用read
函数:
ssize_t read(int fd, void *buf, size_t count);
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than
the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are
reading from a pipe, or from a terminal), or because read() was interrupted by a signal. On error, -1 is returned, and errno is set appropriately. In this case it is left
unspecified whether the file position (if any) changes.
read
会从文件描述符fd
中读取内容,并读取count
个字节,并将内容读到buf
中。
它会返回读到的字节数。如果是0,则是读到末尾了。如果是-1,那就是出错了。
在没有出错的情况下,我们打印buf
的内容(读取到的内容)。
'\0'
标志着字符串的结尾。
我们运行该程序:
I love linux programming
ret=10
buf=I love lin
ret=10
buf=ux program
ret=5
buf=ming
我们发现,程序阻塞了,它一直在等待输入。这是因为它在read
处阻塞了。
但这是不好的,因为如果这是一个网络程序,如果客户端一直没有消息发过来的话,难道就要一直阻塞吗?这是很浪费资源的。
于是,我们要用到select
。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
DESCRIPTION
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 blocking.
select
会轮询监听多个文件描述符,如果有文件描述符准备好了io操作,那么这个文件描述符就会被激活。
Three independent sets of file descriptors are watched. Those listed in readfds will be watched to see if characters become available for reading (more precisely, to see if a
read will not block; in particular, a file descriptor is also ready on end-of-file), those in writefds will be watched to see if a write will not block, and those in exceptfds
will be watched for exceptions. On exit, the sets are modified in place to indicate which file descriptors actually changed status. Each of the three file descriptor sets may be
specified as NULL if no file descriptors are to be watched for the corresponding class of events.
fd_set *readfds, fd_set *writefds,fd_set *exceptfds
是三个传入传出参数。它们分别监听读事件、写事件和异常事件。fd_set *readfds
表示监控有读数据到达文件描述符集合,我们将会使用这个参数,因为我们要监听读事件。
如果设置为NULL
就表示不需要监控文件描述符的该事件。
nfds is the highest-numbered file descriptor in any of the three sets, plus 1.
nfds
为要监控的文件描述符的最大数+1。因为文件描述符的下标是从0开始的。
The timeout argument specifies the minimum interval that select() should block waiting for a file descriptor to become ready
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
timeout
就是要阻塞的时间,它由秒和微秒组成。
RETURN VALUE
On success, select() and pselect() return the number of file descriptors contained in the three returned descriptor sets (that is, the total number of bits that are set in
readfds, writefds, exceptfds) which may be zero if the timeout expires before anything interesting happens
select
返回激活的文件描述符的总数。如果timeout
时间到了,就会返回0。
另外:
Four macros are provided to manipulate the sets. FD_ZERO() clears a set. FD_SET() and FD_CLR() respectively add and remove a given file descriptor from a set. FD_ISSET() tests
to see if a file descriptor is part of the set; this is useful after select() returns.
FD_ZERO()
清空一个集合。
FD_SET()
从一个集合中增加给定的文件描述符。
FD_CLR()
从一个集合中移除给定的文件描述符。
FD_ISSET()
测试一个文件描述符是否在一个集合中。
有了以上的知识,我们来看代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
int fd;
char buf[11];
int ret, sret;
fd = 0;
fd_set readfds;
struct timeval timeout;
while (1) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
sret = select(1, &readfds, NULL, NULL, &timeout);
if (sret == 0) {
printf("sret=%d\n", sret);
printf(" timeout\n");
} else {
printf("sret=%d\n", sret);
//set the buf to 0
memset((void *) buf, 0, 11);
ret = read(fd, (void *) buf, 10);
printf("ret=%d\n", ret);
if (ret != -1) {
buf[10] = '\0';
printf(" buf=%s\n", buf);
}
}
}
return 0;
}
这时候,如果客户端没有发消息过来,服务器端只会等5秒,5秒之后可以处理其他的事。之后如果发消息来了,还是能够收到,因为服务端一直在循环,重要的是,select
并不阻塞。