redis,memcached,nginx网络组件,网络编程——reactor的应用

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 目标明白网络模块要处理那些事情reactor 是怎么处理这些事情的reactor 如何封装的网络模块与业务逻辑的关系如何优化 reactor网络编程关注的问题

目标

  1. 明白网络模块要处理那些事情
  2. reactor 是怎么处理这些事情的
  3. reactor 如何封装的
  4. 网络模块与业务逻辑的关系
  5. 如何优化 reactor

网络编程关注的问题

连接的建立

分为两种:

  服务端处理接收客户端的连接;

  服务端作为客户端连接第三方服务(如数据库)

int clientfd = accept(listenfd, addr, sz);
// 举例为非阻塞io,阻塞io成功直接返回0;
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
int ret = connect(connectfd, (struct sockaddr
*)&addr, sizeof(addr));
// ret == -1 && errno == EINPROGRESS 正在建立连接
// ret == -1 && errno = EISCONN 连接建立成功

连接的断开

分为两种:

  主动断开

  被动断开

// 主动关闭
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);
    // write()
    // close(fd);
}
// 被动:写端关闭
int n = write(fd, buf, sz);
if (n == -1 && errno == EPIPE) {
    close_write(fd);
    // close(fd);
}

消息的到达

从缓冲区中读取数据

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
}

消息发送完毕

往缓冲区中写数据

int n = write(fd, buf, dz);
if (n == -1) {
    if (errno == EINTR || errno == EWOULDBLOCK) {
        return;
   }
    close(fd);
}

网络 IO 职责

检测 IO

 io 函数本身可以检测 io的状态;但是只能检测一个 fd对应的状态;

 io 多路复用可以同时检测多个 io的状态;

区别:

 io 函数可以检测具体的状态,io 多路复用只能检测出可读、可写、错误、断开等笼统的事件

检测 io剖析

  io 函数和系统调用中都有用到 检测 io。主要功能就是检测 io 是否就绪,如果对应到 socket 网络通信来说每个函数检测的部分如下:

acccept();//检测全连接队列是否有数据:
      //第 1 次握手:将数据放到半连接队列
      //第 3 次握手:将数据放入全连接队列
connect();//检测是否收到 ACK,收到 ACK 就代表 IO 就绪,连接成功
      //第 2 次握手成功,就表示 client 连接成功
read = 0; //检测 buf 是否含有 EOF 标记
      //关闭连接时,会往对应的缓冲区写入 EOF,读到 EOF 就会返回 0
write //就是把数据写到 send_buf 缓冲区中,至于数据什么时候写,以什么形式写,何时到达对端,都是根绝协议栈来决定的

操作 IO

只能使用 io 函数来进行操作;分为两种操作方式:

  阻塞 io

  非阻塞 io

阻塞IO 和 非阻塞IO

  • 阻塞在网络线程
  • 连接的 fd阻塞属性决定了 io函数是否阻塞
  • 具体差异在:io 函数在数据未到达时是否立刻返回
// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

详细分析可以看I/O详解与五种网络I/O模型


IO 多路复用

io 多路复用只负责检测io,不负责操作 io

int n = epoll_wait(epfd, evs, sz, timeout);

 timeout = -1 一直阻塞直到网络事件到达;

 imeout = 0 不管是否有事件就绪立刻返回;

 timeout = 1000 最多等待 1 s,如果1 s内没有事件触发则返回;

详细分析可以看I/O详解与五种网络I/O模型

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);


9b7e2f4d81784e57815dd7c514e32797.png

 调用 epoll_create 会创建一个 epoll对象;

 调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发是会调用回调函数(ep_poll_callback),将触发的事件拷贝到 rdlist 双向链表中;

 调用 epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;

reactor编程

reactor为什么要引入 IO多路复用?

Q: 什么是 IO 复用,IO 多路复用是否具有操作 具体连接的 IO功能?

A: IO 多路复用只有检测 IO 的功能,能检测多条连接是否 IO 就绪,但是不具备 IO 操作的功能,无法操作 IO 数据

Q: 为什么要把 IO 检测的功能丢给 IO 多路复用去做,而不是 IO 函数自己来做?

A: 主要是为了提升性能,因为在大部分情况下,大会部分连接是没有交互的。

 提升性能的原因如下,就 IO 是否阻塞的情况进行分析:


阻塞 IO :若 IO 有自己检测,那么就代表每条 连接需要一条线程来处理

非阻塞 IO :每个 IO 都需要调用 while 循环在应用层检测

reactor 把对 IO 的处理转换成对事件的处理:


注册 IO 就绪事件,注册到 IO 多路复用之中。注册具体事件时,会绑定一个回调函数,当事件发生时调用该回调函数,并在回调函数中操作具体的 IO

epoll_wait 收集事件,处理事件(通常是封装为事件循环)

reactor中用到了 IO 多路复用 和 非阻塞 IO,他们分别用到了 IO的哪种功能?


IO 多路复用 :检测 IO

非阻塞 IO:操作 IO

reactor 为什么要搭配非阻塞 IO?


多线程环境:将一个 listen放到多个 epoll中处理,如果此时有三个县城响应了,但是只会有一个线程抢到执行权,其余的线程就会一直被阻塞

边缘触发:读事件出发时,如果 read 在一次事件中把 read_buf 读空后再 read,就会阻塞线程

