Reactor 模型的理解
- 组成: 由非阻塞的 io + IO多路复用机制组成
- 特征:基于事件循环,以事件驱动或者==事件回调的方式来实现业务逻辑
单 Reactor 模型
Epoll
根据不同的事件( EPOLLIN | EPOLLOUT ) 等
对不同的文件描述符进行不同的操作
作为服务器端连接客户端,触发 EPOLLIN
作为客户端连接服务器端,触发 EPOLLOUT
, 触发则说明连接建立。这是因为在三次握手过程中,收到来自服务器端的 syn ack
包时,会触发自己发送 ack
包,便触发 EPOLLOUT
事件
连接断开的一些表现
所以服务端收到 FIN
包有两种检测方式,但最常用的是 ev.events & EPOLLRDHUP
半关闭状态
连接断开过程中半关闭状态
-
背景
客户端关闭写通道,此时服务端想要推送完所有数据后再关闭;推送系统中
close_read()
或close_write()
或close()
服务端close_read send send ... close
客户端close()
-
实现
要实现半关闭状态;
close-wait
阶段,必须收到ack
包,才能直到客户端收到了我们推送的所有数据 -
细节
发送端:
shutdown(SHUT_WR)
发送一个FIN
包,并且标记该socket
为SEND_SHUTDOWN
shutdown(SHUT_RD)
不发送任何包,但是标记该socket
为RCV_SHUTDOWN
接收端:
收到FIN
包标记该socket
为RCV_SHUTDOWN
对于epoll
而言,如果一个socket
同时标记为SEND_SHUTDOWN
和RCV_SHUTDOWN
, 那么poll
会返回EPOLLHUP
如果一个socket
被标记为RCV_SHUTDOWN
;poll
会返回EPOLLRDHUP
应用:skynet
支持半关闭状态
如果不需要半关闭状态时,在比如收到 read() = 0
时,我们就可以直接 close(fd)
, 否则我们可以使用上述的方式来达成半关闭状态
read() = 0 --> close_read()...之类
消息到达
系统调用 --> 线程不会切换
中断 --> 线程切换
errno = ETIMEDOUT
TCP 探活包超时属于传输层,TCP 协议自己的特性,和平常应用层的心跳检测不同。
为什么应用层还需要开启心跳检测呢:
因为 TCP
传输层的探活检测无法判断进程阻塞或者死锁的情况
应用情况:
- 数据库间,主从赋值,使用心跳检测
- 客户端与服务器,使用心跳检测
- 客户端->反向代理->上有服务器; 反向代理与上层服务器之间使用探活检测
- 客户端-> 数据库,使用探活检测
消息发送
解释:
errno = EWOULDBLOCK
时处理方法
- 原因 --> 写缓冲区满
- 重新设定
EPOLLOUT
-->(epoll_event)ev.events |= EPOLLOUT
- 注册
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
- 再次触发并发送
单 reactor 模型 + 任务队列 + 线程池
将这些事件放在任务队列,由线程池里的线程进行“消费”,避免主线程被这些任务阻塞
代表: skynet
多 reactor
应用:memcached accept(fd, backlog)
acceptor
接收连接,然后通过比如 round robin
的方式选择不同的 reactor/epoll
进行管理
(one eventloop one thread)
多进程方式
应用:nginx
ps小笔记: epoll --> 多路IO复用,一个线程就能处理多个客户端的连接请求,或者事件请求; Reactor 设计模式(Reactor 模型),分发机制,事件驱动,当事件来临或 IO 就绪时,根据不同的方式分发给不同的
handler
来进行处理