一、select函数的作用
select函数是一个I/O多路复用函数。所谓多路复用比较官方的定义是内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。比如电脑的键盘和显示器,如果一直阻塞在键盘上等待键盘输入那么如果这时候显示器上来了信息怎么办?岂不是不能正常显示!I/O复用就是将键盘和显示器监控起来,如果键盘上来了消息内核就会通知去处理键盘上的事件,如果没有事件发生就让电脑去干其他事情这样,就不用让电脑一直等待浪费时间浪费精力了=、=当然,键盘和显示器具体是怎么监控的我也不得而知=、=在这个离只是觉得可以这样子举例说明,也不知恰当与否。对于I/O多路复用知乎上有一篇帖子讲的挺好的http://www.zhihu.com/question/28594409
在linux中man一下我们就可以看到select函数的原型:
select函数的第一个参数是要监控的集合大小,这个值必须是一个常量(数字或宏等)。第二个参数表示要监控的是可读事件集合,第三个参数是可写事件集合,第四个参数是要监控的错误事件集合,第五个参数表示select函数阻塞的时间长短当其为NULL时表示select一直阻塞直到事件发生。
伴随着select的还有以下操作:
FD_ZERO(fd_set *):清空监控集合
FD_SET(int ,fd_set*):将文件描述符加入到集合中
FD_CLR(int,fd_set*):将文件描述符从集合中删除
FD_ISSET(int ,fd_set* ):检查文件描述符是否在集合中
在linux网络编程中select常常用于服务器端连接多个客户端的实现,select会自动监控集合中的元素,当某些元素发生变化时内核会通知select函数,然后select函数会将没有发生事件的元素剔除掉,集合中只剩下发生事件的元素,然后使用上述FD_ISSET函数进行探测看具体是哪一个元素发生变化了。
linux下用select实现一个简单的多客户端服务器程序的步骤如下:
1、将监听套接字加入到select监听集合中和自己建的一个一维数组中。
2、用select监控集合,当集合上某元素发生变化时就用循环探测到底是一维数组中哪个元素发生变化了。
3、如果是监听套接字发生事件则调用accept函数,并将返回的套接字描述符加入到一维数组和监听集合中。
4、如果是客户端的套接字发生事件则执行转发功能。
代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
int cli_fd,sev_fd,port,sev_addr_len;
struct sockaddr_in cli_addr,sev_addr;
int result,recv_count;
fd_set readfds,testfds;
int cli_count = 0,fd[20];
char buf[256];
int i,j;
memset(fd,0,sizeof(fd));
if (argc != 2)
{
printf("参数有误!\n"); return EXIT_FAILURE;
}
//初始化sever端socket
sev_fd = socket(AF_INET,SOCK_STREAM,0);
if (sev_fd < 0)
{
perror("Socket Error"); return EXIT_FAILURE;
}
int on = 1;
if (setsockopt(sev_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
perror("Setsockopt Error");
return EXIT_FAILURE;
}
//初始化sever端IP
port = atoi(argv[1]);
sev_addr.sin_family = AF_INET;
sev_addr.sin_port = htons(port);
sev_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口
if (bind(sev_fd,(struct sockaddr *)&sev_addr,sizeof(sev_addr)) == -1)
{
perror("Bind Error"); return EXIT_FAILURE;
}
//监听
if (listen(sev_fd,20) == -1)
{
perror("Listen Error"); return EXIT_FAILURE;
}
FD_ZERO(&readfds); //清空集合
FD_SET(sev_fd,&readfds); //将监听套接字加入到集合中
fd[cli_count] = sev_fd; //将监听套接字加入到一维数组中
cli_count++;
while(1)
{
testfds = readfds; //因为select每次会改变集合内容,因此这里使用一个中间变量供select使用,并且每次循环都将其重新赋值
result = select(20,&testfds,NULL,NULL,NULL); //select调用
if (result == -1)
{
perror("Select Error");
return EXIT_FAILURE;
}
if (result > 0)
{
for (i = 0; i < cli_count; i++)
{
if (FD_ISSET(fd[i],&testfds))
{
if (fd[i] == sev_fd)
{
//接受连接
sev_addr_len = sizeof(sev_addr);
cli_fd = accept(sev_fd,(struct sockaddr *)&sev_addr,&sev_addr_len);
if (cli_fd < 0)
{
perror("Accept Error"); return EXIT_FAILURE;
}
FD_SET(cli_fd, &readfds);
fd[cli_count] = cli_fd;
cli_count++;
}
else
{
memset(buf,0,sizeof(buf));
recv_count = recv(fd[i], buf, sizeof(buf),0);
if (recv_count == 0)
{
cli_count--;
close(fd[i]);
FD_CLR(fd[i],&readfds);
for (j = i; j < cli_count; j++)
fd[j] = fd[j + 1];
}
else
{
for (j = 1; j < cli_count; j++)
{
if (fd[j] == fd[i])
continue;
send(fd[j],buf,strlen(buf),0);
}
}
}
}
}
}
}
close(sev_fd);
}