I/O复用使得程序能监听多个文件描述符,这对提高程序的性能至关重要。
I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的,并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。
Linux下实现I/O复用的系统调用主要有select、poll、epoll。
select系统调用
select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
原型如下:
#include<sys/select.h>
int select(int nfds, fd_set *readfds, fd_set* writefds, fd_set* exceptfds, srtuct timeval *timeout);
nfds: 最大文件描述符+1
readfds、writefds和exceptfds参数分别表示可读、可写和异常等事件对应的文件描述符的集合。应用程序调用select函数时,通过这三个参数传入自己感兴趣的文件描述符。select调用返回时,内核将修改他们来通知应用程序哪些文件描述符已经就绪。这三个参数是fd_set结构指针类型,定义如下:
FD_ZREO(fd_set *fdset);
FD_SET(int fd, fd_set *fdset);
FD_CLR(int fd,fdJ_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
timeout参数用来设置select函数的超时时间,它是一个timeval结构体指针,定义如下:
struct timeval
{
long tv_sec; //秒数
long tv_usec; //微妙数
}
如果给timeout变量的所有成员都传递0,则select将立即返回。如果给timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。
select成功时返回就绪文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0。失败返回-1并设置error。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置error为EINTR。
下面来看实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("192.168.101.250");
int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
listen(sockfd, 5);
int nfds = sockfd + 1;
fd_set read;
int fds[128];
int i=0;
for(;i<128;i++)
{
fds[i]=-1;
}
fds[0] = sockfd;
while(1)
{
FD_ZERO(&read); //清空
int i = 0;
for(; i < 128; ++i)
{
if(fds[i] != -1)
{
FD_SET(fds[i], &read);
}
}
int n = select(nfds, &read, NULL, NULL, NULL);
if(n < 0)
{
printf("error\n");
exit(0);
}
if(n == 0) // 超时
{
printf("time out\n");
continue;
}
i = 0;
for(; i < 128; ++i)
{
if(fds[i] == -1)
continue;
if(FD_ISSET(fds[i], &read))
{
if(fds[i] == sockfd)
{
int len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr*)&cli, &len);
printf("one client link \n");
int i = 0;
for(; i < 128; ++i)
{
if(fds[i] == -1)
{
fds[i] = c;
if((nfds-1)<c)
{
nfds=c+1;
}
break;
}
}
}
else
{
char buff[128] = {0};
int n = recv(fds[i], buff, 128, 0);
if(n <= 0)
{
close(fds[i]);
fds[i] = -1;
continue;
}
printf("%s\n", buff);
send(fds[i], "ok", 2, 0);
}
}
}
}
}
运行结果如下:
服务器:
客户端: