简介
功能:select允许程序监视多个文件描述符,直到一个或多个文件描述符可读或可写则认为文件描述符已准备就绪
特点:支持阻塞模式和非阻塞模式
阻塞模式 直到监听的文件描述符可读或者可写返回
非阻塞模式 描述符就绪,设置的超时时间到,被信号处理程序中断 返回
注意点:1.select在退出时,每个文件描述符集和超时时间都会被修改,以指示哪些文件描述符实际更改了状态,所以循环调用前必须重新初始化,超时时间也必须重新设置!!!
2.在文件描述符集中可以指定的文件描述符范围上。Linux内核没有固定的限制,但是glibc实现使fd设置一个固定大小的类型,其中fd SETSIZE定义为1024,并且fd *()宏根据该限制进行操作。要监视大于1023的文件描述符,请使用poll(2)
FD_ZERO() 重置描述符.
FD_SET() 添加描述符
FD_CLR() 删除描述符
FD_ISSET 文件描述符是否在就绪集合里
结构体
//超时设置
struct timeval {
long tv_sec; /* seconds */ 秒
long tv_usec; /* microseconds */ 微秒
};
函 数 名 : select
功能描述 :
输入参数 : int nfds 监听最大描述符+1
fd_set *readfds 监听读属性
fd_set *writefds 监听写属性
fd_set *exceptfds 监听异常属性
struct timeval *timeout 超时设置
返 回 值 : int -1 失败 并设置错误码 0 超时 >0 就绪文件描述符个数(读 写 异常集合 总数)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
*************************************************************/
服务器端代码(仅用于验证)
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#define SERVER_PORT 9999
#define SERVER_LISTEN_NUM 10
#define TIMEOUT_SEC 5
#define TIMEOUT_USEC 0
#define SOKCETLIST_MAX SERVER_LISTEN_NUM+1
/*
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct timeval {
long tv_sec; seconds
long tv_usec; microseconds
};
*/
typedef struct
{
int use;
int sock;
}SocketList_t;
SocketList_t g_sock_list[SOKCETLIST_MAX];
int socketlist_init(void)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
g_sock_list[i].use = 0;
g_sock_list[i].sock = -1;
}
}
int socketlist_add(int sock)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (g_sock_list[i].use)
{
continue;
}
g_sock_list[i].use = 1;
g_sock_list[i].sock = sock;
return 0;
}
return -1;
}
void socketlist_del(int sock)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (g_sock_list[i].use && g_sock_list[i].sock == sock)
{
g_sock_list[i].use = 0;
g_sock_list[i].sock = -1;
return;
}
}
}
int socketlist_fdmax_get(void)
{
int i = 0;
int maxfd = -1;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (!g_sock_list[i].use)
{
continue;
}
maxfd = g_sock_list[i].sock >= maxfd ? g_sock_list[i].sock : maxfd;
}
return maxfd;
}
void sock_init_before_select(fd_set *readset, fd_set *writeset, fd_set *errorfds)
{
int i = 0;
if (readset)
{
FD_ZERO(readset);
}
if (writeset)
{
FD_ZERO(writeset);
}
if (errorfds)
{
FD_ZERO(errorfds);
}
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (!g_sock_list[i].use)
{
continue;
}
if (readset)
{
FD_SET(g_sock_list[i].sock, readset);
}
if (writeset)
{
FD_SET(g_sock_list[i].sock, writeset);
}
if (errorfds)
{
FD_SET(g_sock_list[i].sock, errorfds);
}
}
}
int main(void *arg, char *argv[])
{
int i = 0;
int sock = -1;
int listen_sock = -1;
int ret = 0;
int max_fd = -1;
struct sockaddr_in address;
struct sockaddr_in remote_addr;
socklen_t remote_addrlen = sizeof(remote_addr);
fd_set writeset;
fd_set readset;
fd_set errorfds;
unsigned long optval = 1;
struct timeval timeout = {TIMEOUT_SEC, TIMEOUT_USEC};
unsigned char recv_buf[256];
socketlist_init();
sock = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sock)
{
printf("socket fail\r\n");
return -1;
}
//一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
address.sin_family = AF_INET;
address.sin_port = htons(SERVER_PORT);
address.sin_addr.s_addr = INADDR_ANY;
ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
if (-1 == ret)
{
printf("bind fail\r\n");
return -1;
}
ret = listen(sock, SERVER_LISTEN_NUM);
if (-1 == ret)
{
printf("listen fail\r\n");
return -1;
}
socketlist_add(sock);
while (1)
{
max_fd = socketlist_fdmax_get();
sock_init_before_select(&readset, &writeset, &errorfds);
printf("time sec:%ld,usec:%ld\r\n", timeout.tv_sec, timeout.tv_usec);
timeout.tv_sec = TIMEOUT_SEC;
timeout.tv_usec = TIMEOUT_USEC;
ret = select(max_fd+1, &readset, NULL, &errorfds, &timeout); //连接连上了文件描述符置为可写
printf("select ret:%d\r\n", ret);
if (-1 == ret)
{
printf("select error:%d,%s\r\n", errno, strerror(errno));
}
else if (0 == ret)
{
printf("select time out\r\n");
}
else
{
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (g_sock_list[i].use && FD_ISSET(g_sock_list[i].sock, &readset))
{
if (sock == g_sock_list[i].sock)
{
listen_sock = accept(sock, (struct sockaddr *)&remote_addr, (socklen_t *)&remote_addrlen);
if (-1 == listen_sock)
{
printf("accept fail err:%s\r\n", strerror(errno));
continue;
}
printf("new client addr\r\n");
socketlist_add(listen_sock);
}
else
{
//可以创建客户端线程处理,这里只是简单验证
ret = recv(g_sock_list[i].sock, recv_buf, sizeof(recv_buf), 0);
if (-1 == ret)
{
continue;
}
printf("recv success ret:%d\r\n", ret);
}
}
}
}
}
}
返回错误情况
1.EBADF 无效描述符,例如监听的描述符被close了 则返回此错误
2.EINTR(signal 7)Linux平台上执行malloc(),如果没有足够的RAM,Linux不是让malloc()失败返回, 而是向当前进程分发SIGBUS信号
3.EINVAL 无效参数 (1).select 第一个参数 最大描述符+1是负数(2).描述符超过最大值(3).超时参数为负数
4.ENOMEM 内存申请失败