重构muduo库梳理

前情提要

 

基于c++11的特性重写muduo的核心相关代码,在这里并且简单梳理一下。当然本人对于muduo库的理解还仅限于能看懂简单的使用。对于各个对象内存的管理,类的设计,以及这样设计的精髓等还差得很远。后面有了更深的体会还会来这里更新。总的来说muduo库实现了业务与网络通信的完全解耦,上层与下层之间有大量的回调函数,初看还是有点绕的。这里的梳理仅限于对muduo代码有一定的了解。小白可以直接忽略这篇文章。文末有一张全部的关系图。

Channel类

首先看一下channel类,该类是muduo库对于我们要监听的fd的一个封装。该类继承了 noncopyable类,表示该类的对象不能调用拷贝构造函数和拷贝赋值运算符。看一下成员变量:

class Channel : noncopyable
{
private:
    static const int kNoneEvent;//这个是0
    static const int kReadEvent;
    static const int kWriteEvent;//这三个就是表示常量,对EPOLLIN EPOLLOUT 的封装
    
    EventLoop *loop_;//该channel 所属的哪一个时间循环loop,也就是哪一个事件循环来监控这个
    const int fd_; //封装的fd
    int events_;   //想要监听的事件
    int revents_;  //实际发生的事件  有一个问题是 想要监听的时间跟实际发生的时间还能不一样吗?
    int index_;   //是否有添加到poller中
    
    std::weak_ptr<void> tie_; //自己绑定的那个 TCPconnection,为什么是弱指针?
    bool tied_;//是否已经绑定
    
    ReadEventCallback readCallback_; 
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;//四个回调函数,也就是发生对应事件后 要执行的函数。
​
    //两个私有方法
    void update(); // 调用 自己所属的Loop来更新epoll中监听的事件
    void handleEventWithGuard(Timestamp receiveTime);//根据revents_ 的事件来调用不同的函数。
 public:
    using EventCallback = std::function<void()>;
    using ReadEventCallback = std::function<void(Timestamp)>;//读事件的回调函数还需要时间戳
    Channel(EventLoop *loop, int fd); //构造函数只需要传入 Loop 和 fd就可
    ~Channel();//没有需要手动释放的资源,fd在哪里close?
    void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); } 
    void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }
    void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }
    void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }//设置各种回调,采用移动构造函数可以防止function对象被拷贝。但是拷贝也
    void enableReading() { events_ |= kReadEvent; update(); }
    void disableReading() { events_ &= ~kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableWriting() { events_ &= ~kWriteEvent; update(); }
    void disableAll() { events_ = kNoneEvent; update(); }//设置各种类型的监听事件,最后调用update更新到到loop的poll中
}

channel 类比较简单,就是对fd的一个封装,把要监听的事件也封装进去了。 当监听事件发生,通过channel调用不同的回调函数就能执行读写错误关闭操作。而这些回调函数是TCPconnection对象注册的。

poller 和 epollpoller

这两个简单来说就就是封装了epoll_wait 这个函数核心 以及一系列其他的功能,例如修改监听事件等。

poller 类是父类,其子类可以是poll 方法 可以是 epoll 方法,但是muduo中我们还是用epoll比较好。

class Poller : noncopyable
{
private:
    EventLoop * ownerLoop_; //这个poll所属的loop是哪一个
protected:
    using ChannelMap = std::unordered_map<int, Channel*>; 
    ChannelMap channels_; //用一个map来管理所有监听的socket
public:
    Poller(EventLoop *loop);
    virtual ~Poller() = default;
    //下面三个纯虚函数,简单来说就是调用epll_wait 或者改变或者删除监听的fd的事件
    //channel中改变event_就是如此
    virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0;
    virtual void updateChannel(Channel *channel) = 0;
    virtual void removeChannel(Channel *channel) = 0;
    //传入的channel是否是该poll所监听的
    bool hasChannel(Channel *channel) const;
    //静态函数
    static Poller* newDefaultPoller(EventLoop *loop); //构造一个子类的对象 通过父类指针返回
} 

然后就是epollpoller

class EpollPoller : public Poller //共有继承表示基类的所有成员在子类中的所有访问方式不变
{
    //成员变量
private:
    int epollfd_; //epollfd,指向内核中的各种数据结构
    using EventList = std::vector<epoll_event>;
    EventList events_; //调用epollwait后活跃的事件集合,在时间的event中可以获取fd或者channel指针
    void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;//根据调用填写活跃的channel
    // 更新channel通道
    void update(int operation, Channel *channel);
   
public:
    //重写父类纯虚函数
    Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;
    void updateChannel(Channel *channel) override;
    void removeChannel(Channel *channel) override;
}

epollpoller的总体思路作用如下:

该类中核心的成员是epollfd 和events_ 后者是一个调用epoll_wait() 的传出函数

