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; }