關於TCP ,select,epoll伺服器的區別與聯絡
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