Unix函数select和poll, 用来支持Unix中I/O复用的功能,在Unix中I/O模型可以分为以下几种:
(1)阻塞I/O
(2)非阻塞I/O
(3)I/O复用(select和poll)
(4)信号驱动I/O(SIGIO)
(5)异步I/O
阻塞I/O是当应用程序和内核交换数据时,由于内核还没有准备好数据,那么应用程序必须进行阻塞,不能继续执行,直到内核的数据准备好!应用程序取到数据返回后,阻塞过程结束!但返回的结果也并不一定是正确的!这里只是举一个简单的例子!也许情况会更加的复杂!
非阻塞I/O,例如在和内核交换数据时,如果内核的数据没有准备好,那么应用程序不会一真等待,会有一个返回信息,以判断是那里出了问题!这样有助于确认是在那个阶段出了问题!
I/O复用,我们就可以调用系统调用select和poll!在这两个系统调用中的某一个阻塞,而不是真正的阻塞I/O系统调用!
下面主要介绍I/O复用中的select函数。select函数可以指示内核等待多个事件中的任一个发生,仅在一个或多个事件发生,或者等待一个足够的时间后才唤醒进程!
#include<sys/types.h>
#include<sys/time.h>
int select(int maxfdp1, fd_set *readset,fd_set * writeset,fd_set *excpetset, const struct timeval *timeout);
int maxfdpl是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!
比如我们的描述符为1 4 5,那么maxfdp1就为6.描述符从0开始。
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
中间的三个参数readset 、writeset和excpetset指定我们要让内核测试读、写、异常条件所需的描述字!函数select使用描述字集,它一般是一个整型的数组,每个数中的每一位代表一个描述符!
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
系统提供了4个宏对描述符集进行操作:
#include <sys/select.h>
#include <sys/time.h>
//设置文件描述符集fdset中对应于文件描述符fd的位
void FD_SET(int fd, fd_set *fdset); // (将一个给定的文件描述符加入集合之中)
//清除文件描述符集fdset中对应于文件描述符fd的位(设置为0)
void FD_CLR(int fd, fd_set *fdset); // (将一个给定的文件描述符从集合中删除)
//清除文件描述符集fdset中的所有位(既把所有位都设置为0)
void FD_ZERO(fd_set *fdset); // (清空集合)
Select()
//来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。
void FD_ISSET(int fd, fd_set *fdset); //(检查集合中指定的文件描述符是否可以读写)
struct timeval,这个参数是一个结构体的指针,它表示等待内核中的一组描述符任一个准备好需要花费多久的时间!其中timeval指定了秒数和微秒数。
struct timeval{
long tv_sec;//秒数
long tv_usec;//微秒数
};
将 timeout设置为空指针时,会永远等待下去,等待固定的时间:如果timeout指向的timeval中的具体的值时,会等待一个固定的时间,不等待立刻返回,
这时timeval中的tv_sec和tv_usec为0.
返回值,
select有三个可能的返回值。
1.正常情况下返回就绪的文件描述符个数;
2.经过了timeout时长后仍无设备准备好,返回值为0;
3.如果select被某个信号中断,它将返回-1并设置errno为EINTR。
4.如果出错,返回-1并设置相应的errno。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
示例代码:
服务端代码:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<stdio.h>
#define LISTENNUM 5 //listen队列
#define PORT 1234 //端口
#define MAXDATASIZE 1024 //缓冲区
int main()
{
int i,maxi,maxsocketfd,socketfd;
int isready;
fd_set rset,allset;//select文件描述符集合
int listenfd,connectfd;
struct sockaddr_in server;
int fd[FD_SETSIZE];//最大描述符个数FD_SETSIE,最多1024
char recvbuf[MAXDATASIZE];
int sin_size;
int reclen;
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("Socket failed.\n");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置socket
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)
{
perror("Bind fail.\n");
exit(1);
}
if(listen(listenfd,LISTENNUM) == -1)
{
perror("Listen fail.\n");
exit(1);
}
maxsocketfd = listenfd;
maxi = -1;
for(i = 0; i < FD_SETSIZE; i++)
{
fd[i] = -1;
}
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
while(1)
{
struct sockaddr_in addr;
rset = allset;
isready = select(maxsocketfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(listenfd,&rset))
{
printf("Accept a connection.\n");
sin_size = sizeof(struct sockaddr_in);
if((connectfd = accept(listenfd,(struct sockaddr*)&addr,(socklen_t*)&sin_size)) == -1)
{
perror("Accept error.\n");
continue;
}
for(i = 0;i < FD_SETSIZE;i++)
{
if(fd[i] < 0)
{
fd[i] = connectfd;
break;
}
}
if(i == FD_SETSIZE)
{
printf("Clients over range.\n");
exit(1);
}
FD_SET(connectfd,&allset);
maxsocketfd = maxsocketfd > connectfd ? maxsocketfd : connectfd;
maxi = maxi > i ? maxi : i;
if(--isready <= 0)
continue;
}
for(i =0;i <= maxi;i++)
{
if((socketfd = fd[i]) < 0)
continue;
if(FD_ISSET(socketfd,&rset))
{
printf("Have connect.\n");
if((reclen = recv(socketfd,recvbuf,MAXDATASIZE,0)) == 0)
{
close(socketfd);
FD_CLR(socketfd,&allset);
fd[i] = -1;
}
else
printf("Recv: %s\n",recvbuf);
}
}
}
close(listenfd);
}
客户端代码:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<stdio.h>
#define PORT 1234
#define MAXDATASIZE 1024
int main()
{
int clifd,len,sendlen;
char sendbuf[MAXDATASIZE];
clifd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in clisock;
clisock.sin_family = AF_INET;
clisock.sin_port = htons(PORT);
clisock.sin_addr.s_addr = inet_addr("192.168.1.15");
connect(clifd,(struct sockaddr *)&clisock,sizeof(clisock));
strcpy(sendbuf,"Hello!");
sendlen = sizeof(sendbuf);
while(1)
{
send(clifd,sendbuf,sendlen,0);
sleep(1);
}
}