ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux21:网络编程

Linux21:网络编程 IO多路复用定义理解一个线程同时监控很多个文件描述符socket/fd哪个 IO 准备好了就处理哪个不用开一堆线程。三种多路复用接口selectpollepollIO多路复用和这三个服用接口有啥关系IO 多路复用 要实现的功能select /poll/epoll 实现这个功能的 3 套工具函数select你的程序 → 调用select()【多路复用接口】 → 内核帮你监视一堆fd → 阻塞等待直到有fd就绪 select返回 → 你自己遍历找到就绪fd → read/write读写数据intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);缺点最大 1024 限制每次都拷贝全部 fd返回后要循环扫描所有 fd并发越大效率越差。select 返回时内核会改写这个位图把没就绪的 fd 对应的 bit 清零只保留就绪 fd 的 bit 为 1。timeout是select的最后一个参数timeout 控制 select 的阻塞行为决定 select 要在内核等多久。⚠️重点timeval 是传入传出参数Linux 下 select 返回之后内核会修改 timeval 的值减去已经消耗掉的等待时间。IO 多路复用监控的对象就是一堆 fd可以是普通文件 fd可以是 socket网络连接最常用可以是管道、eventfd、设备 fdselect 的 fd_set每一个 bit 位就对应一个 fd 数字。fd5 → 操作 fd_set 的第 5 比特位。FD_SET(5,rset);// 把编号为5的文件描述符加入select监听集合nfds 所有监听fd里面最大的那个fd 1socket就绪条件socket 读就绪POLLIN /readfds满足下面任意一条读就绪内核接收缓冲区有数据收到对方发来的数据→ 可读。对方正常关闭连接FIN 报文对端 close。此时read()会返回 0。⚠️这里非常关键收到 FINsocket 直接读就绪read 返回 0不是报错。连接发生错误比如 RST 复位读就绪read 会返回 -1errno 报错。监听 socketlisten 之后的 accept fd有新客户端连接到达完成三次握手。读就绪来了数据对方关连接 (FIN)socket 出错 (RST)listen fd新连接到来写就绪记忆发送缓冲区有空闲位置非阻塞 connect 连接建立完成socket 出错核心理解socket和select就绪条件IO多路复用之间啥关系在我的理解下Linux 一切皆文件socket、普通文件、管道全部抽象为文件描述符 fd统一使用 read/write 接口。socket 调用 socket ()得到 fd内核维护这个 socket 对应的接收、发送缓冲区维护 TCP 连接状态。IO 多路复用是模型思想单线程同时监视多个 fdselect 是实现这个模型的系统调用接口。过程如下Socketfd交给 select 监视 → 内核检查 socket 缓冲区状态看是否满足读 / 写就绪条件 → 满足就标记该 fd 就绪 → select 返回 → 用户代码检测 fd调用 read/accept/write。工具函数epollselect /poll/epoll 拷贝对比select用户态 fd_set → 拷贝到内核内核改完再拷贝回来。每次系统调用都全量拷贝。poll用户态 pollfd 数组 → 拷贝到内核内核填 revents拷贝回用户。每次系统调用全量拷贝。epollepoll_ctl(ADD)一次性把 fd 注册拷贝进内核放到内核红黑树之后不再重复拷贝有事件发生内核回调把 fd 放到内核就绪链表epoll_wait只把就绪链表里面少量就绪事件拷贝回用户态没有就绪就什么都不拷贝。select/poll每次调用都搬运全部 fdepoll注册搬一次wait 只搬已经出事的 fd。epoll三个重点api//1. 创建epoll实例返回epollfdintepoll_create(intsize);//2. 向epoll实例 添加/修改/删除要监听的fd和事件intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);//3. 阻塞等待事件就绪拿到已经就绪的事件集合intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);epoll_ctlepfdepoll_create /epoll_create1 返回的 epoll 实例 fdop操作类型三选一EPOLL_CTL_ADD把 fd 加入 epoll注册事件EPOLL_CTL_MOD修改已经注册过 fd 的监听事件EPOLL_CTL_DEL从 epoll 中移除这个 fdfd要操作的目标文件描述符socket、管道等eventstruct epoll_event 结构体指针struct epoll_event宏 含义EPOLLIN 读事件缓冲区有数据可读对端关闭连接也会触发 EPOLLINEPOLLOUT 写事件发送缓冲区有空位可以 send 写数据EPOLLPRI 带外数据TCP 紧急数据很少用EPOLLERR 错误事件不需要注册内核自动监听EPOLLHUP 挂起对端断开不需要注册内核自动监听EPOLLET 边缘触发 ET默认是 LT 水平触发需要手动或上这个标志epoll_wait 等待事件到来intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);epfdepoll 实例events用户态数组首地址输出参数内核把就绪事件填到这里maxevents数组最大元素个数不能为 0。代表这一次系统调用最多返回多少就绪事件。⚠️这个是用户态数组大小和 epoll_create 的 size 完全两码事。timeout阻塞超时单位毫秒-1无限阻塞直到有事件0非阻塞立刻返回没有事件返回 00等待多少毫秒超时直接返回 0api核心理解我认为epoll的api流程分为三部分epoll_create1拿到一个内核小本子 epfd。创建服务端 listen_fd用来接收客户端连接。epoll_ctl (ADD)把 listen_fd 写到本子上监视 EPOLLIN。while (1) 死循环epoll_wait (epfd, …) 蹲点等事件。如果出事的 fd 是 listen_fd说明来了新客户端accept 拿到 client_fd。把 client_fd 设置非阻塞epoll_ctl 加到本子上。如果出事的是 client_fd调用 recv 读数据如果断开close 关掉 socket。epoll_create(int size)你去申请一个小本子epoll 对象。这个本子存在内核里面。本子用来记录我要监视哪些 socket。2.epoll_ctl往本子上写 / 改 / 删记录最重要参数 1 epfd本子编号参数 2 op三种操作三选一EPOLL_CTL_ADD把 socket 登记到本子上开始监视它EPOLL_CTL_MOD这个 socket 已经登记了改监视的条件EPOLL_CTL_DEL把这个 socket 从本子擦掉不再监视参数 3 fd你要监视的 socket 编号比如客户端连接参数 4 ev结构体 epoll_event3.epoll_wait蹲点等待事件发生你问内核“本子上登记的那些 socket有没有哪个出事了”参数epfd那个内核小本子events你自己在用户代码开辟的数组用来接收结果。内核把已经出事的 socket 信息拷贝到这个数组。maxevents这个数组最多能放多少条结果。比如数组开 4096这里填 4096。这个是你数组大小不是 epoll 最多能监听多少连接。epoll 可以监听几万只是这一次最多返回 4096 个事件。timeout等待多久-1死等有事件才返回0不等立刻返回0等多少毫秒返回值 retret0有 ret 个 socket 出事了结果存在 events [0]~events [ret‑1]ret0超时啥也没发生ret-1出错epoll 的两种模式区别LT 水平触发默认不加 EPOLLET只要 socket 缓冲区还有数据没读完每次 epoll_wait 都会反复提醒你。好比桌子上有苹果你拿走一个还有剩下就一直喊你。ET 边缘触发加 EPOLLET只有新数据刚到来那一瞬间喊你一次桌子上来一堆苹果只喊你一次。如果你只拿一部分剩下的苹果放桌上再也不会喊你。ET 必须socket 设置非阻塞循环 recv 读到没数据为止。不然剩下的数据永远收不到。ET 必须非阻塞不然读完数据的时候 recv 会卡住整个线程。日志系统日志系统是什么程序运行的时候把发生的各种事情记录下来写到文件 / 控制台这套记录的整套机制就叫日志系统。为什么要日志分级1.开发环境 和 线上环境需求完全不一样2.出问题方便排查快速区分问题严重程度不同信息重要程度不一样分级就是为了可以灵活过滤不用把所有日志全部打出来。DEBUG调试 INFO普通信息 WARN警告 ERROR错误 FATAL致命两种模式异步日志同步日志①同步日志调用 log () 的时候直接调用 write 写磁盘。写磁盘很慢。如果 epoll 服务器业务线程正在处理客户端此时打日志磁盘卡住整个服务器就卡住了大并发下灾难。②异步日志生产常用业务线程生成日志字符串放到内存队列立刻返回继续处理网络事件。单独开辟一条日志线程专门从队列拿日志批量写入文件。业务线程不用等待磁盘 IO 完成性能高。
返回列表