muduo源码分析——TcpConnection

muduo中的TcpConnection算是整个框架中的大头吧,因为每个客户端连接对应一个Channel和一个TcpConnection,而之前已经说了,Channel只是一个通道,那么对于客户端连接的各种处理,基本都是在TcpConnection上完成的了。
这个类的cpp文件算是muduo里比较长的了,有400+行。

注意我参考的代码时flamingo的代码,是基于muduo,或者会有些出入。

TcpConnection的成员

    private:
		EventLoop*                  loop_;//这个TcoConnection在哪个EventLoop里
		const string                name_;
		StateE                      state_;  // FIXME: use atomic variable
		// we don't expose those classes to client.
		std::shared_ptr<Socket>     socket_;//这个连接对应的socket
		std::shared_ptr<Channel>    channel_;//对应的Channel
		const InetAddress           localAddr_;//这里的地址信息
		const InetAddress           peerAddr_;//对面的地址信息
		ConnectionCallback          connectionCallback_;
		MessageCallback             messageCallback_;//在TcoConnection中的handleread函数中被调用。
		WriteCompleteCallback       writeCompleteCallback_;
		HighWaterMarkCallback       highWaterMarkCallback_;
		CloseCallback               closeCallback_;
		size_t                      highWaterMark_;
		Buffer                      inputBuffer_;//输入和输出的缓冲
		Buffer                      outputBuffer_; // FIXME: use list as output buffer.

我给主要的都加上了注释,相信理解起来会比较轻松。接下来我们看看初始化。

TcpConnection::TcpConnection(EventLoop* loop, const string& nameArg, int sockfd, const InetAddress& localAddr, const InetAddress& peerAddr)
    : loop_(loop),
    name_(nameArg),
    state_(kConnecting),
    socket_(new Socket(sockfd)),//new了一个新的socket,对应这条连接
    channel_(new Channel(loop, sockfd)),//new了一个新的Channel
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64 * 1024 * 1024)//高水位为64M,暂时还没去彻底研究buffer
{ //绑定回调函数,当Channel收到请求,需要调用对应的事件函数时,就会调用下面的函数,也是我们需要特别关心的函数
    channel_->setReadCallback(std::bind(&TcpConnection::handleRead, this, std::placeholders::_1));
    channel_->setWriteCallback(std::bind(&TcpConnection::handleWrite, this));
    channel_->setCloseCallback(std::bind(&TcpConnection::handleClose, this));
    channel_->setErrorCallback(std::bind(&TcpConnection::handleError, this));
    LOGD("TcpConnection::ctor[%s] at 0x%x fd=%d", name_.c_str(), this, sockfd);
    socket_->setKeepAlive(true);

同样的,我加上了注释。

接下来我们看看上面绑定的回调函数,它们是具体处理消息的函数,算是最重要的函数了。

handleRead

void TcpConnection::handleRead(Timestamp receiveTime)
{
    loop_->assertInLoopThread();
    int savedErrno = 0;
    int32_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
    if (n > 0)
    {
        //messageCallback_指向CTcpSession::OnRead(const std::shared_ptr& conn, Buffer* pBuffer, Timestamp receiveTime)
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//喊客户代码来拿数据了
    }
    else if (n == 0)
    {
        handleClose();//关闭连接
    }
    else
    {
        errno = savedErrno;
        LOGSYSE("TcpConnection::handleRead");
        handleError();
    }
}

代码逻辑不复杂,只是int32_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);比较难理解,现在卡在了readv函数上,有待进一步解决。

handleWrite

void TcpConnection::handleWrite()
{
    loop_->assertInLoopThread();
    if (channel_->isWriting())//暂不明
    {
        int32_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());//向socket中写入 outputBuffer_.readableBytes个字节的buffer内的内容
        if (n > 0)//n是写入的字节数
        {
            outputBuffer_.retrieve(n);//buffer释放n个字节的空间,也不能说释放,buffer是通过移动指针来操作区间的。
            if (outputBuffer_.readableBytes() == 0)//当没有数据了
            {
                channel_->disableWriting();//那你都没数据了,还怎么写?,所以disable掉
                if (writeCompleteCallback_)
                {
                    loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));//回调告诉写完了
                }
                if (state_ == kDisconnecting)//暂不明
                {
                    shutdownInLoop();//最终调用到socket.shutdown,优雅的关闭连接。需要进一步探讨
                }
            }
        }
        else
        {
            LOGSYSE("TcpConnection::handleWrite");
            // if (state_ == kDisconnecting)
            // {
            //   shutdownInLoop();
            // }
            //added by zhangyl 2019.05.06
            handleClose();
        }
    }
    else
    {
        LOGD("Connection fd = %d  is down, no more writing", channel_->fd());
    }
}

突然发现把东西写在代码的注释里,比说一大堆话,清晰的多,但还是要总结一下吧。
总的来说就是向socket写入了n个数据,readableBytes返回的是buffer里面可以读到多少个字节,那么对于输出buffer来说,其实就是可以从buffer中取多少个字节,然后往socket里写。
有很多的标记位,比如state_,event_等,暂时还没理清他们的关系。

handleClose

这个函数是用来关闭TcpConnection的。

void TcpConnection::handleClose()
{
    //在Linux上当一个链接出了问题,会同时触发handleError和handleClose
    //为了避免重复关闭链接,这里判断下当前状态
    //已经关闭了,直接返回
    if (state_ == kDisconnected)
        return;
    
    loop_->assertInLoopThread();
    LOGD("fd = %d  state = %s", channel_->fd(), stateToString());
    //assert(state_ == kConnected || state_ == kDisconnecting);
    // we don't close fd, leave it to dtor, so we can find leaks easily.
    setState(kDisconnected);//设置状态为切断的
    channel_->disableAll();//

    TcpConnectionPtr guardThis(shared_from_this());
    connectionCallback_(guardThis);//这里跟建立连接时一样,再次调用了这个回调函数,所以在用户代码中,需要进行分情况,看到底时建立连接还是断开连接
    // must be the last line
    closeCallback_(guardThis);//调用关闭连接的回调函数,最终调用到TcpConnection::connectDestroyed

}

流程是,输出日志,设置状态,关闭通道,通知客户代码关闭连接,最后调用TcpConnection的销毁连接代码。整体还是不难理解的,就是要注意的一点是,最后销毁TcpConnection的时候先回调到了TcpServer中,然后再通过EventLoop执行了TcpConnection的销毁函数,可以看出TcpServer才是老大哈哈,代码功能,解耦还是做的不错滴。

这个handleClose函数一般是在别的函数里被调用的,比如强制关闭。

send函数和sendInLoop函数

send函数比较好理解,就是发送数据用的,这是TcpConnection提供给外部的一个接口。我们会发现有好多send函数和sendInLoop函数。send算是接口,最终都是调用到sendInLoop函数,由sendInLoop函数来执行具体的发送操作。
先来看看sendInLoop函数的代码

void TcpConnection::sendInLoop(const string & message)
{
    sendInLoop(message.c_str(), message.size());//将string的传入参数拆分后在放入真正的发送函数中。
}

void TcpConnection::sendInLoop(const void* data, size_t len)
{
    loop_->assertInLoopThread();
    int32_t nwrote = 0;
    size_t remaining = len;
    bool faultError = false;
    if (state_ == kDisconnected)
    {
        LOGW("disconnected, give up writing");
        return;
    }
    // if no thing in output queue, try writing directly
    if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    {
        nwrote = sockets::write(channel_->fd(), data, len);
        //TODO: 打印threadid用于调试,后面去掉
        //std::stringstream ss;
        //ss << std::this_thread::get_id();
        //LOGI << "send data in threadID = " << ss;

        if (nwrote >= 0)//表示,发送成功
        {
            remaining = len - nwrote;//剩下多少没写的
            if (remaining == 0 && writeCompleteCallback_)
            {
                loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));//回调告诉客户代码:我写完了
            }
        }
        else // nwrote < 0//出问题了
        {
            nwrote = 0;
            if (errno != EWOULDBLOCK)
            {
                LOGSYSE("TcpConnection::sendInLoop");
                if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
                {
                    faultError = true;
                }
            }
        }
    }

    //assert(remaining <= len);
    if (remaining > len)//出现的情况是,nwrote小于0,所以退出了
        return;

    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(static_cast<const char*>(data) + nwrote, remaining);//把没写的添加到输出缓存区
        if (!channel_->isWriting())
        {
            channel_->enableWriting();
        }
    }
}

我们来看一看其中一个send函数的代码

void TcpConnection::send(const void* data, int len)
{
    if (state_ == kConnected)
    {
        if (loop_->isInLoopThread())//如果是当前这个线程,就直接调用sendInLoop函数去发送数据
        {
            sendInLoop(data, len);
        }
        else//如果不是当前这个线程,则扔给 eventLoop去处理
        {
            string message(static_cast<const char*>(data), len);
            loop_->runInLoop(
                std::bind(static_cast<void (TcpConnection::*)(const string&)>(&TcpConnection::sendInLoop),
                    this,     // FIXME
                    message));
        }
    }
}

逻辑比较明确了,send函数作为接口,接受客户代码的调用,然后调用sendInLoop函数,来执行具体的操作。并没有什么逻辑上的难点,就是要注意判断不同的情况,具体在代码中注释了。
简单分析一个点:当输出缓冲区有数据的时候,会append到输出缓冲区中,靠的是第一,remain=len>0,第二,faultError 为false,因为不会执行第一个if,所以不会有机会被改成true。那么就会执行到后面的append函数。

connectEstablished函数

注意我参考的代码和陈硕muduo的源码有些不同的地方,不过本质还是差不多的。

void TcpConnection::connectEstablished()
{
    loop_->assertInLoopThread();
    if (state_ != kConnecting)
    {
        //一定不能走这个分支
        return;
    }
        
    setState(kConnected);//将state状态设置为已连接
    channel_->tie(shared_from_this());//shared_from_this功能为返回一个当前类的std::share_ptr

    //假如正在执行这行代码时,对端关闭了连接
    if (!channel_->enableReading())
    {
        LOGE("enableReading failed.");
        //setState(kDisconnected);
        handleClose();
        return;
    }

    //connectionCallback_指向void XXServer::OnConnection(const std::shared_ptr& conn)
    connectionCallback_(shared_from_this());
}

这个函数比较简单,设置状态,然后调用连接建立完成的回调。Channel->tie_是一个std::weak_ptr,可以认为是和Channel绑在一起的对象,为什么里面是void呢,之前的文章也说过,一个Channel对应一个TcpConnection,或者对应一个Acceptor。所以是void,因为这样可以指向不同的数据类型

你可能感兴趣的:(c++网络编程)