多路IO之select
優點:單進程下支持高並發,可以跨平台
缺點:多次從內核到應用,應用到內核的數組拷貝;
每次內核都會重置填寫的數據
最大支持1024客戶端,原因在於fd_set定義使用了FD_SETSIZE,大小為1024;
以下是select模型server代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/select.h>
#include <ctype.h>
int main(){
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv;
bzero(&serv,sizeof(serv));
serv.sin_port = htons(8888);
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = htonl(INADDR_ANY);
//reset port
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bind(lfd,(struct sockaddr*)&serv,sizeof(serv));
listen(lfd,128);
fd_set rdset;//readevent
fd_set allset;//bak readevent
FD_ZERO(&rdset);
FD_SET(lfd,&rdset);
allset = rdset;
struct sockaddr_in client;
socklen_t len = sizeof(client);
int nfds = lfd;
int nready = 0;
while(1){
rdset = allset;//備份傳給內核
//阻塞等待事件就緒
nready = select(nfds+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(lfd,&rdset)){
//有新連接事件,將得到的CFD加入到集合
int cfd = accept(lfd,(struct sockaddr*)&client,&len);
if(cfd > 0){
//rdset是一個傳入傳出集合,每次都會重置
FD_SET(cfd,&allset);
}
if(nfds < cfd){
nfds = cfd;
}
nready --;
//如果就緒事件就一個,且是新連接就跳出循環
if(nready <= 0)
continue;
}
int i = 0;
for(i = lfd+1;i<nfds +1;i++){
if(FD_ISSET(i,&rdset)){
char buf[256] = {0};
int ret = read(i,buf,sizeof(buf));
if(ret < 0){
perror("read err");
close(i);
FD_CLR(i,&allset);
}
else if (ret == 0){
close(i);//client closed
FD_CLR(i,&allset);
}
else{
int j = 0;
for(;j<ret;j++){
buf[j] = toupper(buf[j]);
}
write(i,buf,ret);
}
if(--nready <= 0)
break;//no event.jump for.
}
}
}
close(lfd);
return 0;
}
多路IO之POLL模型:
POLL的原理與select相同,比select改進的地方:
1,請求和返回分離,避免每次都要重設數組
2,可以突破1024限制,poll是由打開文件的上限決定,可以使用ulimit命令查看上限
3,不能跨平台
poll代碼解析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <poll.h>
#include <arpa/inet.h>
#define _MAXLINE_ 80
#define _SERVER_PORT_ 8888
#define _MAX_OPEN 1024
int main(){
int i,maxi;
char strIP[16];
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct pollfd client[_MAX_OPEN];
struct sockaddr_in clientaddr,servaddr;
int len = sizeof(clientaddr);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(_SERVER_PORT_);
//set reuse port
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(lfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0){
perror("bind err");
return -1;
}
listen(lfd ,128);
client[0].fd = lfd;//監聽第一個文件描述符
client[0].events = POLLIN;//監聽讀事件
for(i = 1; i <_MAX_OPEN;i++){
client[i].fd = -1;//用-1初始化,因為0也是描述符
}
maxi = 0;//記錄client數組有效最大元素下標
while(1){
int nready = poll(client,maxi+1,-1);
//判斷是否有新連接
if(client[0].revents & POLLIN){
//此處不會阻塞
int cfd = accept(lfd,(struct sockaddr*)&clientaddr,&len);
printf("recv form %s:%d\n",
inet_ntop(AF_INET,&clientaddr.sin_addr,strIP,sizeof(strIP)),
ntohs(clientaddr.sin_port));
for(i = 1;i<_MAX_OPEN;i++){
if(client[i].fd < 0){
client[i].fd = cfd;
break;
}
}
if(i == _MAX_OPEN){
//最大客戶連接上限
printf("max connected...\n");
continue;
}
client[i].events = POLLIN;
if (i > maxi)
maxi = i;
if(--nready <= 0)
continue;//沒有更多就緒事件,繼續回到POLL阻塞
}
for(i = 1;i<=maxi;i++){
//前面的IF沒有滿足,說明沒有新連接,而是讀事件
int cfd;
//先找到第一個大於0的文件描述符
if((cfd = client[i].fd) < 0)
continue;
if(client[i].revents & POLLIN){
char buf[_MAXLINE_] = {0};
int ret = read(cfd,buf,sizeof(buf));
if(ret < 0){
if(errno == ECONNRESET){
printf("client[%d] aborted connection\n",i);
close(cfd);
client[i].fd =-1;
//POLL中不需要像SELECT一樣移除,直接置-1即可
}
else{
perror("read error");
exit(-1);
}
}
else if(ret == 0){
printf("client[%d] closed\n",i);
close(cfd);
client[i].fd = -1;
}
else{
write(cfd,buf,ret);
}
if(--nready <= 0)
break;
}
}
}
close(lfd);
return 0;
}
多路IO之EPOLL:
不管是select,還是poll,都需要遍歷數組輪詢,而且select僅支持1024個客戶端,在大量並發,少量活躍的情況下效率較低,也就滋生了epoll模型。
1,可以突破1024限制,不跨平台
2,無須遍歷整個文件描述符集,只需遍歷被內核IO事件異步喚醒,而加入ready隊列的文件描述符。
3,除了select/poll的IO事件水平觸發(level triggered)外,還提供邊沿觸發(edge Triggered),可以緩存IO狀態,減少epoll_wait調用,提高效率
代碼原型如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
int main(){
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv;
bzero(&serv,sizeof(serv));
serv.sin_addr.s_addr = htonl(INADDR_ANY);
serv.sin_port = htons(8888);
serv.sin_family = AF_INET;
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bind(lfd,(struct sockaddr*)&serv,sizeof(serv));
listen(lfd,128);
struct sockaddr_in client;
socklen_t len = sizeof(client);
//create epoll node
int epfd = epoll_create(1);
struct epoll_event ev,evs[1024];
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
while(1){
int nready = epoll_wait(epfd,evs,1024,-1);
if(nready > 0){
int i = 0;
for(;i<nready;i++){
if(evs[i].data.fd == lfd){
if(evs[i].events & EPOLLIN){
int cfd = accept(lfd,(struct sockaddr*)&client,&len);
if(cfd > 0){
ev.data.fd = cfd;
//將新的連接上樹
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
}
}
}
else{
//處理讀事
if(evs[i].events & EPOLLIN){
char buf[256]={0};
int ret = read(evs[i].data.fd,buf,sizeof(buf));
if(ret > 0){
write(evs[i].data.fd,buf,ret);
}
else if(ret == 0){
//client closed
close(evs[i].data.fd);
ev.data.fd = evs[i].data.fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&ev);
}
else{
perror("read err");
close(evs[i].data.fd);
ev.data.fd = evs[i].data.fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&ev);
}
}
}
}
}
}
close(epfd);
close(lfd);
return 0;
}
