I/O复用使得程序可以同时监听多个文件描述符,这对提高程序的性能至关重要。需要指出的是I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来是串行工作的。如果要实现并发,只能使用多进程或多线程等手段。
Linux下实现I/O复用的系统调用主要有select、poll、epoll。
1、select系统调用
select系统调用的用途是,在一定指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
select系统调用的原型如下:
#include<sys/select.h>
int select(int nfds,fd_set *readset,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
1)nfds参数指定被监听的文件描述符总数。通常它被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
2)readfds,writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用select函数时,通过这三个函数传入自己感兴趣的文件描述符。select调用返回时,内核将修改通知应用程序那些文件描述符已经就绪。
3)timeout参数用来设置select函数的超时时间。它是一个timeval结构类型的指针,采用指针参数是因为内核将修改它以告诉用用程序select等待了多久。不过我们不能完全信任select调用返回后的timeout值,比如调用失败时timeout值是不确定的。
select成功时返回就续文件描述符(可读、可写、异常)的总数,如果在超时时间内没有任何文件描述符就续。select返回0。select失败时返回-1。如果在select等待期间,程序接收到信号,则select立即返回-1。
代码实例:
clic.c
#include<stdio.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
int sockcli = socket(AF_INET,SOCK_STREAM,0);
if(sockcli == -1)
{
perror("socket");
exit(1);
}
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(atoi(argv[2]));
addrSer.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(struct sockaddr);
int ret = connect(sockcli,(struct sockaddr*)&addrSer,addrlen);
if(ret == -1)
{
printf("Client connect Server fail\n");
return -1;
}
else
printf("Client Connect Server success\n");
char mess[256];
char recvbuff[256];
while(1)
{
printf("Enter message:");
scanf("%s",mess);
send(sockcli,mess,strlen(mess)+1,0);
recv(sockcli,recvbuff,256,0);
printf("From server self:>%s\n",recvbuff);
}
close(sockcli);
return 0;
}
ser.c
utili.h
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<string.h>
#define LISTEN_QUEUE_SIZE 5
typedef
int startup(char *ip,short port)
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("create socket fail");
return -1;
}
int yes = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr=inet_addr(ip);
socklen_t addrlen = sizeof(struct sockaddr);
bind(sockfd,(struct sockaddr*)&address,addrlen);
listen(sockfd,LISTEN_QUEUE_SIZE);
return sockfd;
}
#include"utili.h"
#include<sys/select.h>
#define MAX_CLIENT_NUM 10
void data_handler(int sockconn)
{
char mess[256];
recv(sockconn,mess,256,0);
printf("recv mess:>%s\n",mess);
send(sockconn,mess,strlen(mess)+1,0);
}
int main(int argc ,char *argv[])
{
int sockSer = startup(argv[1],atoi(argv[2]));
fd_set readset;
int max_sock=sockSer;
int client_conn_num = 0;
int client_fd[MAX_CLIENT_NUM]={0};
while(1)
{
FD_ZERO(&readset);
FD_SET(sockSer,&readset);
int i;
for(i=0;i<MAX_CLIENT_NUM;i++)
{
if(client_fd[i]!=0)
{
FD_SET(client_fd[i],&readset);
}
}
int ret = select(max_sock+1,&readset,NULL,NULL,NULL);
if(ret == -1)
{
perror("select");
continue;
}
else if(ret == 0)
{
printf("server time out");
continue;
}
else
{
if(FD_ISSET(sockSer,&readset))
{
struct sockaddr_in addrCli;
socklen_t addrlen = sizeof(struct sockaddr);
int sockconn = accept(sockSer,(struct sockaddr*)&addrCli,&addrlen);
if(sockconn == -1)
{
perror("accept");
continue;
}
if(client_conn_num < MAX_CLIENT_NUM)
{
client_fd[client_conn_num++]=sockconn;
if(sockconn>max_sock)
{
max_sock = sockconn;
}
}
else
{
printf("server over load\n");
}
}
else
{
for(i=0;i<client_conn_num;i++)
{
if(FD_ISSET(client_fd[i],&readset))
{
data_handler(client_fd[i]);
}
}
}
}
}
close(sockSer);
return 0;
}
2、poll 系统调用
poll系统调用和select类似,也是在指定时间内轮训一定数量的文件描述符,以测试其中是否有就绪者。poll的原型如下:
#include<poll.h>
int poll(struct pollfd *fds,nfds_t nfds,int timout);
1)fds是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体的定义如下:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 注册的事件 */
short revents; /* 实际发生的事件,由内核填充*/
}
2)nfds指定被监听集合fds的大小。
3)timeout参数指定poll的超时值,单位是毫秒。当timeout为-1时,poll将永远阻塞,直到某个事件发生,当timeout为0时,poll调用将立即返回。
ser.c
#include"../utili.h"
#include<poll.h>
#define MAX_CLIENT_NUM 5
void data_handler(int sockconn)
{
while(1)
{
char message[256];
recv(sockconn,message,256,0);
printf("mess>>%s\n",message);
send(sockconn,message,strlen(message)+1,0);
}
}
int main(int argc,char *argv[])
{
int sockSer = startup(argv[1],atoi(argv[2]));
struct pollfd client_fd[MAX_CLIENT_NUM];
client_fd[0].fd = sockSer;
client_fd[0].events = POLLIN;
int i;
for(i=1;i<MAX_CLIENT_NUM;i++)
{
client_fd[i].fd=0;
}
int num = 1;
while(1)
{
//printf("aaaaaaaaaaaaaaaaaaa\n");
int ret = poll(client_fd,num,-1);
// printf("bbbbbbbbbbbbbbbb\n");
if(ret == -1)
{
perror("poll");
continue;
}
else if(ret == 0)
{
printf("server time out");
continue;
}
else
{
int i;
if(client_fd[0].revents & POLLIN)
{
struct sockaddr_in addrcli;
socklen_t addrlen = sizeof(struct sockaddr);
int sockconn = accept(sockSer,(struct sockaddr*)&addrcli,&addrlen);
if(sockconn == -1)
{
perror("accept");
continue;
}
for(i=0;i<MAX_CLIENT_NUM;i++)
{
if(client_fd[i].fd == 0)
{
client_fd[i].fd = sockconn;
client_fd[i].events = POLLIN;
num++;
break;
}
}
if(i>=MAX_CLIENT_NUM)
{
printf("server over load");
}
}
else
{
for(i=1;i<MAX_CLIENT_NUM;i++)
{
if(client_fd[i].fd == 0)
continue;
if(client_fd[i].revents&POLLIN)
{
data_handler(client_fd[i].fd);
}
}
}
}
}
close(sockSer);
return 0;
}
3、epoll系统调用
ser.c
#include<stdio.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include"../utili.h"
#define MAX_CLIENT_NUM 10
#define MAX_EVENT_SIZE 100
void data_handler(int sockconn)
{
char buffer[256];
recv(sockconn,buffer,256,0);
printf("Msg:>%s\n",buffer);
send(sockconn,buffer,strlen(buffer)+1,0);
}
int main(int argc,char* argv[])
{
int sockSer = startup(argv[1],atoi(argv[2]));
struct epoll_event event[MAX_EVENT_SIZE];
int epoll_fd = epoll_create(MAX_EVENT_SIZE);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockSer;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sockSer,&ev);
int client_conn_num = 0;
while(1)
{
int ret = epoll_wait(epoll_fd,event,MAX_EVENT_SIZE,-1);
if(ret == -1)
{
perror("epoll_wait");
continue;
}
else if(ret == 0)
{
perror("server time out");
continue;
}
else
{
int i;
for(i=0;i<ret;i++)
{
if(event[i].data.fd == sockSer&&event[i].events&EPOLLIN)
{
struct sockaddr_in addrCli;
socklen_t addrlen = sizeof(struct sockaddr);
int sockconn = accept(sockSer,(struct sockaddr*)&addrCli,&addrlen);
if(sockconn == -1)
{
perror("accept");
continue;
break;
}
if(client_conn_num >= MAX_CLIENT_NUM)
{
printf("server over load");
}
else
{
ev.events = EPOLLIN;
ev.data.fd = sockconn;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sockconn,&ev);
client_conn_num++;
}
}
else if(event[i].events &EPOLLIN)
{
data_handler(event[i].data.fd);
}
}
}
}
close(sockSer);
return 0;
}