muduo笔记 网络库(十)TcpConnection补充:ConnectionCallback/MessageCallback等回调

TcpConnection回调

TcpConnection回调有哪些?

TcpConnection为应用程序提供了几个用于处理连接及数据的回调接口,主要包括 ConnectionCallback(连接建立回调)、MessageCallback(接收到消息回调)、WriteCompleteCallback(写完成回调)、HighWaterMarkCallback(高水位回调)、CloseCallback(连接关闭回调)。

通过以下几个成员实现存储、判断:

    ConnectionCallback connectionCallback_;       // 连接回调
    MessageCallback messageCallback_;             // 收到消息回调
    WriteCompleteCallback writeCompleteCallback_; // 写完成回调
    HighWaterMarkCallback highWaterMarkCallback_; // 高水位回调
    CloseCallback closeCallback_;                 // 关闭连接回调

    size_t highWaterMark_;                        // 高水位阈值
    Buffer inputBuffer_;                          // 应用层接收缓冲区
    Buffer outputBuffer_; // FIXME: use list<Buffer> as output buffer. // 应用层发送缓冲区
    boost::any contex_;                           // 绑定一个用户自定义类型的上下文参数

connectionCallback_存放用户注册的连接回调, 会在以下几种情形回调:
1)Tcp连接确认建立时:TcpServer/TcpClient在Tcp连接建立时,通过newConenction新建TcpConnection对象时,回调TcpConnection::connectEstablished,进而回调connectionCallback_。
2)Tcp连接确认销毁时:TcpServer/TcpClient在移除Tcp连接时,通过removeConnectionInLoop销毁连接,回调connectionCallback_。
3)强制关闭Tcp连接时:TcpConnection强制销毁时,调用forceClose()/forceClose(),或者从sockfd read到数据量为0(对端关闭连接),进而调用handleClose,回调connectionCallback_。

messageCallback_存放用户注册的接收到消息回调,会在Channel调用handleRead处理读事件时,调用cpConnection::handleRead,从sockfd read到数据量>0,进而回调messageCallback_。

writeCompleteCallback_存放用户注册的写完成回调,会在数据发送完毕回调函数,所有的用户数据都已拷贝,outputBuffer_被清空也会回调该函数。

highWaterMarkCallback_存放用户注册的高水位回调,用户send数据时,会在应用发送缓存堆积数据过多(>highWaterMark_)时调用。

回调注册

注册回调接口:

    void setConnectionCallback(const ConnectionCallback& cb)
    { connectionCallback_ = cb; }

    void setMessageCallback(const MessageCallback& cb)
    { messageCallback_ = cb; }

    void setWriteCompleteCallback(const WriteCompleteCallback& cb)
    { writeCompleteCallback_ = cb; }

    void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
    { highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }

何时调用connectionCallback_?

调用connectionCallback_分为多种情况。connectionCallback_ 通常不应该为空,如果用户没有特殊设置,应该设为默认的defaultConnectionCallback。

1)Tcp连接建立时

/**
* TcpServer/TcpClient 调用newTcpConnection创建TcpConnection对象后, 用来做一些连接以建立的事后工作
*/
void TcpConnection::connectEstablished()
{
    loop_->assertInLoopThread();
    assert(state_ == kConnecting);
    setState(kConnected);
    channel_->tie(shared_from_this()); // tie this TcpConnection object to channel_
    channel_->enableReading(); // 使能监听读事件, 连接上有读事件发生时, 如收到对端数据, 会触发channel_::handleRead


    // FIXME: add by Martin
    assert(connectionCallback_ != nullptr); // connectionCallback_通常不允许为空, TcpServer/TcpClient可以设置为缺省值defaultConnectionCallback


    connectionCallback_(shared_from_this()); // 回调connectionCallback_
}

2)Tcp连接销毁时,通常由TcpServer/TcpClient主动移除通道事件触发

/**
* TcpServer/TcpClient移除通道事件时, 调用该函数销毁tcp连接
* @note 只有处于已连接状态(kConnected)的tcp连接, 才需要先更新状态, 关闭通道事件监听
*/
void TcpConnection::connectDestroyed()
{
    loop_->assertInLoopThread();
    if (state_ == kConnected) // 只有kConnected的连接, 才有必要采取断开连接动作
    {
        setState(kDisconnected);
        channel_->disableAll(); // 关闭通道事件监听


        connectionCallback_(shared_from_this()); // 调用连接回调
    }
    channel_->remove(); // 从EventLoop和Poller中移除监听通道事件
}

3)对端关闭连接(从sockfd read返回0),本地调用handleClose被动关闭

