一、select
设置select代理,将lfd交给select,监听socket连接,若有连接,则通知server进行accpet接收,并将cfd也交给select,因此,select有两个监听,一个监听连接(lfd),一个监听传输(cfd)
int select(int nfds, fd_set * readfds, fd_set *writefds,
fd_set * errorfds, struct timeval * timeout);
//返回值:所有监听集合中,满足对应事件的文件描述符总数
/*
nfds: 所有监听的文件描述符中最大的文件描述符+1
readfds,writefds,exceptfds监听集合,都是位图集合和传入传出参数
timeout:NULL阻塞等待,0非阻塞监听,其他值则等待此时间
*/
//接下来介绍位图的操作
void FD_ZERO(fd_set *fdset);//清空集合
void FD_SET(fd, fd_set *fdset);//添加文件描述符到监听集合
void FD_CLR(fd, fd_set *fdset);//将一个文件描述符从监听集合删除
int FD_ISSET(fd, fd_set *fdset);//判断文件描述符是否在监听集合中
void FD_COPY(fd_set *fdset_orig, fd_set *fdset_copy);
1.1 select 实现server分析
socket();
bind();
listen();
fd_set rset;
FD_ZERO(&rset);
FD_set(lfd,&rset);
select(lfd+1,&rset)
accept
read
1.2 select实现server
#define port 8100
int main(int argc,char *argv[])
{
int lfd,cfd,maxfd=0;
char buf[80];
struct sockaddr_in client_addr,server_addr;
socklen_t client_addr_len;
fd_set rset,allset;
//创建所需的变量,包含文件描述符,缓冲区,sockaddr,文件描述符集等
bzero(&server_addr, sizeof(server_addr));
lfd=socket(AF_INET,SOCK_STREAM,0);
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(port);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
listen(lfd,128);
printf("listen sucess-----\n");
//进行server的bind,linsten操作
maxfd=lfd;
FD_ZERO(&allset);
FD_SET(lfd,&allset);
//对读集合进行初始化
while(1){
printf("step while-----\n");
rset=allset;//拷贝集合
int sret=select(maxfd+1,&rset,NULL,NULL,NULL);//进行select
if(sret<0)perror("select error");
printf("select sucess-----\n");
if(FD_ISSET(lfd,&rset)){//如果有客户端请求,则开始accept
socklen_t len=sizeof(client_addr);
cfd=accept(lfd,(struct sockaddr*)&client_addr,&len);
printf("accept sucess-----\n");
FD_SET(cfd,&allset);
if(maxfd<cfd)maxfd=cfd;
if(sret==1)continue;
}
//accept结束后,开始对所有写操作的客户端进行读操作
for(int i=lfd+1;i<=maxfd;i++)
if(FD_ISSET(i,&rset)){
int recount=read(cfd,buf,sizeof(buf));
printf("read ready-----\n");
if(recount==0){
close(i);
FD_CLR(i,&allset);
break;
}else
printf("read success print:\n");
write(STDOUT_FILENO,buf,recount);
write(cfd,buf,recount);
}
}
close(lfd);
return 0;
}
1.3 select 特点
缺点
- 监听上限受文件描述符限制(最大1024)
- 只能自己添加逻辑,去提高轮询效率(比如只有3号和1024号,需要轮询1000多次)
优点 - 跨平台
2.1.4 select优化
为了提高轮询效率,增加一个数组,用来记录有操作的文件描述符,以免进行1024次轮询。
for(int i=0;i<1024;i++)client[i]=-1;
int i;
while(1){
printf("第%d次循环\n",count++);
rset=allset;
sret=select(maxfd+1,&rset,NULL,NULL,NULL);
if(sret<0)perror("select error");
printf("select sucess-----\n");
if(FD_ISSET(lfd,&rset)){
socklen_t len=sizeof(client_addr);
cfd=accept(lfd,(struct sockaddr*)&client_addr,&len);
for (i = 0; i < 1024; i++)
if (client[i] < 0) {
client[i] = cfd; /* 保存accept返回的文件描述符到client[]里 */
break;
}
if(i==1024)printf("too many clients");
if(i>maxi)maxi=i;
FD_SET(cfd,&allset);
if(maxfd<cfd)maxfd=cfd;
if(sret==1)continue;
}
for( i=0;i<=maxi;i++){
int curfd=client[i]; //这一步必须做
if(curfd<0)continue;
if(FD_ISSET(curfd,&rset)){
int recount=read(curfd,buf,sizeof(buf));
printf("read ready-----\n");
if(recount==0){
close(curfd);
FD_CLR(curfd,&allset);
client[i]=-1;
}else{
write(curfd,buf,recount);}
}
}
}
close(lfd);
return 0;
}
二、 poll
#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
2.1 poll特点
- 优点
自带数组,避免轮询过多
可以讲监听集合和返回集合分离
没有1024大小的限制 - 缺点
还是需要轮询
不能跨平台
三、突破1024文件描述符限制
cat /proc/sys/fs/file-max
查看当前系统最大描述符上限
sudo vim /etc/security/limits.conf //修改上限
- soft nofile 3000
- hard nofile 50000
四、 epoll
本质是红黑树
#include<sys/epoll.h>
int epoll_create(int size)
//size 监听节点数量且仅供参考,可动态扩容
//return:返回红黑树根结点的epfd
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);//操作监听
/*
op: EPOLL_CTL_ADD:添加fd监听到红黑树
EPOLL_CTL_MOD 修改epoll红黑树上的监听事件
EPOLL_CTL_DEL 删除监听
event:
struct epoll_event{
uint32_t events;
epoll_data_t data;
};
/*
events: EPOLLIN/EPOLLOUT/EPOLLERR/EPOLLET
data: int fd
void *ptr
*/
int epoll_wait(int epfd,struct epoll_events*events,int maxevents,int timeout);
/*
events: 传出参数,event[数组],将满足的event传出
maxevents:数组元素总个数,而非数组大小
timeout:-1阻塞,0非阻塞,>0超时时间
return:>0满足监听总个数,可作为循环上限
*/
4.1 epoll实现server思路
socket->bind->listen->epoll_create->epoll_ctl(lfd)
while()->epoll_wait()->for(ep[i])->if(ep[i].data.fd==lfd)accept->else write
4.2 epoll的事件模型
4.2.1 ET(edge trigger)边缘触发
event.events =EPOLLIN | EPOLLET
缓冲区剩余数据不会导致wait返回
只支持非阻塞模式
设置非阻塞状态:
int flag=fcnl(cfd,F_GETFL);
flag|=O_NOBLOCK;
fcnl(cfd,F_SETFL,flag);
4.2.2 LT(level trigger)水平触发 (默认)
缓冲区剩余数据会导致wait返回
网友评论