通过updateChannel()removeChannel()函数更新内核中的监听的事件,也就是要监听的fd以及对应的事件,通过调用poll 来实现epoll_wait,填充成员变量 events_,同时填充传入的数组 activeChannel ,将有事件的channel填入这个数组中,交给上层使用,至于谁调用了 poll 我们后面再说。

EventLoop

前面的几个类都是基础类,这个类就是一个核心的类了。

class EventLoop : noncopyable
{
private:
    std::atomic_bool looping_; //原子操作标识是否处于loop循环中
    std::atomic_bool quit_; //原子才做标识是否退出
    const pid_t threadId_; //标识当前loop所在的线程id
    Timestamp pollReturnTime_; // poller返回发生事件的channels的时间点
    std::unique_ptr<Poller> poller_;//该loop的 epollloop
    int wankeupFd_; //wakeupfd 用来唤醒epoll_wait
    std::unique_ptr<Channel> wakeupChannel_;//上面fd的封装channel
    ChannelList activeChannels_;//从epoll返回中活跃的channel
    std::atomic_bool callingPendingFunctors_; //是否需要回调函数
    std::vector<Functor> pendingFunctors_; //要执行的回调函数
    std::mutex mutex_;//有锁 说明 该对象中可能被多个线程所操作。
}

EventLoop是 关联 poller 与 channel的桥梁 channel 中通过自己所属的 eventloop 去找到该eventlooop中的poller 然后修改fd 的事件。

此外 eventloop 还调用poller 的 epoll_wait方法,找出有事件发生的channel 并且执行对应的事件回调函数,并且执行上层交给eventloop的事件。总的来说 eventloop就这两个功能。

Thread

muudo库实现的是 one loop per thread 的思想。所以一个thread 就会对应一个eventloop。而类thread就是要实现这个。

class Thread : noncopyable
{
public:
    using ThreadFunc = std::function<void()>; //开启线程要执行的函数
​
    explicit Thread(ThreadFunc, const std::string &name = std::string()); //构造函数
                                                                 //只需要传入构造函数和名字就可以
    ~Thread();//实现线程分离。
​
    void start(); //开启线程
    void join(); //join 线程等待线程结束。
​
    bool started() const { return started_; }
    pid_t tid() const { return tid_; }
    const std::string& name() const { return name_; }
​
    static int numCreated() { return numCreated_; }
private:
    void setDefaultName();  //设置默认的名字并且将静态变量的线程数+1
​
    bool started_;
    bool joined_;
    std::shared_ptr<std::thread> thread_; //线程的共享指针
    pid_t tid_;                         //tid
    ThreadFunc func_;                   //线程的调用函数
    std::string name_;                  //名字
    static std::atomic_int numCreated_; //静态变量 一共开启了多少个线程。
};

这里要重点说一下 start()

void Thread::start()  // 一个Thread对象,记录的就是一个新线程的详细信息
{
    started_ = true;// 这个函数就是要开启线程了
    sem_t sem;
    sem_init(&sem, false, 0); //要用到信号量
    // 开启线程
    thread_ = std::shared_ptr<std::thread>(new std::thread([&](){ //利用lambda表达式实现线程执行函数
        // 获取线程的tid值
        tid_ = CurrentThread::tid(); 
        sem_post(&sem);//只有在获得线程名字后 才会然start ()执行完毕。
        // 开启一个新线程,专门执行该线程函数
        func_(); 
    }));
    // 这里必须等待获取上面新创建的线程的tid值 
    sem_wait(&sem);
}

也就是类Thread的对象构建完毕后,只有在start函数执行后才会正式的开启线程,执行的就是我们传入构造函数的func。

EventLoopThread

上面那个thread 类其实 可以算一个公共的目标,实际上做的事情跟muduo 没关系,(解耦拉满) 主要看你传入的是什么函数。而这里的eventLoop 就非常有用了。

//这给类的实现还是非常有趣的
class EventLoopThread : noncopyable
{
public:
    using ThreadInitCallback = std::function<void(EventLoop*)>; //这里仍然有一个新的回调函数
​
    EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback(),
        const std::string &name = std::string());
    ~EventLoopThread();
​
    EventLoop* startLoop();
private:
    void threadFunc(); //线程的回调函数
​
    EventLoop *loop_;//与线程关联的loop
    bool exiting_; //
    Thread thread_; //线程类
    std::mutex mutex_;//锁
    std::condition_variable cond_;//条件变量
    ThreadInitCallback callback_; //传入的新回调函数
};
要具体看一下函数的实现才能了解全貌:

//构造函数 ,传入名字 和 新回调函数
EventLoopThread::EventLoopThread(const ThreadInitCallback &cb, 
        const std::string &name)
        : loop_(nullptr)
        , exiting_(false)
        , thread_(std::bind(&EventLoopThread::threadFunc, this), name)//这个threadfunc后面再说
        , mutex_()
        , cond_()
        , callback_(cb)
{
​
}
​
EventLoopThread::~EventLoopThread()
{
    exiting_ = true;
    if (loop_ != nullptr)
    {
        loop_->quit();
        thread_.join();
    }
}
​
EventLoop* EventLoopThread::startLoop()
{
    thread_.start(); // 启动底层的新线程
​
    EventLoop *loop = nullptr;
    {
        std::unique_lock<std::mutex> lock(mutex_);
        while ( loop_ == nullptr )//等待上面开启的线程执行完绑定loop的操作 eventloop  栈空间
        {
            cond_.wait(lock);
        }
        loop = loop_;
    }
    return loop;
}
​
// 下面这个方法,是在单独的新线程里面运行的
void EventLoopThread::threadFunc()
{
    EventLoop loop; // 创建一个独立的eventloop,和上面的线程是一一对应的,one loop per thread
    if (callback_) //首先执行上面传入的新回调函数
    {   
        callback_(&loop);
    }
    {
        std::unique_lock<std::mutex> lock(mutex_); //锁住,为什么需要锁呢?
                                                //因为我们条件变量判断的是Loop_ 主线程和 新开启的线程                                                 都可以改变这个loop所以必须要用锁。
        loop_ = &loop;  //将本类的loop 绑定为 该栈中创建的loop
        cond_.notify_one(); //只有绑定了栈loop才能让上面的函数成功返回loop
    }
    loop.loop(); // EventLoop loop  => Poller.poll //执行loop函数,也就是epoll_wait
    std::unique_lock<std::mutex> lock(mutex_);
    loop_ = nullptr;
}

总的来说 EventLoopthread就是实现了one loop per thread。就是一个线程里面有一个loop。并且这个loop是保存在栈中的!

eventloop就是提供thread 线程执行函数,在函数内,栈中创建一个loop 并且绑定到该类的指针,然后执行loop死循环

,最后返回这个指针,供给外部操作这个loop。当然这里的loop已经执行了poll 所以是阻塞的,我们要操作这个loop 必须要通过runinloop 或者 queueinloop来向里面添加执行事件,才能够在dopendingfunctor 中执行。

EventLoopThreadPool

这个类就是一个线程池类,用来管理所有的EventLoopThread类别

class EventLoop;
class EventLoopThread;
​
class EventLoopThreadPool : noncopyable
{
public:
    using ThreadInitCallback = std::function<void(EventLoop*)>; 
​
    EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg);
    ~EventLoopThreadPool();
​
    void setThreadNum(int numThreads) { numThreads_ = numThreads; } //设置subloop个数
​
    void start(const ThreadInitCallback &cb = ThreadInitCallback());
​
    // 如果工作在多线程中,baseLoop_默认以轮询的方式分配channel给subloop
    EventLoop* getNextLoop();
​
    std::vector<EventLoop*> getAllLoops();
​
    bool started() const { return started_; }
    const std::string name() const { return name_; }
private:
​
    EventLoop *baseLoop_; // EventLoop loop;  
    std::string name_;  //名字
    bool started_;  //是否开启
    int numThreads_;    //subloop个数
    int next_;  //轮询的下一个thread是谁
    std::vector<std::unique_ptr<EventLoopThread>> threads_; //储存的是threads_的指针
    std::vector<EventLoop*> loops_; //储存了所有的subloop。
};
​
​
​
​
EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg)
    : baseLoop_(baseLoop)
    , name_(nameArg)
    , started_(false)
    , numThreads_(0)
    , next_(0)
{}
​
EventLoopThreadPool::~EventLoopThreadPool()
{}
​
//这个函数比较重要,传入的就是回调函数,也就是在线程函数中执行的回调函数。
//根据设置的num数目来开启所有的线程。
void EventLoopThreadPool::start(const ThreadInitCallback &cb)
{
    started_ = true;
​
    for (int i = 0; i < numThreads_; ++i)
    {
        char buf[name_.size() + 32];
        snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
        EventLoopThread *t = new EventLoopThread(cb, buf);
        threads_.push_back(std::unique_ptr<EventLoopThread>(t));
        loops_.push_back(t->startLoop()); // 底层创建线程,绑定一个新的EventLoop,并返回该loop的地址
    }
​
    // 整个服务端只有一个线程,运行着baseloop
    if (numThreads_ == 0 && cb)
    {
        cb(baseLoop_);
    }
}
​
// 如果工作在多线程中,baseLoop_默认以轮询的方式分配channel给subloop
EventLoop* EventLoopThreadPool::getNextLoop()
{
    EventLoop *loop = baseLoop_;
​
    if (!loops_.empty()) // 通过轮询获取下一个处理事件的loop
    {
        loop = loops_[next_];
        ++next_;
        if (next_ >= loops_.size())
        {
            next_ = 0;
        }
    }
​
    return loop;
}
​
std::vector<EventLoop*> EventLoopThreadPool::getAllLoops()
{
    if (loops_.empty())
    {
        return std::vector<EventLoop*>(1, baseLoop_);
    }
    else
    {
        loops_;
    }
}
​

这个类的功能就是开启设置好的subloop线程,然后可以通过轮询每次都返回一个eventloop,最后上层调用可以通过这个loop的runinloop来实现回调。

InetAdress

这个类就跳过,只是将linux是api封装一下更符合使用。

Acceptor

这个函数就是比较上层的函数调用,主要作用是创建listenfd,然后开启listen, 然后该fd注册的回调函数就是创建一个新的连接,当然这个回调函数也是上层传递的。

class Acceptor : noncopyable
{
public:
    using NewConnectionCallback = std::function<void(int sockfd, const InetAddress&)>;
    Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
    ~Acceptor();
    
    void setNewConnectionCallback(const NewConnectionCallback &cb) 
    {
        newConnectionCallback_ = cb;
    }
    bool listenning() const { return listenning_; }
    void listen();
private:
    void handleRead(); //本类里面fd的回调函数
​
    EventLoop *loop_; // Acceptor用的就是用户定义的那个baseLoop,也称作mainLoop
    Socket acceptSocket_; //这个就是listen的fd
    Channel acceptChannel_;//上面fd的channel
    NewConnectionCallback newConnectionCallback_;
    bool listenning_;
};
​
​
static int createNonblocking() //这个就是创建非阻塞 socket的函数
{
    int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
    if (sockfd < 0) 
    {
        LOG_FATAL("%s:%s:%d listen socket create err:%d \n", __FILE__, __FUNCTION__, __LINE__, errno);
    }
    return sockfd; 
}
​
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
    : loop_(loop)
    , acceptSocket_(createNonblocking()) // socket
    , acceptChannel_(loop, acceptSocket_.fd()) //创建channel
    , listenning_(false)
{
    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(true);//端口复用
    acceptSocket_.bindAddress(listenAddr); // bind
    // TcpServer::start() Acceptor.listen  有新用户的连接,要执行一个回调(connfd=》channel=》subloop)
    // baseLoop => acceptChannel_(listenfd) => 
    acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); //绑定写事件回调函数
}
​
Acceptor::~Acceptor()
{
    acceptChannel_.disableAll();
    acceptChannel_.remove();//回调函数就是移除channel
}
​
void Acceptor::listen()
{
    listenning_ = true;
    acceptSocket_.listen(); // listen 开启监听
    acceptChannel_.enableReading(); // acceptChannel_ => Poller 向poller中注册fd 
}
​
// listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
    InetAddress peerAddr;
    int connfd = acceptSocket_.accept(&peerAddr); //传入参数
    if (connfd >= 0)
    {
        if (newConnectionCallback_)
        {
            newConnectionCallback_(connfd, peerAddr); // 轮询找到subLoop,唤醒,分发当前的新客户端的Channel
        }
        else
        {
            ::close(connfd);
        }
    }
    else
    {
        LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__, __FUNCTION__, __LINE__, errno);
        if (errno == EMFILE)
        {
            LOG_ERROR("%s:%s:%d sockfd reached limit! \n", __FILE__, __FUNCTION__, __LINE__);
        }
    }
}

这个类其实作用很简单,就是创建一个监听的fd,然后开启监听,如果监听事件发生就执行回调函数。

Buffer

这个类的实现比较有意思,并且思路可以去用到其他的的代码设计中。这个buf就是一个vector<char>管理的连续空间用来储存消息,有一个readidex和一个writeindex来指向读取数据的位置和写入数据的位置。 一般来说一个TCPserver 中会有一个 inputbuffer 和一个 outputbuffer。其中inputbuffer 是TCPserver 从内核中读取数据到inputbuffer中,所以这里的write就是从内核中的数据转移到这个buffer中,read数据就是应用层从buffer中读取数据。 而outputbuffer就是TCPbuffer向内核写入数据时,如果内核的缓冲区满了,那么server就会注册可写事件,然后将剩余的数据写入到outputbuffer中。(何时可写数据会触发呢? 一旦内核的写缓冲区有空余就会一直触发写事件!)所以这里buffer的write是应用层写入,read是内核缓冲区读取数据。这里比较有一意思的是他的扩容过程,代码如下:

//何时这个vector要扩容呢,显然要写入的数据长度比空闲的数据长度大时就要进行扩容了。
//这里的比较是将wirteable的数据 加上前面 8字节后readidex前的数据也加进去比较。
​
void makeSpace(size_t len)
    {
        if (writableBytes() + prependableBytes() < len + kCheapPrepend)
        {//如果空间还是小了,ok那么直接才后面扩容,readindex前的空余空间也可以不管了
            buffer_.resize(writerIndex_ + len);
        }
        else
        {//如果空间能用,那么我们就将readable 中的数据移动到 8字节处开始,再开始写入。
            size_t readalbe = readableBytes();
            std::copy(begin() + readerIndex_, 
                    begin() + writerIndex_,
                    begin() + kCheapPrepend);
            readerIndex_ = kCheapPrepend;
            writerIndex_ = readerIndex_ + readalbe;
        }
    }

然后就是从fd中读取数据的函数了:

这里的设计比较巧妙。从内核中读取数据,我们肯定是想一次性就把数据读完,这样就只会有一次系统调用,但是我们在读取数据的时候不知道数据大小多少,所以buffer内存的设置不太清楚多少比较合适。muduo库中巧妙利用了栈空间的思维。将内核中的数据首先填充到buffer中,待buffer满了后,再填到栈中的extrabuf中,栈buf不论设置成多少最后都会自动的回收所以就不会有顾虑,这样我们通过extrabuf就能会知道内核的数据到底有多少,所有我们就根据这个数据来扩充buffer,最后拷贝数据到buffer中。

ssize_t Buffer::readFd(int fd, int* saveErrno)
{
    char extrabuf[65536] = {0}; // 栈上的内存空间  64K
    
    struct iovec vec[2];
    
    const size_t writable = writableBytes(); // 这是Buffer底层缓冲区剩余的可写空间大小
    vec[0].iov_base = begin() + writerIndex_;
    vec[0].iov_len = writable;
​
    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof extrabuf;
    
    const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
    const ssize_t n = ::readv(fd, vec, iovcnt);
    if (n < 0)
    {
        *saveErrno = errno;
    }
    else if (n <= writable) // Buffer的可写缓冲区已经够存储读出来的数据了
    {
        writerIndex_ += n;
    }
    else // extrabuf里面也写入了数据 
    {
        writerIndex_ = buffer_.size();
        append(extrabuf, n - writable);  // writerIndex_开始写 n - writable大小的数据
    }
​
    return n;
}

TCPconnection

这个类就是至关重要的类的。前面的很多回调函数都会在这里实现。先来看一下成员变量:

class TcpConnection : noncopyable, public std::enable_shared_from_this<TcpConnection>
{
private:
    enum StateE {kDisconnected, kConnecting, kConnected, kDisconnecting};
    void setState(StateE state) { state_ = state; }
​
    void handleRead(Timestamp receiveTime); //channel中发生事件时的回调
    void handleWrite();
    void handleClose();
    void handleError();
​
    void sendInLoop(const void* message, size_t len);
    void shutdownInLoop();
​
    EventLoop *loop_; // 这里绝对不是baseLoop, 因为TcpConnection都是在subLoop里面管理的
    const std::string name_;
    std::atomic_int state_;
    bool reading_;
​
    // 这里和Acceptor类似   Acceptor=》mainLoop    TcpConenction=》subLoop
    std::unique_ptr<Socket> socket_; //
    std::unique_ptr<Channel> channel_;
​
    const InetAddress localAddr_;
    const InetAddress peerAddr_;
​
    ConnectionCallback connectionCallback_; // 有新连接时的回调
    MessageCallback messageCallback_; // 有读写消息时的回调
    WriteCompleteCallback writeCompleteCallback_; // 消息发送完成以后的回调
    HighWaterMarkCallback highWaterMarkCallback_;
    CloseCallback closeCallback_;
    size_t highWaterMark_;
​
    Buffer inputBuffer_;  // 接收数据的缓冲区
    Buffer outputBuffer_; // 发送数据的缓冲区
};

每一个TCPconn都有 一个inputbuf 和 outputbuf。 非阻塞的io连接必须要有这些缓冲区。 成员变量中的 socket 是accept 返回的fd,其中loop是eventloopthreadpool 通过轮询下发的loop。其中的handle 四个函数就是注册给channel 的 读写错误关闭的回调,在这里实现,并且注册给channel。 此外还有 一系列的其他回调 ,例如连接回调,消息回调等等。这些要么是在channel的回调函数中执行,要么是在loop的dopendingfunctor中执行。 ok下面来看一下重点的几个函数:

void TcpConnection::handleRead(Timestamp receiveTime)
{
    int savedErrno = 0;
    ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
    if (n > 0)
    {
        // 已建立连接的用户,有可读事件发生了,调用用户传入的回调操作onMessage
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    }
    else if (n == 0)
    {
        handleClose();
    }
    else
    {
        errno = savedErrno;
        LOG_ERROR("TcpConnection::handleRead");
        handleError();
    }
}

首先是handleread(),这个函数是channel监听read事件时发生的回调函数,简单来说就是将内核中的数据读取到inputbuffer中来。然后调用了 messagecallback,这个函数就是用户给的回调函数(如果是回射服务器的话,那么这里的回调执行的就是写入读取的数据)

