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