#include
#include
int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
nfds
: 是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加 1
。在linux系统中,select
的默认最大值为1024
。设置这个值的目的是为了不用每次都去轮询这1024
个fd
,假设我们只需要几个套接字,我们就可以用最大的那个套接字的值加上1作为这个参数的值,当我们在等待是否有套接字准备就绪时,只需要监测nfds+1
个套接字就可以了,这样可以减少轮询时间以及系统的开销。
readfds
: 用来检测输入是否准备就绪的文件描述符集合(从内核读,用的最多)
writefds
: 用来检测输出是否准备就绪的文件描述符集合(往内核写,比较少用)
exceptset
; 很少用到,我没用过。用来检测异常情况是否发生的文件描述符集合。
timeout
: 堵塞的超时时间,如果为 NULL
则堵塞。
系统调用 select() 会一直堵塞,直到一个或者多个文件描述符集合称为就绪态,select 就会往下执行。
这个概念很常见,跟套接字可读一样。
select
监听,如果是可读套接字的 fd_set
,那么套接字对应的内核中,一旦收到数据,那么就会使 select
返回,fd_set
准备就绪。select
监听,如果是可写的套接字的 fd_set
,那么套接字对应的内核中,一旦数据的缓冲区有空余的位置写数据,那么就会使 select
返回,fd_set
准备就绪。这句话有些难理解。具体的说:
一、 满足下列四个条件中的任何一个时,一个套接字准备好读。
1
。那就意味着,默认情况下,只要缓冲区中有数据,那就是可读的。我们可以通过使用SO_RCVLOWAT
套接字选项(参见setsockopt函数)来设置该套接字的低水位大小。此种描述符就绪(可读)的情况下,当我们使用read/recv
等对该套接字执行读操作的时候,套接字不会阻塞,而是成功返回一个大于0
的值(即可读数据的大小)。二、满足下列四个条件中的任何一个时,一个套接字准备好写。
2048
,而套接字默认的发送缓冲区大小是8k,这就意味着一般一个套接字连接成功后,就是处于可写状态的。我们可以通过SO_SNDLOWAT
套接字选项(参见setsockopt函数)来设置这个低水位。此种情况下,我们设置该套接字为非阻塞,对该套接字进行写操作(如write,send等),将不阻塞,并返回一个正值(例如由传输层接受的字节数,即发送的数据大小)。readfds
的 fd_set
是一个标准输入,一个 writefds
的 fd_set
是标准输出。使用 select
堵塞等待。readfds
中是标准输出, select
会立马返回就绪writefds
时,因为里面放的是标准输入,因为标准输入在正常情况下就是可写的,所以 select
会立马返回并就绪,不会堵塞。fd_set
这个网上资料很多,细节很多。我有空回来再补
操作 fd_set
的四个宏:
#include
int FD_ZERO(int fd, fd_set *fdset); // 初始化:相当于将 fd_set 清零
int FD_CLR(int fd, fd_set *fdset); // 将文件描述符 fd 从 fd_set 中移除
int FD_SET(int fd, fd_set *fd_set); // 将文件描述符 fd 加入 fd_set 中
int FD_ISSET(int fd, fd_set *fdset); // 如果文件描述符 fd 是 fd_set 所指向的成员,返回 true
timeout
参数struct timeval
#include
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
上面说,timeout
为 NULL
时,select
会堵塞。如果 timeout
的两个值都为 0
,不会堵塞。select
直接立即返回
select
返回值这个看多了用多了代码就会了,大部分使用select
的代码都会出现判断它的所有返回值的:
while(1)
{
...
ret = select(nfds, readfds, ...);
if (ret > 0) {}
else if (ret == 0) {}
else if (ret < 0) {}
...
}
返回值:
EBADF
和 EINTR
。具体上网搜"t_select.h"
在文末获取。
#include
#include
#include
#include
#include
#include "t_select.h"
/**
* example:
* ./t_select 10 0r 1w
*
* comment:
* 10: timeout(second)
* 0r: 以读形式读取标准输入
* 1w: 以写形式去写标准输出
*/
int main(int argc, char *argv[])
{
fd_set readfds, writefds;
int ready, nfds, fd, numRead, j;
struct timeval timeout;
struct timeval *pto;
char buf[10]; /* Large enough to hold "rw\0" */
if (argc < 2 || strcmp(argv[1], "--help") == 0)
usageError(argv[0]);
/* Timeout for select() is specified in argv[1] */
// "-": 代表堵塞; 否则就是超时时间
if (strcmp(argv[1], "-") == 0)
{
pto = NULL; /* Infinite timeout */
}
else
{
pto = &timeout;
timeout.tv_sec = getLong(argv[1], 0, "timeout");
timeout.tv_usec = 0; /* No microseconds */
}
/* Process remaining arguments to build file descriptor sets */
nfds = 0;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
for (j = 2; j < argc; j++)
{
numRead = sscanf(argv[j], "%d%2[rw]", &fd, buf);
if (numRead != 2)
usageError(argv[0]);
if (fd >= FD_SETSIZE)
printf("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
if (fd >= nfds)
nfds = fd + 1; /* Record maximum fd + 1 */
if (strchr(buf, 'r') != NULL)
FD_SET(fd, &readfds);
if (strchr(buf, 'w') != NULL)
FD_SET(fd, &writefds);
}
/* We've built all of the arguments; now call select() */
// 第一个参数:套接字返回
ready = select(nfds, &readfds, &writefds, NULL, pto);
/* Ignore exceptional events */
if (ready == -1)
usageError("select");
/* Display results of select() */
printf("ready = %d\n", ready);
for (fd = 0; fd < nfds; fd++)
printf("%d: %s%s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "",
FD_ISSET(fd, &writefds) ? "w" : "");
if (pto != NULL)
printf("timeout after select(): %ld.%03ld\n",
(long)timeout.tv_sec, (long)timeout.tv_usec / 1000);
exit(EXIT_SUCCESS);
}
/**************************************************************************/
#include
#include
#include
#include
#include
#define GET_NUM_H
#define GN_NONNEG 01 /* Value must be >= 0 */
#define GN_GT_0 02 /* Value must be > 0 */
/* By default, integers are decimal */
#define GN_ANY_BASE 0100 /* Can use any base - like strtol(3) */
#define GN_BASE_8 0200 /* Value is expressed in octal */
#define GN_BASE_16 0400 /* Value is expressed in hexadecimal */
/* Print a diagnostic message that contains a function name ('fname'),
the value of a command-line argument ('arg'), the name of that
command-line argument ('name'), and a diagnostic error message ('msg'). */
static void
gnFail(const char *fname, const char *msg, const char *arg, const char *name)
{
fprintf(stderr, "%s error", fname);
if (name != NULL)
fprintf(stderr, " (in %s)", name);
fprintf(stderr, ": %s\n", msg);
if (arg != NULL && *arg != '\0')
fprintf(stderr, " offending text: %s\n", arg);
exit(EXIT_FAILURE);
}
/* Convert a numeric command-line argument ('arg') into a long integer,
returned as the function result. 'flags' is a bit mask of flags controlling
how the conversion is done and what diagnostic checks are performed on the
numeric result; see get_num.h for details.
'fname' is the name of our caller, and 'name' is the name associated with
the command-line argument 'arg'. 'fname' and 'name' are used to print a
diagnostic message in case an error is detected when processing 'arg'. */
static long
getNum(const char *fname, const char *arg, int flags, const char *name)
{
long res;
char *endptr;
int base;
if (arg == NULL || *arg == '\0')
gnFail(fname, "null or empty string", arg, name);
base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8
: (flags & GN_BASE_16) ? 16
: 10;
errno = 0;
res = strtol(arg, &endptr, base);
if (errno != 0)
gnFail(fname, "strtol() failed", arg, name);
if (*endptr != '\0')
gnFail(fname, "nonnumeric characters", arg, name);
if ((flags & GN_NONNEG) && res < 0)
gnFail(fname, "negative value not allowed", arg, name);
if ((flags & GN_GT_0) && res <= 0)
gnFail(fname, "value must be > 0", arg, name);
return res;
}
/* Convert a numeric command-line argument string to a long integer. See the
comments for getNum() for a description of the arguments to this function. */
long getLong(const char *arg, int flags, const char *name)
{
return getNum("getLong", arg, flags, name);
}
/* Convert a numeric command-line argument string to an integer. See the
comments for getNum() for a description of the arguments to this function. */
int getInt(const char *arg, int flags, const char *name)
{
long res;
res = getNum("getInt", arg, flags, name);
if (res > INT_MAX || res < INT_MIN)
gnFail("getInt", "integer out of range", arg, name);
return res;
}
/***********************************************************************************************************/
static void usageError(const char *progName)
{
fprintf(stderr, "Usage: %s {timeout|-} fd-num[rw]...\n", progName);
fprintf(stderr, " - means infinite timeout; \n");
fprintf(stderr, " r = monitor for read\n");
fprintf(stderr, " w = monitor for write\n\n");
fprintf(stderr, " e.g.: %s - 0rw 1w\n", progName);
exit(EXIT_FAILURE);
}