select系统调用的目的是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。poll和select应该被归类为这样的系统 调用,它们可以阻塞地同时探测一组支持非阻塞的IO设备,直至某一个设备触发了事件或者超过了指定的等待时间——也就是说它们的职责不是做IO,而是帮助 调用者寻找当前就绪的设备。
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数nfds是需要监视的文件描述符数,要监视的文件描述符值为0 ~ nfds-1。
参数readfds指定需要监视的可读文件描述符集合,当这个集合中的一个描述符上有数据到达时,系统将通知调用select函数的程序。
参数writefds指定需要监视的可写文件描述符集合,当这个集合中某个描述符可以发送数据时,程序将收到通知。
参数exceptfds指定需要监视的异常文件描述符集合,当这个集合中的一个描述符发生异常时,程序将受到通知。
参数timeout指定了阻塞的时间,如果在这段时间内监视的文件描述符上没有事件发生,则函数select()将返回0。
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
fd_set结构体是文件描述符集,该结构体实际上是一个整型数组,数组中的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符 数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等 于1024,这就限制了select能同时处理的文件描述符的总量。
struct timeval{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
这个参数有三种可能:select()返回值情况:
超时时间内,如果文件描述符就绪,select返回就绪的文件描述符总数(包括可读、可写和异常),如果没有文件描述符就绪,select返回0;
select调用失败时,返回 -1并设置errno,如果收到信号,select返回 -1并设置errno为EINTR。
文件描述符的就绪条件:
在网络编程中,
1)下列情况下socket可读:
a) socket内核接收缓冲区的字节数大于或等于其低水位标记SO_RCVLOWAT;
b) socket通信的对方关闭连接,此时该socket可读,但是一旦读该socket,会立即返回0(可以用这个方法判断client端是否断开连接);
c) 监听socket上有新的连接请求;
d) socket上有未处理的错误。2)下列情况下socket可写:
a) socket内核发送缓冲区的可用字节数大于或等于其低水位标记SO_SNDLOWAT;
b) socket的读端关闭,此时该socket可写,一旦对该socket进行操作,该进程会收到SIGPIPE信号;
c) socket使用connect连接成功之后;
d) socket上有未处理的错误。
select优点
解决了读写阻塞的问题,一进程一线程就可完成数据间的传输,避免了多进程多线程的消耗,提高了资源利用率。
select缺点
select支持的文件描述符数量最大1024
每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
每次调用select都需要在内核遍历传递进来的所有fd,查看有没有就绪的fd,这个开销在fd很多时也很大,效率随FD数目增加而线性下降
#include
#include
#include
#include
#include
#include
#include
const int backlog = 8; //最大监听数
int fds[32];
int fds_nums = sizeof(fds) / sizeof(*fds);
#define BuffSize 1024
int m_listen(char *ip, int port)
{
//套接字结构体
struct sockaddr_in ser;
(struct sockaddr_in*)memset(&ser, 0x00, sizeof(struct sockaddr_in));
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = inet_addr(ip);
//套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(sock_fd == -1)
{
perror("socket error:");
exit(1);
}
//设置套接字属性
int optval = 1;
if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) == -1)
{
perror("setsockopt error:");
exit(1);
}
//绑定端口
if(bind(sock_fd, (struct sockaddr*)&ser, sizeof(struct sockaddr)) == -1)
{
perror("bind error:");
exit(1);
}
//监听
if(listen(sock_fd, backlog) == -1)
{
perror("listen error:");
exit(1);
}
return sock_fd;
}
int main(int argc, char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage [%s] [ip] [port]\n", argv[0]);
exit(0);
}
int sock_fd = m_listen(argv[1], atoi(argv[2]));
fd_set readfds;
//清空读取文件描述符集
FD_ZERO(&readfds);
int i;
for(i = 0; i < fds_nums; ++i)
fds[i] = -1;
//将监听套接字加入0号位
fds[0] = sock_fd;
while(1)
{
int max_fd = -1;
for(i = 0; i < fds_nums; ++i)
if(fds[i] > 0)
{
FD_SET(fds[i], &readfds);
max_fd = max_fd > fds[i] ? max_fd : fds[i];
}
switch(select(max_fd + 1, &readfds, NULL, NULL, NULL))
{
case 0:
fprintf(stderr, "time out ...\n");
break;
case -1:
perror("select error:");
break;
default:
for(i = 0; i < fds_nums; ++i)//遍历所有接受管理的描述符
{
//如果是监听套接字
if(i == 0 && FD_ISSET(sock_fd, &readfds))
{
struct sockaddr_in cli;
socklen_t len = sizeof(struct sockaddr_in);
int conn_fd;
if((conn_fd = accept(sock_fd, (struct sockaddr*)&cli, &len)) == -1)
{
perror("connect error:");
break;
}
fprintf(stderr, "accept a new client, ip:%s port:%d\n", inet_ntoa(cli.sin_addr),\
ntohs(cli.sin_port));
int j;
for(j = 0; j < fds_nums; ++j)
if(fds[j] == -1)
{
fds[j] = conn_fd;
break;
}
if(j == fds_nums)
close(conn_fd);
}
else
{
if(FD_ISSET(fds[i], &readfds))
{
int ret;
int buff[BuffSize] = {0};
if((ret = recv(fds[i], buff, BuffSize - 1, 0)) == -1)
{
perror("recv error:");
close(fds[i]);
fds[i] = -1;
}
else if(ret)
{
buff[ret] = '\0';
fprintf(stderr, "recv:%s\n", buff);
}
else
{
close(fds[i]);
fds[i] = -1;
}
}
}
}
break;
}
}
return 0;
}