muduo1

  1. 线程安全
  2. 构造函数, 做到线程安全, 只要不要在构造函数中暴露this指针就好了, 因为this尚在构造期间, 不同的线程在不同时期访问这个不完整的this将引发错误, 使用constructor() + init()的方法可以避免这种隐患
  3. 析构函数, 做到线程安全不容易, 原因是析构函数会破坏mutex, 需要某种手段确保析构期间不会发生竞争或压根就不做析构, 前者困难, 后者使用shared_ptr/weak_ptr来代理资源, 只要存在对资源的引用, 就不做析构
    (关于shared_ptr/weak_ptr, 本书p14解释的很好)

2

面向对象分类难: 本质在于某些物体很难准确分类, 不同的人看法不同, 或者本就不止一个分类适合它.

code

tcp server: ipaddress, port, eventloop

Buffer TcpConnect EventLoop TcpServer TcpClient

Buffer接管read write

InetAddress接管pv4地址

EventLoop接管时间循环, 每个线程一个EventLoop, 负责IO和定时器事件派发 异步唤醒
用TimerQueue作为时间管理, 用Poller作为IOmultiplexing

EventLoopThread启动一个线程, 运行EventLoop::Loop

TcpConnection核心, 封装一次TCP连接, 不发起连接

TcpClient发起连接 重试

TcpServer接收客户连接

内部类

Channel??
Scoket封装一个fd, 在析构是关闭fd, 它是Acceptor TcpConnection的成员, 生命期由后者控制
EventLoop TimerQueue也拥有它的fd, 但是不封装为Socket

SocketOps封装各种Sockets系统调用

Poller是PollPoller和EPollPoller的基类 采用点评触发
是EventLoop的成员, 生命期由后者控制
PollPoller和EpollPoller封装poll和epoll
poll便于调试, 因为poll是上下文无关的, 用strace很容易知道库的行为是否正确

Connector用于发起Tcp连接 是TcpClient的成员

Acceptor用于接收Tcp连接, 是TcpServer的成员

TimerQueue用timerfd实现定时, 用std::map管理Timer

EventLoopThreadPoll创建IO线程池, 用于把TcpConnection分配到某个EventLoop线程上, 是TcpServer的成员

线程模型
one loop per thread + thread pool

思维装换
主动调用 换成 注册一个接收连接的回调, 需要使用的时候, 只管往连接中操作 网络库负责无阻赛的发送

TCP
建立连接, 连接一旦建立, 双方是平等的可以各自手法数据
断开连接, 主动断开(close, shutdown) 被动断开recv返回0
消息到达, 文件描述符可读, 事件
对这个事件的处理方式决定了网络编程的风格
阻塞还是非阻塞 如何分包 等, 应用层的缓冲如何设计等
消息发送完毕, 是指将数据发送到了操作系统的内核缓冲区, 之后由TCP协议栈负责数据的发送与重传, 此时并不代表对方已经收到数据

关于6.4.1后面提及的问题, …

封装socket api

Acceptor: 封装等待sockfd, Channel用于观察sockfd上的readable事件, 并调用回调Acceptor::handleRead()
Acceptor构造函数和listen成员执行TCP传统api步骤, socket bind listen

TcpServer管理accept建立TcpConnection连接, 该类供用户使用, 由用户管理

分析:
EventLoop, EventLoop拥有一个Poller对象和Channel池
EventLoop主要有两个工作:
1 一个主循环让Poller不停的工作
2 处理Channel上的事件

Poller, 管理所有的fd, 从中poll出所有需要处理的event, 将所关系的事件加入到EventLoop的Channel池中

一个Channel管理一个fd上可读或可写或错误的事件, 当Poller返回活跃事件时, 将设置Channel的状态, 在由EventLoop告知他处理

TcpServer, TcpServer主要管理一个Acceptor, 当TcpServer启动时, Acceptor开始监听
Acceptor在创建时自然会生成一个listenfd
Acceptor需要处理可读事件, 当它可读是表明一个新连接到达, 新连接需要只要如何除了, Acceptor只管接收, 并不处理连接相关的事宜, 它将调用回调函数, 这个函数在Acceptor创建时, 在TcpServer中被绑定
TcpServer处理这个新连接回调, 连接建立成功, 创建一个TcpConnection, 加入到TcpConnection map中, TcpServer会处理很多连接, 很明显
TcpConnection能够很轻易的知道与服务器TcpServer建立连接的客户端在服务器上建立的connfd, 也知道客户和本地的IP地址, 那么客户与服务器之间的数据处理都可以在这里处理了, 但TcpConnection只管维护连接, 他不知道数据如何处理

处理数据的逻辑应该由用户设置

用户与TcpConnection之间的鸿沟在于TcpServer, TcpServer是真是的与用户交互的对象, 所以TcpConnection需要的东西, 将由用户设定后告诉TcpServer, TcpServer在转交TcpConnection, TcpConnection的事件处理也是转交Channel

然而Channel的芳心却在EventLoop那里, 所以整个流程可以看做是绕了一圈, 设置好Channel, 终于能交给EventLoop工作了, 而Chennel处理的事件, 最终会反馈到用户设置的事件上来

Buffer

EventLoop/Poller/Channel

每个EventLoop只有一个Poller用于检测出用户关心在某个fd的事件, 然后交由Channel处理
一个fd(pollfd)对应一个Channel, 有加之与这个fd上的各种事件回调, Channel知道fd关注那些事件, 通过poll返回那些要处理的事件
而poll存在于Poller, 所以Channel由Poller管理, poller轮询所有fd, 找到就绪的fd, 为Channel中的fd设置revents
有EventLoop去处理这些Channel的revents的回调

TcpClient/TcpServer

linux 定时器

1
2
3
4
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);