select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
select API
select函数原型如下:
# include<stdio.h>
int select(int nfds, fd_set* readfds, fd_set* wtitefds, fd_set* exceptfds, struct timeval* timeout);
//nfd参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中最大的值加一,因为文件描述符是从0开始的。
//reafds、writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符结合。
//应用程序调用select时,通过这3个参数传入自己感兴趣的文件描述符。select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
//这3个参数时fd_set结构体指针类型。fd_set结构体仅包含一个数组,该数组的每个元素的每一位标记一个文件描述符。
//fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。
//timeout参数用来设置select函数的超时时间。
由于位操作过于繁琐,我们通常使用下面的一系列宏来访问fd_set结构体中的位:
# include<sys/select.h>
FD_ZERO(fd_set* fdset);//清楚fd_set中所有位
FD_SET(int fd, fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd, fd_set *fdset);//清楚fdset的位fd
int FD_ISSET(int fd, fd_set *fdset);//测试fdset的位fd是否被设置
文件描述符就绪的条件
哪些情况下文件描述符可以被认为是可读、可写或出现异常,对于select的使用非常关键。在网络编程中:
下列情况下socket可读:
- socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
- socket通信的对方关闭连接。此时队该socket的读操作将返回0。
- 监听套接字上有新的连接请求。
- socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
下列情况下socket可写:
- socke内核发送缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回地字节数大于0。
- socket的写操作被关闭。对写操作被关闭的soacket执行写操作将触发一个SIGPIPE信号。
- socket使用非阻塞connect连接成功或者失败之后。
- socket上有未处理的错误。此时我们可以使用getsockopt来读取和清楚该错误。
代码清单
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<unistd.h>
# include<arpa/inet.h>
# include<sys/socket.h>
# include<string.h>
# include<arpa/inet.h>
# include<sys/select.h>
# define MAXFD 10
//初始化fds数组
void fds_init(int *fds)
{
int i = 0;
for(; i < MAXFD; ++i)
{
fds[i] = -1;
}
}
//添加套接字描述符到fds数组
void fds_add(int *fds, int fd)
{
int i = 0;
for(; i < MAXFD; ++i)
{
if(fds[i] == -1)
{
fds[i] = fd;
break;
}
}
}
//删除指定套接字描述符
void fds_del(int *fds, int fd)
{
int i = 0;
for(; i < MAXFD; ++i)
{
if(fds[i] == fd)
{
fds[i] = -1;
break;
}
}
}
int main()
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in saddr, caddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//命名套接字
int res = bind(sockfd, (struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
//监听套接字
listen(sockfd, 5);
//定义数组
int fds[MAXFD];
fds_init(fds);
//将定义好的套接字描述符添加到数组中
fds_add(fds, sockfd);
while(1)
{
fd_set fdset;
FD_ZERO(&fdset);
int maxfd = -1;
int i = 0;
for(; i < MAXFD; i++)
{
if(fds[i] == -1)
{
continue;
}
FD_SET(fds[i], &fdset);
if(fds[i] > maxfd)
{
maxfd = fds[i];
}
}
struct timeval tv = {5, 0};
int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
if(n == -1)
{
printf("select error\n");
continue;
}
else if(n == 0)
{
printf("time out\n");
continue;
}
else
{
int i = 0;
for(; i < MAXFD; i++)
{
if(fds[i] == -1)
{
continue;
}
if(FD_ISSET(fds[i], &fdset))
{
if(fds[i] == sockfd)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr, &len);
if(c < 0)
{
continue;
}
printf("accept c = %d\n", c);
fds_add(fds, c);
}
else
{
char buff[128] = {0};
int num = recv(fds[i], buff, 127, 0);
if(num <= 0)
{
close(fds[i]);
fds_del(fds,fds[i]);
printf("one client close\n");
}
else
{
printf("recv(%d)=%s\n", fds[i], buff);
send(fds[i], "ok", 2, 0);
}
}
}
}
}
}
}