IO复用-代替多线程-select,poll,epoll
select
//select(maxfd,rset,wset,eset,timeout); r读,w写,e错误,timeout多长时间轮询一次
//有事件就返回
//rset--> uL fds_bites[_FD_SIZE/(8*sizeof(long))]
//#define _FD_SIZE 1024 默认值1024,内核定义
//数组总大小_FD_SIZE/(8*sizeof(long)),每一位可以存(8*sizeof(long))个有效数据位,所以总共可以有_FD_SIZE个文件操作符
fd_set rfds , rset;
FD_ZERO(&rfds);
//fd[fd/32]=1<<(fd&31);
//fd&31 与fd%32的结果是一致的,目的是把数组响应位数的二进制第(fd)位置一
FD_SET(sockfd,&rfds);
int maxfd=sockfd;
while(1){
rset=rfds;
int nready=select(maxfd+1,&rset,NULL,NULL,NULL);//事件个数
if(FD_ISSET(sockfd,&rset)){
//定义clientaddr
int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
FD_SET(clientfd,&rfds);
maxfd=clientfd;
}
int i=0;
for(i=sockfd+1;i<=maxfd;i++){
//(原本我的理解是此时rfds=rset的)这里为什么是rset而不是不可以是rfds
//理解错误的地方,只有读事件即有客户端加入的时候,rfds=rset
// select阻塞在写事件的时候,没有执行FD_SET,此时的rfds=rset不一致
if(FD_ISSET(i,&rset)){
// 读事件,发送
//FD_CLR(i,&rfds);close(i);
//第一层bit_set。第二层系统io
}
}
}
缺点:
内核每一次都需要拷贝rset,拷贝整个,有很多不需要;IO数量有限制;参数较多,许都需要单独维护
//poll代替select
struct pollfd fds[1024]={0};
fds[sockfd].fd=socket;
fds[sockfd].event=POLLIN;
int maxfd=sockfd;
while(1){
int nready=poll(fds,maxfd+1,-1);
if(fds[sockfd].revents & POLLIN){
//定义clientaddr
int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
fds[clientfd].fd=clientfd;
fds[clientfd].events=POLLIN;
maxfd=clientfd;
}
int i=0;
for(i=sockfd+1;i<=maxfd;i++){
if(fds[i].revents & POLLIN) //清空判断123
fds[i].fd=-1;
fds[i].events=0;
close(i);
}
//epoll
//基于IO的划分 listenfd/clientfd 面向IO,IO越来越多,每个IO都要处理不同事件判断---符合正常思考
//基于事件划分(reactor,底层也是epoll) EPOLLIN/EPOLLOUT 面向事件,划分好事件,在事件里面处理不同的IO---代码不用很长
int epfd=epoll_create(1);//链表,1size没有意义,可以是任何数
struct epoll_event ev;
ev.data.fd=sockfd;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
struct epoll_event events[1024];
while(1){
int nready=epoll_wait(epfd,events,1024,-1);
for(int i=0;i<nready;i++){
int connfd=events[i].data.fd;
//基于IO状态做判断 (if---accept else----recv+send)
if(sockfd==connfd){
xxx;
ev.data.fd=clientfd;
//epoll的水平触发(FT)和边沿触发(ET),默认是水平触发,边沿触发(EPOLLIN|EPOLLET)
//水平触发的用处:先读TCP头部Length,在循环Length次接收数据
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
}
else if(events[i].events & EPOLLIN){
xxx;
if(count==0){
//注意:recv,send, epoll_ctl里面对应的都是connfd,而不是i
epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
close(connfd);
}
}
}
}
//连接池原理
#define BUFFER_LENGTH 128
struct conn_item{
int fd;//对应的clientfd
char buffer[];
int idx;//每次拼接从哪里开始
};
struct conn_item connlist[1024]={0};
//第一步
connlist[clientfd].fd=clientfd;
memset(connlist[clientfd].buffer,0,BUFFER_LENGTH);
connlist[clientfd].idx=0;
//第二步
char* buffer=connlist[connfd].buffer;
int idx=connlist[connfd].idx;
int count=recv(connfd,buffer+idx,BUFFER_LENGTH-idx,0);
connlist[connfd].idx+=count;
send(connfd,buffer+idx,count,0);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!