reactor的应用
1.梳理一下reactor网络编程
2.编程细节 返回值以及错误码
3.redis nginx,memcached reactor具体应用
网络编程关注的问题
1. 连接的建立
分为两种:服务端处理接受客户端的连接,服务端作为客户端连接第三方服务;
int clientfd = accept(listenfd,addr,sz); if (clientfd == -1 && errno == EWOLDBLOCK){ //说明全连接队列为空 }
int connectfd = socket(AF_INET,SOCK_STREAM,0); int ret = connect(connectfd, (struct sockaddr *)&addr, sizeof(addr)); errno = EINREOGRESS //正在建立连接 errno = EISCONN //连接建立成功
2.连接的断开
分为两种:主动断开和被动断开
close(fd)//主动断开 shutdown(fd,SHUT_RDWR); shutdown(fd,SHUT_RD);//主动关闭本地读端,对端写段关闭 shutdown(fd,SHUT_WR);//主动关闭本地写端,对端读段关闭
//被动:读端关闭 //有的网络编程需要支持半关闭状态 int n = read(fd, buf, sz); if (n == 0) { close_read(fd);//被动关闭读端 //close(fd); } int n =write(fd,buf,sz); if (n == -1 && errno == EPIPE){ close_write(fd); //被动关闭写端 //close (fd); }
3.消息的到达
从读缓冲区中读取数据
int n = read(fd,buf,sz);//n可以小于sz
int n = read(fd, buf, sz); if (n < 0) { // n == -1 if (errno == EINTR || errno == EWOULDBLOCK) break; close(fd); } else if (n == 0) { close(fd); } else { // 处理 buf }
如果n=-1,并且errno为EWOULDBLOCK表示都缓冲区为空,或者EINRT表示正向的错误,下次会继续读,如果是其他错误则close
4.消息发送完毕
往写缓冲去写数据
int n=write (fd,buf,sz);//n必须等于sz才算写成功
int n = write(fd, buf, dz); if (n == -1) { if (errno == EINTR || errno == EWOULDBLOCK) { return; } close(fd); }
如果返回值n=-1并且errno=EWOULDBLOCK,表示写缓冲区已经满了,下一次再去写。
网络IO职责
检测IO
io 函数本身可以检测 io 的状态;但是只能检测一个 fd 对应的状态;io 多路复用可以同时检测多个io的状态;区别是:io函数可以检测具体状态;io 多路复用只能检测出可读、可写、错误、断开等笼统的事件;
操作IO
只能使用io函数来进行操作;分为两种操作方式:阻塞io和非阻塞io;
阻塞IO和非阻塞IO
- 阻塞在网络线程;
- 连接的 fd 阻塞属性决定了 io 函数是否阻塞;
- 具体差异在:io 函数在数据未到达时是否立刻返回;
// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下; int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag | O_NONBLOCK);
阻塞和非阻塞正在数据拷贝阶段都是阻塞的,不同的是在数据准备阶段消息到达之前是不是的立刻返回
tips:最好设置为非阻塞的,比如设置读事件,此时取消,但读事件已经爆出来了,读缓冲区为空,此时如果读则会一直阻塞。
IO多路复用
io多路复用只负责检测io,不负责操作io。epoll:事件管理器
结构以及接口
struct eventpoll { // ... struct rb_root rbr; // 管理 epoll 监听的事件 struct list_head rdllist; // 保存着 epoll_wait 返回满⾜条件的事件 // ... }; struct epitem { // ... struct rb_node rbn; // 红⿊树节点 struct list_head rdllist; // 双向链表节点 struct epoll_filefd ffd; // 事件句柄信息 struct eventpoll *ep; // 指向所属的eventpoll对象 struct epoll_event event; // 注册的事件类型 // ... }; struct epoll_event { __uint32_t events; // epollin epollout epollel(边缘触发) epoll_data_t data; // 保存 关联数据 }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; int epoll_create(int size); /** op: EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL event.events: EPOLLIN 注册读事件 EPOLLOUT 注册写事件 EPOLLET 注册边缘触发模式,默认是水平触发 */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); /** events[i].events: EPOLLIN 触发读事件 EPOLLOUT 触发写事件 EPOLLERR 连接发生错误 EPOLLRDHUP 连接读端关闭 EPOLLHUP 连接双端关闭 */ int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
调用 epoll_create 会创建一个 epoll 对象;调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发时会调用回调函数 ( ep_poll_callback ),将触发的
事件拷贝到 rdlist 双向链表中;调用 epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;
epoll编程
连接建立
// 一、处理客户端的连接 // 1. 注册监听 listenfd 的读事件 struct epoll_event ev; ev.events |= EPOLLIN; epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev); // 2. 当触发 listenfd 的读事件,调用 accept 接收新的连接 int clientfd = accept(listenfd, addr, sz); struct epoll_event ev; ev.events |= EPOLLIN; epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev); // 二、处理连接第三方服务 // 1. 创建 socket 建立连接 int connectfd = socket(AF_INET, SOCK_STREAM, 0); connect(connectfd, (struct sockaddr *)&addr, sizeof(addr)); // 2. 注册监听 connectfd 的写事件 struct epoll_event ev; ev.events |= EPOLLOUT; epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev); // 3. 当 connectfd 写事件被触发,连接建立成功 if (status == e_connecting && e->events & EPOLLOUT) { status == e_connected; // 这里需要把写事件关闭 epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL); }
连接断开
if (e->events & EPOLLRDHUP) { // 读端关闭 close_read(fd); close(fd); } if (e->events & EPOLLHUP) { // 读写端都关闭 close(fd); }
数据到达
// reactor 要用非阻塞io // select if (e->events & EPOLLIN) { while (1) { int n = read(fd, buf, sz); if (n < 0) { if (errno == EINTR)//被信号打断 continue; if (errno == EWOULDBLOCK) break; close(fd); } else if (n == 0) { close_read(fd); // close(fd); } / / 业务逻辑了 } }
数据发送完毕
int n = write(fd, buf, dz); if (n == -1) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK) { struct epoll_event ev; ev.events = EPOLLOUT; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); } close(fd); } / / ... if (e->events & EPOLLOUT) { int n = write(fd, buf, sz); //... if (n > 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); } }
reactor应用
组成:io多路复用+非阻塞io
将对io的处理转化成对事件的处理
1/redis
单线程 socket在主线程,计算在主线程,read与decode,encode与send在多线程(增加速度,减轻主线程的压力)
为什么是单线程?
因为kv结构,v采用多种数据结构,加锁比较繁琐
水平触发
2/memcached
多线程,kv结构,固定结构,加锁方便,
水平触发,需要界定数据包
3/nginx
多进程,需要界定数据包, 惊群现象,使用共享内存加锁,负载均衡
边沿触发:反向代理,不需要界定数据包
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现