用select产生的bug:当一个数据到达时,select会报告读事件,但是数据可能没有通过校验和检测——所以该事件会被丢弃。但此时 select 已经上报读事件了,此时如果用的是阻塞 IO 去读,就会造成阻塞线程

Q: 是不是 IO 多路复用一定要搭配 非阻塞 IO?

A: 不一定:例如 MySQL

连接建立

// 一、处理客户端的连接
// 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);
}

数据到达

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);
        return;
 }
    close(fd);
}
// ...
if (e->events & EPOLLOUT) {
    int n = write(fd, buf, sz);
    //...
    if (n == sz) {
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
   }
}

reactor 应用:后续补充源码解析

 The reactor design pattern is an event handling pattern

(事件处理模式)for handling service requests delivered

 concurrently to a service handler by one or more inputs

(处理一个或多个并发传递到服务端的服务请求). The service

 handler then demultiplexes the incoming requests and

dispatches them synchronously (同步)to the associated

request handlers.

单 reacrtor  

04488aa82c01413489d41a115cb84e78.png

多 reactor(one eventloop per thread)

多线程

92a5b99b22da48308ec796540742f048.png

多进程

8ce3fcedc83649a5b15c13638d6d3eba.png 

目录
相关文章
|
1月前
|
机器学习/深度学习 PyTorch TensorFlow
卷积神经网络深度解析:从基础原理到实战应用的完整指南
蒋星熠Jaxonic,深度学习探索者。深耕TensorFlow与PyTorch,分享框架对比、性能优化与实战经验,助力技术进阶。
|
3月前
|
监控 安全 Shell
管道符在渗透测试与网络安全中的全面应用指南
管道符是渗透测试与网络安全中的关键工具,既可用于高效系统管理,也可能被攻击者利用实施命令注入、权限提升、数据外泄等攻击。本文全面解析管道符的基础原理、实战应用与防御策略,涵盖Windows与Linux系统差异、攻击技术示例及检测手段,帮助安全人员掌握其利用方式与防护措施,提升系统安全性。
159 6
|
7月前
|
SQL 分布式计算 Serverless
鹰角网络:EMR Serverless Spark 在《明日方舟》游戏业务的应用
鹰角网络为应对游戏业务高频活动带来的数据潮汐、资源弹性及稳定性需求,采用阿里云 EMR Serverless Spark 替代原有架构。迁移后实现研发效率提升,支持业务快速发展、计算效率提升,增强SLA保障,稳定性提升,降低运维成本,并支撑全球化数据架构部署。
685 56
鹰角网络:EMR Serverless Spark 在《明日方舟》游戏业务的应用
|
6月前
|
人工智能 监控 安全
NTP网络子钟的技术架构与行业应用解析
在数字化与智能化时代,时间同步精度至关重要。西安同步电子科技有限公司专注时间频率领域,以“同步天下”品牌提供可靠解决方案。其明星产品SYN6109型NTP网络子钟基于网络时间协议,实现高精度时间同步,广泛应用于考场、医院、智慧场景等领域。公司坚持技术创新,产品通过权威认证,未来将结合5G、物联网等技术推动行业进步,引领精准时间管理新时代。
|
2月前
|
机器学习/深度学习 人工智能 算法
卷积神经网络深度解析:从基础原理到实战应用的完整指南
蒋星熠Jaxonic带你深入卷积神经网络(CNN)核心技术,从生物启发到数学原理,详解ResNet、注意力机制与模型优化,探索视觉智能的演进之路。
328 11
|
7月前
|
存储 SQL 运维
中国联通网络资源湖仓一体应用实践
本文分享了中国联通技术专家李晓昱在Flink Forward Asia 2024上的演讲,介绍如何借助Flink+Paimon湖仓一体架构解决传统数仓处理百亿级数据的瓶颈。内容涵盖网络资源中心概况、现有挑战、新架构设计及实施效果。新方案实现了数据一致性100%,同步延迟从3小时降至3分钟,存储成本降低50%,为通信行业提供了高效的数据管理范例。未来将深化流式数仓与智能运维融合,推动数字化升级。
299 0
中国联通网络资源湖仓一体应用实践
|
9月前
|
机器学习/深度学习 编解码 自动驾驶
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
321 3
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
|
3月前
|
数据采集 存储 数据可视化
Python网络爬虫在环境保护中的应用:污染源监测数据抓取与分析
在环保领域,数据是决策基础,但分散在多个平台,获取困难。Python网络爬虫技术灵活高效,可自动化抓取空气质量、水质、污染源等数据,实现多平台整合、实时更新、结构化存储与异常预警。本文详解爬虫实战应用,涵盖技术选型、代码实现、反爬策略与数据分析,助力环保数据高效利用。
209 0
|
3月前
|
安全 Linux
利用Libevent在CentOS 7上打造异步网络应用
总结以上步骤,您可以在CentOS 7系统上,使用Libevent有效地构建和运行异步网络应用。通过采取正确的架构和代码设计策略,能保证网络应用的高效性和稳定性。
105 0
|
6月前
|
机器学习/深度学习 算法 测试技术
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
本文探讨了基于图的重排序方法在信息检索领域的应用与前景。传统两阶段检索架构中,初始检索速度快但结果可能含噪声,重排序阶段通过强大语言模型提升精度,但仍面临复杂需求挑战
178 0
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析