void TcpConnection::handleWrite()
{
    if (channel_->isWriting())
    {//只有channel监听的是write事件才会执行
        int savedErrno = 0;
        ssize_t n = outputBuffer_.writeFd(channel_->fd(), &savedErrno);//从outputbuf中写入数据到内核
        if (n > 0) //如果写入数据大于0 表示写入成功
        {
            outputBuffer_.retrieve(n); //修改buf的index
            if (outputBuffer_.readableBytes() == 0)
            {//如果此时的outbuffer中的所有数据全部被写进内核
                channel_->disableWriting();//此时的channel就没必要继续监听write事件
                if (writeCompleteCallback_)
                {
                    // 唤醒loop_对应的thread线程,执行回调
                    loop_->queueInLoop( //为什么其他的回调函数都是直接执行,而这个回调需要在subeventloop中执行呢?
                        std::bind(writeCompleteCallback_, shared_from_this())
                    );
                }
                if (state_ == kDisconnecting)
                {
                    shutdownInLoop();
                }
            }//反之,如果buffer中还有数据没有完全写完,此时的channel仍然监听写事件。
        }
        else
        {
            LOG_ERROR("TcpConnection::handleWrite");
        }
    }
    else
    {
        LOG_ERROR("TcpConnection fd=%d is down, no more writing \n", channel_->fd());
    }
}

然后就是handlewrite() 这个函数就是注册的写事件发生时channel执行的回调函数。一般来说初学者(包括我)都会把注意力集中在可读事件上,也就是内核缓冲区数据后会通知epoll,然后进行读操作。 那么写事件是如何通知的呢? 内核中同样有写缓冲区,当我们想内核写入数据,同时写缓冲区满了,那么我们就要注册可写事件,一旦内核的写缓冲区有空余地方,立刻通知channel执行写事件。(这里的实现好像跟之前写的Tinywebserver不太一样,tinywebsever都是统一应用层写入到buffer中,然后注册写事件)。这里是直接就写,只有缓冲区满了才会注册写事件,当然后面会提到实现。

ok看完了回调的读写。我们再来看一下如何直接执行写事件。

void TcpConnection::send(const std::string &buf)
{
    if (state_ == kConnected)
    {
        if (loop_->isInLoopThread())
        {//判断loop与线程是否是同一个,只有在单loop的时候才会执行这里,因为send函数是用户直接调用的
            sendInLoop(buf.c_str(), buf.size());
        }
        else
        {//不是在一个线程里,就去加入到对应loop线程 的functor中。
            loop_->runInLoop(std::bind(
                &TcpConnection::sendInLoop,
                this,
                buf.c_str(),
                buf.size()
            ));
        }
    }
}
​
/**
 * 发送数据  应用写的快, 而内核发送数据慢, 需要把待发送数据写入缓冲区, 而且设置了水位回调
 */ 
void TcpConnection::sendInLoop(const void* data, size_t len)
{
    ssize_t nwrote = 0;
    size_t remaining = len;
    bool faultError = false;
​
    // 之前调用过该connection的shutdown,不能再进行发送了
    if (state_ == kDisconnected)
    {
        LOG_ERROR("disconnected, give up writing!");
        return;
    }
​
    // 表示channel_第一次开始写数据,而且缓冲区没有待发送数据
    if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    {//如果channel没有监听write事件,同时 buff中也没有数据要写,那么就可以直接写入内核里面去。
        nwrote = ::write(channel_->fd(), data, len);
        if (nwrote >= 0)
        {
            remaining = len - nwrote; //剩余的有多少没有写入内核
            if (remaining == 0 && writeCompleteCallback_)
            {//如果所有的都写入到内核了,并且有回调函数,及执行回调函数
                // 既然在这里数据全部发送完成,就不用再给channel设置epollout事件了
                loop_->queueInLoop(
                    std::bind(writeCompleteCallback_, shared_from_this())
                );
            }
        }
        else // nwrote < 0
        {//下面就是错误处理了,可以忽略
            nwrote = 0;
            if (errno != EWOULDBLOCK)
            {
                LOG_ERROR("TcpConnection::sendInLoop");
                if (errno == EPIPE || errno == ECONNRESET) // SIGPIPE  RESET
                {
                    faultError = true;
                }
            }
        }
    }
​
    // 说明当前这一次write,并没有把数据全部发送出去,剩余的数据需要保存到缓冲区当中,然后给channel
    // 注册epollout事件,poller发现tcp的发送缓冲区有空间,会通知相应的sock-channel,调用writeCallback_回调方法
    // 也就是调用TcpConnection::handleWrite方法,把发送缓冲区中的数据全部发送完成
    if (!faultError && remaining > 0) 
    {//当上面执行了后  还有剩余的缓冲区数据。
        // 目前发送缓冲区剩余的待发送数据的长度
        size_t oldLen = outputBuffer_.readableBytes();
        if (oldLen + remaining >= highWaterMark_
            && oldLen < highWaterMark_
            && highWaterMarkCallback_)
        {//执行高水位的相关操作,限制写入的速度。
            loop_->queueInLoop(
                std::bind(highWaterMarkCallback_, shared_from_this(), oldLen+remaining)
            );
        }
        outputBuffer_.append((char*)data + nwrote, remaining);//将多余的没写入内核的数据写入到缓冲区
        if (!channel_->isWriting())
        {//注册可写事件,只要内核缓冲区有空余,那么就执行写回调
            channel_->enableWriting(); // 这里一定要注册channel的写事件,否则poller不会给channel通知epollout
        }
    }
}

