Muduo(C++11版本) 源码剖析(五)———TcpConnection设计

每接受到一个连接,就会创建一个TcpConnection,每个TcpConnection用shared指针保存在TcpServer中,处理的回调都经过该类,这里最需要注意的就是关于TcpConnection的生命周期。

成员变量:

    private:
		EventLoop*                  loop_;
		const string                name_;
		StateE                      state_;
		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_; 

loop是TcpConnection所在的循环,name是创建时传入的连接的一些信息,state_是TcpConnection当前状态机,有连接中,断开连接中,已连接和已断开4个状态,socket是传入的文件描述符,channel是创建的通道,里面含有一个TcpConnection的一个弱指针,适当的时候提升(Channel那期博客讲过),localAddr和peerAddr是对端和本端的地址信息,接下来就是几个回调函数,最后是输入和输出缓冲区Buff。

 

重要函数及作用:

创建

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)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64 * 1024 * 1024)
{ 
    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);
}
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
    loop_->assertInLoopThread();
    EventLoop* ioLoop = eventLoopThreadPool_->getNextLoop();
    char buf[32];
    snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);
    ++nextConnId_;
    string connName = name_ + buf;

    LOGD("TcpServer::newConnection [%s] - new connection [%s] from %s", name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());

    InetAddress localAddr(sockets::getLocalAddr(sockfd));
    TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
	//放入map容器管理
    connections_[connName] = conn;
    conn->setConnectionCallback(connectionCallback_);//指向业务层Session
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)); // FIXME: unsafe
    //该线程分离完io事件后,立即调用TcpConnection::connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

当Acceptor内通道检测到新连接事件最终回调到TcpServer的newConnection函数,参数还有对端的地址信息以及网络文件描述符,之后从线程池中获得一个线程的EventLoop,创建一个TcpConnection,可以看到在构造函数中传入eventloop,名称信息,文件描述符,对端,本端地址信息,并在构造函数中创建Channel,设置channel事件处理的回调。创建完毕,再向TcpConnection中传入到各个类的回调函数,最后在对应IO线程中调用connectEstablished。

 

读事件处理

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<TcpConnection>& conn, Buffer* pBuffer, Timestamp receiveTime)
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    }
    else if (n == 0)
    {
        handleClose();
    }
    else
    {
        errno = savedErrno;
        LOGSYSE("TcpConnection::handleRead");
        handleError();
    }
}

由IO多路复用检测到读事件,随后通过Channel回调到TcpConnection::handleRead,之后再回调到我们用户设置的业务层Session的某个函数进行处理。

 

写事件处理

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)
        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();
        }
    }
}
void TcpConnection::handleWrite()
{
    loop_->assertInLoopThread();
    if (channel_->isWriting())
    {
        int32_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());
        if (n > 0)
        {
            outputBuffer_.retrieve(n);
            if (outputBuffer_.readableBytes() == 0)
            {
                channel_->disableWriting();
                if (writeCompleteCallback_)
                {
                    loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
                }
                if (state_ == kDisconnecting)
                {
                    shutdownInLoop();
                }
            }
        }
        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());
    }
}

在类中重载了几个send()函数,实际上都会调用sendInLoop函数,那我们来看看sendInLoop这个函数。首先如果channel设置isWiriting或者发送缓冲区没有数据,那么将会直接调用write()。接下来会出现两种情况,一个是全部拷贝到了内核缓冲区,那么发送完毕,直接调用发送完成回调函数;二是遇到一些未发送或者未发送完全那么会压入发送缓冲区,如果超过缓冲区,则会触发高水位回调函数,采取一些措施,可能会关闭连接。压入缓冲区之后,然后使当前通道重新关注可写事件,当可写的时候,直接触发写回调,再写入缓冲区的字节。

 

关闭连接事件处理:

void TcpConnection::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);
}
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
    // FIXME: unsafe
    loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
{
    loop_->assertInLoopThread();
    LOGD("TcpServer::removeConnectionInLoop [%s] - connection %s", name_.c_str(), conn->name().c_str());
    size_t n = connections_.erase(conn->name());
    if (n != 1)
    {
        //出现这种情况,是TcpConneaction对象在创建过程中,对方就断开连接了。
        LOGD("TcpServer::removeConnectionInLoop [%s] - connection %s, connection does not exist.", name_.c_str(), conn->name().c_str());
        return;
    }
    EventLoop* ioLoop = conn->getLoop();
    ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
}
void TcpConnection::connectDestroyed()
{
    loop_->assertInLoopThread();
    if (state_ == kConnected)
    {
        setState(kDisconnected);
        channel_->disableAll();

        connectionCallback_(shared_from_this());
    }
    channel_->remove();
}

关闭连接事件很重要,涉及到TcpConnection和Channel的生命周期以及是否能合理销毁,用了智能指针来管理和控制生命周期。下面我们就来分析一下断开流程中TcpConnection的引用计数问题:

1.首先连接到来创建TcpConnection,并存入容器。--------------------------------------------------------------------------引用计数+1  总数:1

2.客户端断开连接,在Channel的handleEvent函数中会将Channel中的TcpConnection弱指针提升--------------引用计数+1  总数:2

3.触发HandleRead ,可读字节0,进而触发HandleClose,HandleClose函数中栈上的TcpConnectionPtr guardThis会继续将------------------------------------------------------------------------------------------------------------------------------------------------------引用计数+1  总数:3

4.触发HandleClose的回调函数 在TcpServer::removeConnection结束后(回归主线程队列),释放HandleClose的栈指针,以及Channel里提升的指针----------------------------------------------------------------------------------------------------------------引用计数-2   总数:1

5.主线程执行回调removeConnectionInLoop,在函数内部将tcpconnection从TcpServer中保存连接容器中erase掉。但在removeConnectionInLoop结尾用conn为参数构造了bind。---------------------------------------------------------------引用计数不变 总数:1

6.回归次线程处理connectDestroyed事件,结束完释放参数传递的最后一个shard_ptr,释放TcpConnection。------引用计数-1 总数:0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值