/**
* 处理Tcp连接关闭
* @details 更新状态为kDisconnected, 清除所有事件通道监听.
* @note 必须在所属loop线程中运行.
*/
void TcpConnection::handleClose()
{
    loop_->assertInLoopThread(); // 确保在所属loop线程中运行
    LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
    assert(state_ == kConnected || state_ == kDisconnecting);
    // we don't close fd, leave it to dtor, so we can find leaks easily.
    setState(kDisconnected); // 更新Tcp连接状态
    channel_->disableAll();     // 停止监听所有通道事件(读写事件)

    TcpConnectionPtr guardThis(shared_from_this());
    connectionCallback_(guardThis); // 连接回调
    // must be the last line
    closeCallback_(guardThis);      // 关闭连接回调
}

void TcpConnection::handleRead(Timestamp receiveTime)
{
    loop_->assertInLoopThread();
    int savedErrno = 0;
    ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); // 从指定fd读取数据到内部缓冲
    if (n > 0)
    {
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    }
    else if (n == 0)
    {
        handleClose();
    }
    ...
}

4)本地主动强制关闭(forceClose)Tcp连接,当然,connectionCallback_回调跟3)一样,还是在handleClose中发生的

/**
* 在所属loop循环中强制关闭连接, 只对连接为kConnected或kDisconnecting状态才有效
*/
void TcpConnection::forceCloseInLoop()
{
    loop_->assertInLoopThread();
    if (state_ == kConnected || state_ == kDisconnecting)
    {
        // as if we received 0 byte in handleRead()
        handleClose();
    }
}

/**
* 强制关闭连接, 只对连接为kConnected或kDisconnecting状态才有效
* @details 为防止意外, 动作应该放到loop末尾去做
*/
void TcpConnection::forceClose()
{
    // FIXME: use compare and swap
    if (state_ == kConnected || state_ == kDisconnecting)
    {
        setState(kDisconnecting);
        loop_->queueInLoop(std::bind(&TcpConnection::forceCloseInLoop, shared_from_this()));
    }
}

何时调用messageCallback_?

前面在handleRead()中已经看到,当从sockfd read返回>0时,就会回调messageCallback_,交给应用层处理从对端接收来的数据(存放到inputBuffer_中)。

void TcpConnection::handleRead(Timestamp receiveTime)
{
    loop_->assertInLoopThread();
    int savedErrno = 0;
    ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); // 从指定fd读取数据到内部缓冲
    if (n > 0)
    {
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    }
    ...
}

何时调用writeCompleteCallback_,highWaterMarkCallback_?

TcpConnection::send()发送数据时,会转为在IO线程中调用TcpConnection::sendInLoop。将要发送的数据写到内核缓冲区,然后回调writeCompleteCallback_;如果一次没有写完,剩余数据量较大,超过highWaterMark_(高水位),就回调highWaterMarkCallback_。

调用顺序:
send() => sendInLoop() => writeCompleteCallback_(), highWaterMarkCallback_() => handleWrite()

void TcpConnection::sendInLoop(const char *data, size_t len)
{
    string s;
    std::reverse(s.begin(), s.end());
    loop_->assertInLoopThread();
    ssize_t nwrote = 0;
    size_t remaining = len;
    bool faultError = false;
    if (state_ == kDisconnected) // 如果已经断开连接(kDisconnected), 就无需发送, 打印log(LOG_WARN)
    {
        LOG_WARN << "disconnected, give up writing";
        return;
    }

    // write一次, 往对端发送数据, 后面再看是否发生错误, 是否需要高水位回调
    // if no thing output queue, try writing directly
    if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    { // 如果通道没有使能监听写事件, 并且outputBuffer 没有待发送数据, 就直接通过socket写
        nwrote = sockets::write(channel_->fd(), data, len);
        if (nwrote >= 0)
        {
            remaining = len - nwrote;
            // 写完了data[len]到到内核缓冲区, 就回调writeCompleteCallback_
            if (remaining == 0 && writeCompleteCallback_)
            {
                loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
            }
        }
        else // nwrote < 0, error
        {
            nwrote = 0;
            if (errno != EWOULDBLOCK) // EWOULDBLOCK: 发送缓冲区已满, 且fd已设为nonblocking
            { // O_NONBLOCK fd, write block but return EWOULDBLOCK error
                LOG_SYSERR << "TcpConnection::sendInLoop";
                if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
                { // EPIPE: reading end is closed; ECONNRESET: connection reset by peer
                    faultError = true;
                }
            }
        }
    }

    // 处理剩余待发送数据
    // 没有错误, 并且还有没有写完的数据, 说明内涵发送缓冲区满, 要将未写完的数据添加到output buffer中
    assert(remaining <= len);
    if (!faultError && remaining > 0) // 没有故障, 并且还有待发送数据, 可能是发送太快, 对方来不及接收
    { // no error and data remaining to be written
        size_t oldLen = outputBuffer_.readableBytes(); // Buffer中待发送数据(readable空间数据)

        // 如果readable空间数据 + 未写完数据, 超过highWaterMark_(高水位), 回调highWaterMarkCallback_
        if (oldLen + remaining >= highWaterMark_
        && oldLen < highWaterMark_
        && highWaterMarkCallback_)
        {
            loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
        }
        // append data to be written to the output buffer
        outputBuffer_.append(static_cast<const char*>(data) + nwrote, remaining);
        // enable write event for channel_
        if (!channel_->isWriting())
        { // 如果没有在监听通道写事件, 就使能通道写事件
            channel_->enableWriting();
        }
    }
}