TCPserver

最后就是tcpserver了,这个也是总领所有下属类的,也是连接用户操作与底层的一个中间层,通过这一个类我们所有的操作都能够非常清晰。先看看成员变量

class TcpServer : noncopyable //不允许拷贝构造或者赋值
{
     //所有connection的集合 用户名字来进行索引
     using ConnectionMap = std::unordered_map<std::string, TcpConnectionPtr>;
​
    EventLoop *loop_; // baseLoop 用户定义的loop
​
    const std::string ipPort_;
    const std::string name_;
    //监听的fd以及监听的事件,以及监听事件发生后的回调函数
    std::unique_ptr<Acceptor> acceptor_; //运行在mainLoop,任务就是监听新连接事件
    //eventloop 的线程池
    std::shared_ptr<EventLoopThreadPool> threadPool_; // one loop per thread
    //注意这里的回调函数是用户注册给TCPserver的
    ConnectionCallback connectionCallback_; // 有新连接时的回调
    MessageCallback messageCallback_; // 有读写消息时的回调
    WriteCompleteCallback writeCompleteCallback_; // 消息发送完成以后的回调
    //这个也是用户注册的,在线程执行的时候执行
    ThreadInitCallback threadInitCallback_; // loop线程初始化的回调
    std::atomic_int started_;
    
    int nextConnId_;
    ConnectionMap connections_; // 保存所有的连接
}

让我们看看这里面的核心的实现过程:

//首先是构造函数,在用户区我们直接传入的是建立的baseloop 以及名字和 绑定端口和ip以及一个端口复用的标志
TcpServer::TcpServer(EventLoop *loop,
                const InetAddress &listenAddr,
                const std::string &nameArg,
                Option option)
                : loop_(CheckLoopNotNull(loop))
                , ipPort_(listenAddr.toIpPort())
                , name_(nameArg)
                //创建一个新的accept,当然还没有运行监听的函数
                , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))
                //创建新的线程池,当然线程仍然没有执行
                , threadPool_(new EventLoopThreadPool(loop, name_))
                , connectionCallback_()
                , messageCallback_()
                , nextConnId_(1)
                , started_(0)
{
    // 当有先用户连接时,会执行TcpServer::newConnection回调,在accept中注册newconnection的回调函数
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, 
        std::placeholders::_1, std::placeholders::_2));
}
//开启server
void TcpServer::start()
{
    if (started_++ == 0) // 防止一个TcpServer对象被start多次
    {
        threadPool_->start(threadInitCallback_); // 启动底层的loop线程池
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));//向baseloop中注册listen函数
    }
}
​
//新连接进来时,也就是主线程监听的accept的fd发送读事件,执行下面的回调函数,创建新的连接并且生成的channel分发给下面的subloop
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    // 轮询算法,选择一个subLoop,来管理channel
    EventLoop *ioLoop = threadPool_->getNextLoop();  //通过轮询找到一个分发的subloop
    char buf[64] = {0};
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;
    std::string connName = name_ + buf;
​
    LOG_INFO("TcpServer::newConnection [%s] - new connection [%s] from %s \n",
        name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());
​
    // 通过sockfd获取其绑定的本机的ip地址和端口信息
    sockaddr_in local;
    ::bzero(&local, sizeof local);
    socklen_t addrlen = sizeof local;
    if (::getsockname(sockfd, (sockaddr*)&local, &addrlen) < 0)
    {
        LOG_ERROR("sockets::getLocalAddr");
    }
    InetAddress localAddr(local);
​
    // 根据连接成功的sockfd,创建TcpConnection连接对象
    TcpConnectionPtr conn(new TcpConnection( 
                            ioLoop,
                            connName,
                            sockfd,   // Socket Channel,在accept的回调函数,这个fd是accept返回的函数
                            localAddr,
                            peerAddr));
    connections_[connName] = conn;
    // 下面的回调都是用户设置给TcpServer=>TcpConnection=>Channel=>Poller=>notify channel调用回调
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
​
    // 设置了如何关闭连接的回调   conn->shutDown()
    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)
    );
​
    // 直接调用TcpConnection::connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

梳理整个流程

通过echo 回射服务器代码的 连接建立 和读事件的发生 和关闭连接 来梳理整个流程

连接的建立,一个新的连接建立的过程。

