select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。而TCP服务器要想实现多个描述符的等待需要用多进程多线程的方式实现。IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
下面重点对比select服务器和epoll服务器:
(一)select服务器原理图。
(二)epoll服务器模型
注:select 原理图,摘自 IBM iSeries 信息中心。
(三)优缺点对比
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024点
epoll是select的改进其优势:
对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。
对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大
如下是源码:
(1)TCP服务器
server.c
# include<errno.h>
# include<unistd.h>
# include<string.h>
# include<sys/types.h>
# define _PORT_ 999
# define BACKLOG_ 10
int main ()
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
printf("create socket error,error is:%d,errstring is:%d\n",errno,strerror(errno));
}
struct sockaddr_in server_socket;
struct sockaddr_in client_socket;
bzero(&server_socket,sizeof(server_socket));
server_socket.sin_family=AF_INET;
server_socket.sin_addr.s_addr=htonl(INADDR_ANY);
server_socket.sin_addr.port=htons(_PORT_);
if(bind(sock,(struct sockaddr*)&server_socket,sizeof(struct sockaddr_in))<0);
{
printf("error code is:%d,errstring is:%d\n",errno,strerror(errno));
close(sock);
return 1;
}
if(listen(sock,_BACKLOG_)<0)
{
printf("listen error,error is:%d,errstring is:%d\n",errno,strerror(errno));
close(sock);
return 2;
}
printf("bind and listen success,wait accept..");
for(::)
{
socklen_t len=0;
int client_sock=accept(sock,(struct sockaddr *)&client_socket,&len);
if(client_sock<0)
{
printf("accept error error code :%d, error string :%d\n",errno,strerror(errno));
close(sock);
return 3;
}
char buf_ip[INET_ADDRSTRLEN];
memset(buf_ip,'\0',sizeof(buf_ip));
inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip));
printf("get connect, ip is:%d,port is:%d\n",buf_ip,ntohs(client_socket.sin_port));
while(1)
{
char buf[1024];
memset(buf,'\0',sizeof(buf));
read(client_sock,buf,sizeof(buf));
printf("client :# %s\n",buf);
printf("server:$");
memset(buf,'\0',sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
write(client_sock,buf,strlen(buf)+1);
printf("please wait..");
}
}
close(sock);
return 0;
}
client.c
# include<stdio.h>
# include<unistd.h>
# include<sys/socket.h>
# include<sys/types.h>
# include<strings.h>
# include<error.h>
# include< netinet/in.h>
# include<arpa/inet.h>
# define SERVER_PORT 9999
# define SERVER_IP "1992.168.0.111"
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("Usage:client IP\n");
return 1;
}
char *str=argv[1];
char buf[1024];
memset(buf,'\0',sizeof(buf));
struct sockaddr_in server_sock;
int sock=socket(AF_INET,SOCK_STREAM,0);
bzero(&server_sock,szieof(server_sock));
server_sock.sin_family=AF_INET;
inet_pton(AF_INET,SERVER_IP,&server_sock.sin_addr);
server_sock.sin_port=htons(SERVER_PORT);
int ret=connect(sock,(struct sockaddr *)&server_sock,sizeof(server_sock));
if(ret<0)
{
printf("connect is failed ...error is :%d,error string is:%d\n",errno,strerror(errno));
return 1;
}
printf("connect success\n")
while(1)
{
printf("client:#");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
write(sock,buf,sizeof(buf));
if(strncasecmp(buf,"quit",4)==0)
{
printf("quit!\n");
break;
}
printf("please wait..\n");
read(sock,buf,sizeof(buf));
printf("server:$%s\n",buf);
}
close(sock);
return 0;
}
(2)select服务器
# include<stdio.h>
# include<string.h>
# include<stdlib.h>
# include<sys/socket.h>
# include<sys/types.h>
# include<sys/select.h>
# include<netinet/in.h>//not clear
#define _PORT_ 8080
#define _MAX_SIZE_ 10
#define _BACK_LOG_ 3
#define _BUF_SIZE_ 1024
int fd_arr[_MAX_SIZE_];
int max_fd=0;
static void init_fd_arr()
{
int i=0;
for(;i<_MAX_SIZE_;i++)
{
fd_arr[i]=-1;
}
}
static int add_fd_arr(int fd)
{
int i=0;
for(;i<_MAX_SIZE_;i++)
{
if(fd_arr[i]==-1)
{
fd_arr[i]=fd;
return 0;
}
}
return 1;
}
static int remove_fd_arr(int fd)
{
int i=0;
for(;i<_MAX_SIZE_;i++)
{
if(fd_arr[i]==fd)
{
fd_arr[i]=-1;//remove target fd
break;
}
}
return 0;
}
static int reload_fd_set(fd_set *fd_setp) //not clear
{
int i=0;
for(;i<_MAX_SIZE_;i++)
{
if(fd_arr[i]!=-1)
{
FD_SET(fd_arr[i],fd_setp);
if(fd_arr[i]>max_fd)
{
max_fd=fd_arr[i];
}
}
}
return 0;
}
static void print_msg(int i,char buf[])
{
printf("fd:%d,msg:%s\n",i,buf);
}
int select_server()
{
struct sockaddr_in ser;
struct sockaddr_in cli;
fd_set fds;
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("create socket error");
return 1;
}
printf("create socket success\n");
int yes=1;//not clear
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
memset(&ser,'\0',sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(_PORT_);
ser.sin_addr.s_addr=INADDR_ANY;//auto fill with IP
if(bind(fd,(struct sockaddr*)&ser,sizeof(ser))<0)
{
perror("bind error");
return 2;
}
printf("bind socket success\n");
init_fd_arr();
add_fd_arr(fd);
FD_ZERO(&fds);
if(listen(fd,_BACK_LOG_)<0)
{
perror("listen error");
return 3;
}
printf("listen socket success\n");
while(1)
{
#ifdef _SELECT_
reload_fd_set(&fds);
struct timeval timeout=(30,0);
switch(select(max_fd+1,&fds,NULL,NULL,&timeout))
{
case -1:
printf("select error,quit!\n");
exit(1);
break;
case 0:
printf("select timeout,continue wait..\n");
break:
default://return normal
{
int index=0;
for(;index<_MAX_SIZE_;index++)
{
if(index==0&&fd_arr[index]!=-1&&FD_ISSELECT(fd_arr[index],&fds))//new accept
socklen_t len=sizeof(cli);
memset(&cli,'\0',sizeof(cli));
int new_fd=accept(fd,(struct sockaddr*)&cli,&len);
if(-1!=new_fd)
{
printf("get a new requeset!\n");
if(1==add_fd_arr(new_fd))//add new fd failed
{
perror("fd arr is full.close fd !\n")
close(new_fd);
}
}
continue;
}
if(fd_arr[index]!=-1&&FD_ISSET(fd_arr[index],&fds))//just for read fd
{
char buf[_BUF_SIZE_];
memset(buf,'\0',sizeof(buf));
ssize_t size=recv(fd_arr[index],buf,sizeof(buf)-1,0);//read data
if(size==0||size==-1)
{
printf("remote client close. size is:,%d\n",size);
remove_fd_arr(fd_arr[index]);
close(fd_arr[index]);
FD_CLR(fd_arr[index],&fds);
}
else
{
print_msg(index,buf);
}
}
}
}
break;
}
#endif
}
}
int main ()
{
select_server();
return 0;
}
(3)epoll服务器
# include<stdio.h>
# include<stdlib.h>
# include<fcntl.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
# include<sys/epoll.h>
static int startup(const char *_ip,int _port)//create a socket
{
//sock()
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket error");
exit(2);
}
//struct sockaddr_in
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=inet_addr(_ip);
//bind()
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind error");
exit(3);
}
//listen()
if(listen(sock,5)<0)
{
perror("listen error");
exit(4);
}
return sock;
}
static void usage(const char *proc)
{
printf("Usage:%s [ip] [port]",proc);
}
static int set_noblock(int sock)
{
int fl=fcntl(sock,F_GETFL);
return fcntl(sock,F_SETFL,fl|O_NONBLOCK);
}
int main (int argc,char *argv[])
{
// if()
if(argc!=3)
{
usage("argv[0]");
exit(1);
}
int listen_sock=startup(argv[1],atoi(argv[2]));
//create a epoll
int epfd=epoll_create(256);
if(epfd<0)
{
perror("epoll_create");
exit(5);
}//error
else
{
struct epoll_event _ev;
_ev.events=EPOLLIN;
_ev.data.fd=listen_sock;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&_ev);//add a socket
struct epoll_event _ready_ev[128];
int _ready_evs=128;
int _timeout=-1;//block
int nums=0;
int done=0;
while(!done)
{
switch(nums=(epoll_wait(epfd,_ready_ev,_ready_evs,_timeout)))
{
case 0:
printf("time out...\n");
break;
case -1:
perror("epoll_wait");
break;
//default
default:
{
int i=0;
for(;i<nums;++i)
{
int _fd=_ready_ev[i].data.fd;
if(_fd==listen_sock&&_ready_ev[i].events&EPOLLIN)
{
//get a new link
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);//need finish
if(new_sock>0)
{
printf("client info,socket:%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
//link success
//then add to epoll
_ev.events=EPOLLIN | EPOLLET;
_ev.data.fd=new_sock;
set_noblock(new_sock);
epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&_ev);
}
else
{
if(_ready_ev[i].events&EPOLLIN)
{
char buf[102400];
memset(buf,'\0',sizeof(buf));
//read or write
ssize_t _s=recv(_fd,buf,sizeof(buf)-1,0);
if(_s>0)
{
printf("client#%s\n",buf);
_ev.events=EPOLLOUT | EPOLLET;
_ev.data.fd=_fd;
epoll_ctl(epfd,EPOLL_CTL_MOD,_fd,&_ev);
}
else if(_s==0)
{
printf("client close..\n");
//close server
//close epoll
epoll_ctl(epfd,EPOLL_CTL_DEL,_fd,NULL); close(_fd);
}
else
{
perror("error");
}
}
else if(_ready_ev[i].events&EPOLLOUT)
{
const char *msg="HTTP/1.1 200 OK\r\n\r\n<h1>hello world +_+</h1>\r\n";
send(_fd,msg,strlen(msg),0);
epoll_ctl(epfd,EPOLL_CTL_DEL,_fd,NULL);
close(_fd);
}
}
}
}
break;
}
}
}
}
}
//exc 1. the diference of select poll and epoll
//2.epoll
// http xieyi(zhengdehenjindian)kanzhegewangye