/**
* 内核发送缓冲区有空间了, 回调该函数.
* @details 在send()之后触发. send()通常由用户主动调用, handleWrite由epoll_wait/poll监听,
* 由EventLoop对应IO线程回调.
*/
void TcpConnection::handleWrite()
{
    loop_->assertInLoopThread();
    if (channel_->isWriting())
    { // 只有通道在监听写事件时, 处理write事件才有意义
        // 继续往内核缓冲区写readable空间的待发送数据
        ssize_t n = sockets::write(channel_->fd(),
                                   outputBuffer_.peek(),
                                   outputBuffer_.readableBytes());
        if (n > 0)
        {
            outputBuffer_.retrieve(n);
            if (outputBuffer_.readableBytes() == 0)
            { // 发送缓冲区已清空
                channel_->disableWriting();
                if (writeCompleteCallback_)
                { // 应用层发送缓冲区已被清空, 就回调writeCompleteCallback_
                    loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
                }
                if (state_ == kDisconnecting)
                { // 发送缓冲区已清空, 并且连接状态是kDisconnecting, 要关闭连接
                    shutDownInLoop(); // 关闭连接 写方向
                }
            }
        }
        else
        {
            LOG_SYSERR << "TcpConnection::handleWrite";
        }
    }
}

上下文数据传递

input buffer、output buffer是Tcp连接中,伴随着一次或几次请求、应答的数据,是与对端进行沟通的数据。如果想要一下数据,伴随着整个Tcp连接的声明周期,该怎么办?
可以使用TcpConnection::contex_,在连接建立时(回调onConnection),创建boost::any对象(可变类型),然后让TcpConnection::contex_指向该对象。这样,在整个TcpConnection生命周期内,都可以直接通过contex_访问该对象;直到沟通完成,或者连接关闭时,reset contex_。

public:
    ...
    void setContext(const boost::any& contex)
    { contex_ = contex; }

    const boost::any& getContext() const
    { return contex_; }

    boost::any* getMutableContext()
    { return &contex_; }
    ...
private:
    boost::any contex_;                           // 用户自定义参数
    ...

比如,对于一个HttpSever,可以中连接回调onConnection中存放一个HttpContext对象;在收到对端消息,回调onMessage时,可以获得该HttpContext对象;在确认解析完HTTP请求后,可以reset 重置context上下文。

void HttpServer::onConnection(const TcpConnectionPtr &conn)
{
    if (conn->connected()) // 确认已处于连接建立状态
    {
        conn->setContext(HttpContext()); // 让contex_ 指向一个HttpContext对象
    }
}

void HttpServer::onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receivedTime)
{
    // 从TcpConnection获取contex_ 上下文,然后将内容转型为所需的HttpContext类型
    HttpContext* context = boost::any_cast<HttpContext>(conn->getMutableContext()); 

    if (!context->parseRequest(buf, receivedTime))
    { // parse request failure
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }

    if (context->gotAll())
    {
        onRequest(conn, context->request());
        context->reset();
    }
}

知识点

boost::any 类型

可变类型boost::any可以对任意类型安全存储、安全取回。boost::any相当于C语言里面的void*,但在C++中不是类型安全的。中STL中存放不同类型的方法,如vector<boost::any>。

当需要用boost::any进行存储时,可以利用boost::any指向要存储的对象;

当需要取出内容时,可以用boost::any_cast将取出的内容转型为存放时代对象类型。

#include <boost/any.hpp>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
    vector<boost::any> someValues;
    someValues.emplace_back(10);                       // 存放一个int数据
    someValues.emplace_back(string("test"));           // 存放一个string对象

    int& a = boost::any_cast<int&>(someValues.front());      // 取出时转型为int&
    cout << a << endl; // 打印10
    string& s = boost::any_cast<string&>(someValues.back()); // 取出时转型为string&
    cout << s << endl; // 打印test
    return 0;
}

muduo库其它部分解析参见:muduo库笔记汇总

posted @ 2022-04-13 00:28  明明1109  阅读(736)  评论(0编辑  收藏  举报