在用户区创建一个baseloop,然后在自己的回射服务器中 写入TCPserver的 messageCallBack函数 和 ConnectedCallback函数,设置sub线程数目。 设置绑定的ip和端口。创建回射服务器。然后开启回射服务器, 调用了TCPserver::start()函数。

void TcpServer::start()
{
    if (started_++ == 0) // 防止一个TcpServer对象被start多次
    {
        threadPool_->start(threadInitCallback_); // 启动底层的loop线程池
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
    }
}

该函数开启了所有的subloop子线程,创建了所有的subloop,此时这些loop都在epoll_wait()中阻塞。因为没有监听任何的channel。 然后 将创建的acceptor 中的listen函数,由于当前loop是base 同时也是主线程,所以直接执行acceptor的listen函数。

void Acceptor::listen()
{
    listenning_ = true;
    acceptSocket_.listen(); // listen
    acceptChannel_.enableReading(); // acceptChannel_ => Poller在底层注册fd事件
}

该函数开启listen fd 监听新连接的到了,并且在主loop上注册 该fd的读事件。

ok 上面就是服务器开启的过程。正在等待一个连接的到来。

当一个连接到来时,listenfd的读事件发生,主loop中的epoll_wait()返回,执行listen fd 读事件的回调函数。accept新的连接,然后调用 TCPserver中的newconnetion函数。通过轮询算法找到要分发的loop,然后创建新的connection,在这个新的connection的构造汇总也会创建新的channel,并且注册该channel的各种事件。然后将下面的函数加入到的dopendingfunctor中,然后唤醒该线程:

void TcpConnection::connectEstablished()
{
    setState(kConnected);
    channel_->tie(shared_from_this());
    channel_->enableReading(); // 向poller注册channel的epollin事件
    // 新连接建立,执行回调
    connectionCallback_(shared_from_this());
}

将channel绑定,并且注册到subloop的epoll中,调用连接建立回调函数。这时连接就正式建立,并且由subloop来监听该连接的读写事件。

读事件发生时的流程

当有读事件发生时,subloop的poll函数会返回,并且执行相应的hanleread() 函数,该函数就是读取数据到inputbuffer中,

然后执行我们的messageCallBack函数,回射服务器中该函数是 将收到的buffer‘数据全部序列化string拿出来然后再发送给对方。

关闭连接时发生的流程

当读事件返回时并且返回的整数是0。就执行handleclose()

void TcpConnection::handleClose()
{
    LOG_INFO("TcpConnection::handleClose fd=%d state=%d \n", channel_->fd(), (int)state_);
    setState(kDisconnected);
    channel_->disableAll(); 
​
    TcpConnectionPtr connPtr(shared_from_this());
    connectionCallback_(connPtr); // 执行连接关闭的回调
    closeCallback_(connPtr); // 关闭连接的回调  执行的是TcpServer::removeConnection回调方法
}

将连接状态设置成关闭连接,设置成不监听任何事件,调用closeCallback,也就是调用TCPserver中的removeconnection,然后调用

void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{
    LOG_INFO("TcpServer::removeConnectionInLoop [%s] - connection %s\n", 
        name_.c_str(), conn->name().c_str());
​
    connections_.erase(conn->name());
    EventLoop *ioLoop = conn->getLoop(); 
    ioLoop->queueInLoop(
        std::bind(&TcpConnection::connectDestroyed, conn)
    );
}

在server中删除connection,这个地方删除后 connection的析构函数就会执行(如果没有其他地方有share_ptr的话),然后调用

void TcpConnection::connectDestroyed()
{
    if (state_ == kConnected)
    {//在这次调用中不会进来
        setState(kDisconnected);
        channel_->disableAll(); // 把channel的所有感兴趣的事件,从poller中del掉
        connectionCallback_(shared_from_this());
    }
    channel_->remove(); // 把channel从poller中删除掉
}

最后会调用

void EPollPoller::removeChannel(Channel *channel) 
{
    int fd = channel->fd();
    channels_.erase(fd);
​
    LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);
    
    int index = channel->index();
    if (index == kAdded)
    {
        update(EPOLL_CTL_DEL, channel);
    }
    channel->set_index(kNew);
}

从epoll中删除该fd。这样 channel 与connection都关闭了。

总的来说关闭事件发生时,就是要将connection 与 对应的channel 都从相关的集合中删除,然后执行析构。channel 与 connection 都是谁在管理呢?channel是在TCPconnection创建的时候创建的,并且用unique_ptr来管理这个channel,所以当connection销毁的时候 channel也会随之销毁。 那么connection又是如何销毁的呢,connection由 TCPserver 的connectionMap 来管理 通过 share_ptr来管理内存,当从TCPserver的map中删除一个connection时,如果其他地方没有share_ptr ,该connection就会立刻析构,那么这里会直接析构connection吗,答案是不会,因为在handleclose()中新创建了一个share_ptr,然后根据这个ptr来执行closeCallback()只有这个 handleclose()执行完毕 connection才会析构,进而析构socket和channel。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值