网站的网络公司温州网站建设得花多少钱
2026/6/10 1:58:32 网站建设 项目流程
网站的网络公司,温州网站建设得花多少钱,大型车产品网站建设,wordpress移动端加底部导航栏目录介绍类型selectpollepoll介绍 什么是IO多路复用#xff1f; IO多路复用是一种高效的IO处理技术#xff0c;它允许单个线程同时监控多个文件描述符#xff08;如网络连接、文件、管道等#xff09;#xff0c;当其中任何一个准备好读写操作时#xff0c;系统会立即通…目录介绍类型selectpollepoll介绍什么是IO多路复用IO多路复用是一种高效的IO处理技术它允许单个线程同时监控多个文件描述符如网络连接、文件、管道等当其中任何一个准备好读写操作时系统会立即通知程序进行处理。这种机制避免了传统阻塞IO中每个连接都需要独立线程等待的问题极大提升了并发处理能力。具体来说当程序调用select、poll或epoll等系统调用时内核会同时检查所有被监控的文件描述符状态只返回那些已经就绪的IO操作这样程序就能在非阻塞的情况下高效处理成千上万个并发连接是现代高并发服务器的核心技术基础。传统阻塞IO的问题// 每个连接需要一个线程funchandleConn(conn net.Conn){buf:make([]byte,1024)// 这里会阻塞等待数据n,err:conn.Read(buf)// 线程挂起直到有数据// 处理数据...}IO多路复用一个线程监控所有连接// 一个线程监控所有连接for{// 询问内核哪些连接有数据了readyConns:poll()// 返回有数据的连接列表for_,conn:rangereadyConns{gohandleData(conn)// 只处理有数据的连接}}类型select在传统阻塞IO模型下每个网络连接都需要独立线程阻塞等待数据造成大量线程资源浪费而select机制通过IO多路复用技术实现了单线程对最多1024个连接的高效批量监控将分散的阻塞等待转化为统一的事件驱动处理显著提升了网络IO的并发处理能力和系统吞吐量。下面是一个select的使用示例// 用户空间告诉内核要监控哪些fdFD_SET(fd1,readfds);// 监控fd1读FD_SET(fd2,readfds);// 监控fd2读FD_SET(fd3,writefds);// 监控fd3写// 进入内核阻塞等待事件select(nfds,readfds,writefds,exceptfds,timeout);// 返回后内核告诉你哪些fd就绪了if(FD_ISSET(fd1,readfds)){// fd1有数据了可以读了}if(FD_ISSET(fd3,writefds)){// fd3可以写了}内核层发生了什么// 简化版内核逻辑intdo_select(intn,fd_set*readfds,fd_set*writefds,fd_set*exceptfds){while(1){intretval0;// 遍历所有fd0到n-1for(inti0;in;i){structfile*filefget(i);if(!file)continue;// 检查这个fd是否有读事件if(readfdsFD_ISSET(i,readfds)){if(file-f_op-poll(file)POLLIN){FD_SET(i,readfds);// 标记为就绪retval;}}// 检查写事件...// 检查异常事件...}if(retval0)returnretval;// 有fd就绪了if(timeouttime_after(timeout))return0;// 超时schedule();// 让出CPU睡眠等待}}可以看出内核层每次需要遍历所有的fd。为什么最多只能等待1024个连接因为select使用bitmap来存储文件描述符且这个长度由于历史包袱被限制死1024了。不好改所以情况就是这么个情况~// POSIX标准规定#defineFD_SETSIZE1024#includesys/select.h#includestdio.hintmain(){fd_set readfds;// 尝试设置第1024个fdFD_SET(1023,readfds);// ✅ 成功// 尝试设置第1025个fdFD_SET(1024,readfds);// ❌ 数组越界printf(FD_SETSIZE %d\n,FD_SETSIZE);// 输出1024return0;}pollpoll机制针对select的1024连接限制进行了关键改进摒弃了固定大小的位图结构转而采用动态数组来存储文件描述符信息这种设计不仅突破了连接数量的硬性上限使其能够轻松支持成千上万的并发连接还提供了更灵活的事件管理能力为大规模网络应用奠定了基础架构优势。像下面这样#includepoll.hintmain(){intlisten_socksocket(AF_INET,SOCK_STREAM,0);bind(listen_sock,...);listen(listen_sock,5);// poll用数组不是位图structpollfdfds[2000];// ✅ 可以轻松超过1024intnfds0;// 添加监听socketfds[0].fdlisten_sock;fds[0].eventsPOLLIN;// 监听读事件fds[0].revents0;// 内核返回的事件nfds1;while(1){// 调用pollintretpoll(fds,nfds,-1);if(ret0){// 检查监听socketif(fds[0].reventsPOLLIN){// 有新连接intnew_clientaccept(listen_sock,...);// 添加到poll数组fds[nfds].fdnew_client;fds[nfds].eventsPOLLIN;fds[nfds].revents0;nfds;}// 检查客户端socketfor(inti1;infds;i){if(fds[i].reventsPOLLIN){// 有数据charbuffer[1024];intnread(fds[i].fd,buffer,sizeof(buffer));if(n0){// 客户端断开从数组移除close(fds[i].fd);// 用最后一个元素覆盖当前元素fds[i]fds[nfds-1];nfds--;i--;// 重新检查当前位置}else{handle_data(buffer,n);}}}}}}但是轮询所有的fd的问题依然存在呢~~时间复杂度还是O(n)的epollepoll在poll基础上实现了革命性优化其命名中的e代表事件event彰显其事件驱动的核心设计理念。与poll的主动轮询机制不同epoll采用被动事件通知模式通过内核维护的就绪队列和回调机制仅当文件描述符状态发生变化时才触发处理这种设计不仅避免了无效遍历更将时间复杂度从O(n)降至O(1)使单线程能够高效管理数十万并发连接真正实现了高性能的IO多路复用。下面是一段示例代码// ep_poll - epoll的核心函数staticintep_poll(structeventpoll*ep,structepoll_event__user*events,intmaxevents,structtimespec*timeout){intres,avail;structepitem*epi;structepoll_event__user*eventpoll_buf;unsignedlongflags;// 关键直接获取就绪队列O(1)复杂度mutex_lock(ep-mtx);availep-ovflist!EP_UNACTIVE_PTR;if(!avail)availep-rdllink.next!ep-rdllist;// 检查就绪队列是否为空mutex_unlock(ep-mtx);if(avail){// 有就绪的fd直接拷贝给用户空间resep_send_events(ep,events,maxevents);returnres;}// 没有就绪的fd睡眠等待for(;;){set_current_state(TASK_INTERRUPTIBLE);// 再次检查就绪队列mutex_lock(ep-mtx);availep-ovflist!EP_UNACTIVE_PTR;if(!avail)avail!list_empty(ep-rdllist);mutex_unlock(ep-mtx);if(avail||timed_out)break;// 睡眠等待事件if(!schedule_hrtimeout_range(to,slack,HRTIMER_MODE_ABS))timed_out1;}__set_current_state(TASK_RUNNING);returnep_send_events(ep,events,maxevents);}// ep_send_events - 发送就绪事件staticintep_send_events(structeventpoll*ep,structepoll_event__user*events,intmaxevents){structepitem*epi,*tmp;structepoll_eventevent;unsignedlongflags;intcnt0;// 关键只遍历就绪队列不是遍历所有fdlist_for_each_entry_safe(epi,tmp,ep-rdllist,rdllink){// 检查事件是否仍然有效if(ep_item_poll(epi,pt)){// 拷贝到用户空间__put_user(epi-event.events,event.events);__put_user(epi-event.data,event.data);__copy_to_user(events[cnt],event,sizeof(event));if(cntmaxevents)break;}}returncnt;}// 每个epoll实例structeventpoll{spinlock_tlock;// 自旋锁structmutexmtx;// 互斥锁wait_queue_head_twq;// 等待队列structlist_headrdllist;// 关键就绪fd链表structrb_root_cachedrbr;// 红黑树存储所有监听的fdstructepitem*ovflist;// 溢出链表};// 每个监听的fdstructepitem{union{structrb_noderbn;// 红黑树节点structrcu_headrcu;};structlist_headrdllink;// 就绪链表节点structepitem*next;// 溢出链表structepoll_filefdffd;// 文件描述符信息intnwait;// 等待队列数量structlist_headpwqlist;// 等待队列structeventpoll*ep;// 所属的epoll实例structlist_headfllink;// 文件链表structepoll_eventevent;// 事件类型};下面是添加一个fd到epoll的例子。当fd就绪的时候它会触发一个回调添加到就绪队列里面使用这种事件驱动的方式把O(n)优化到了O(1)。这样就使得效率大大提升了// ep_insert - 添加fd到epollstaticintep_insert(structeventpoll*ep,structepoll_event*event,structfile*tfile,intfd){structepitem*epi;structep_pqueueepq;// 分配epitemepikmem_cache_alloc(epi_cache,GFP_KERNEL);// 初始化epitemepi-epep;epi-event*event;epi-ffd.filetfile;epi-ffd.fdfd;// 添加到红黑树O(log n)ep_rbtree_insert(ep,epi);// 设置回调函数 - 关键epq.epiepi;init_poll_funcptr(epq.pt,ep_ptable_queue_proc);// 调用文件的poll函数注册回调reventstfile-f_op-poll(tfile,epq.pt);// 如果已经有事件直接添加到就绪队列if(reventsevent-events){list_add_tail(epi-rdllink,ep-rdllist);}return0;}// 回调函数 - fd就绪时被调用staticvoidep_ptable_queue_proc(structfile*file,wait_queue_head_t*whead,poll_table*pt){structep_pqueue*epqcontainer_of(pt,structep_pqueue,pt);structepitem*epiepq-epi;// 添加到等待队列设置回调add_wait_queue(whead,epi-wait);}golang的网络轮询器就是基于epoll的实现这使得golang的io效率非常高让用户的同步代码能够享受到异步的性能

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询