[muduo网络库]——EchoServer之连接建立(剖析muduo网络库核心部分、设计思想)

上一节我们剖析到了服务器的启动[muduo网络库]——从EchoServer开始启动服务器(剖析muduo网络库核心部分、设计思想),启动服务器以后,所有线程都处于阻塞在poller_->pollepoll_wait上,等待新连接的到来,那么这一篇我们来看看连接的建立。

连接建立

  • EchoServer server(&loop,addr,"EchoServer-01"),在创建一个TcpServer对象时,会调用了TcpServer的构造函数,在构造函数中实例化了一个Acceptor对象acceptor_(new Acceptor(loop,listenAddr,option == kReusePort)),并给Acceptor对象注册了一个回调函数TcpServer::newConnection(),注意这里的newConnetionCallback_ 被给了TcpServer::newConnection()
TcpServer::TcpServer(EventLoop *loop,
            const InetAddress &listenAddr,
            const std::string  &nameArg,
            Option option)
            : loop_(CheckLoopNotNull(loop))
            , ipPort_(listenAddr.toIpPort())
            , name_(nameArg)
            , acceptor_(new Acceptor(loop,listenAddr,option == kReusePort))
            , threadPool_(new EventLoopThreadPool(loop,name_))
            , connectionCallback_()
            , messageCallback_()
            , nextConnId_(1)
            , started_(0)
{
    //当新用户连接时,会执行TcpServer::newConnection回调
    acceptor_->setNewConnetionCallback(std::bind(&TcpServer::newConnection, this,
        std::placeholders::_1,std::placeholders::_2));
}

void setNewConnetionCallback(const NewConnectionCallback &cb)
{
    newConnetionCallback_ = cb;
}
  • 当TcpServer构造函数中实例化Acceptor对象时,Acceptor的构造函数中实例化了一个Channel对象,acceptChannel_(loop, acceptSocket_.fd()),这个Channel对象封装了服务器监听套接字文件描述符。并且注册了回调函数Acceptor::handleRead( )
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool resuseport)
    : loop_(loop)
    , acceptSocket_(createNonblocking()) //创建sock
    , acceptChannel_(loop, acceptSocket_.fd()) //封装成channel
    , listenning_(false)
{
    acceptSocket_.setReuseAddr(true); //更改TCP选项
    acceptSocket_.setReusePort(true); 
    acceptSocket_.bindAddress(listenAddr); //绑定套接字
    // Tcpserver::start() Acceptor.listen 有新用户的连接
    // 执行一个回调(connfd=> channel => subloop)
    // baseloop => acceptChannel_(listenfd) => 
    acceptChannel_.setReadCallback(std::bind(&Acceptor::headleRead,this));
}
  • 此时,当有新连接到来时,主线程的Poller检测到可读事件,程序处理新客户连接请求,主线程解除阻塞,就会执行Channel上的可读事件回调函数Acceptor::handleRead()
void Acceptor::headleRead()
{
    InetAddress peerAddr;
    int connfd = acceptSocket_.accept(&peerAddr);
    if(connfd >= 0)
    {
        if (newConnetionCallback_)
        {
            newConnetionCallback_(connfd,peerAddr);//轮询找到SUBLOOP唤醒,分发当前的新客户端的Channel
        }
        else
        {
            ::close(connfd);
        }
    }
    else
    {
        LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__,__FUNCTION__,__LINE__,errno); 
        if (errno == ENFILE)
        {
            LOG_ERROR("%s:%s:%d sockfd reached limit! \n", __FILE__,__FUNCTION__,__LINE__); 
        } 
    }
}
  • Acceptor::handleRead(),首先执行int connfd = acceptSocket_.accept(&peerAddr);,即调用了Socket::accept(),在底层调用了系统的accept4函数,接受新客户连接。接着调用TcpServer::newConnection( )函数;
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    //轮询算法,选择一个subloop,来管理channel
    EventLoop *ioLoop = threadPool_->getNextLoop();
    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());

    //通过socket获取其绑定的本机的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连接对象conn
    TcpConnectionPtr conn(new TcpConnection(
        ioLoop,
        connName,
        sockfd,
        localAddr,
        peerAddr));

    //下面的回调都是用户设置给TcpServer=>TcpConnection=>Channel=>Poller=>notify channel调用回调
    connections_[connName] = conn;
    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));
}
  • TcpServer::newConnection( )中,ioLoop是轮询选择出来的子EventLoop(子Reactor),然后根据连接成功的sockfd,创建 TcpConnection连接对象conn,并给其注册回调。
值得学习的一点: 我们注意到创建了 TcpConnection对象时,使用了共享型智能指针TcpConnectionPtr 来管理这个对象.using TcpConnectionPtr = std::shared_ptr<TcpConnection>;.在剖析TcpConnection类时,我们也说过它是唯一默认用shared_ptr来管理的类,唯一继承自enable_shared_from_this的类。这是因为其生命周期模糊:可能在连接断开时,还有其他地方持有它的引用,贸然delete会造成空悬指针。只有确保其他地方没有持有该对象的引用的时候,才能安全地销毁对象。
  • 接着执行ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));runInLoop()会唤醒ioLoop所在的子线程,让子线程不在阻塞在poller_->poll上,执行TcpConnection::connectEstablished()
void TcpConnection::connectEstablished()
{
    setState(kConnected);
    channel_->tie(shared_from_this());
    channel_->enableReading(); //向poller注册channel的epollin事件

    //新连接建立 执行回调
    connectionCallback_(shared_from_this());
}

  • 在我们执行TcpConnection::connectEstablished( )时,把新连接conn上的可读事件注册到ioLoop对应的Poller上。
  • 执行结束后,又会继续阻塞在poller_->poll处。这时,子线程的poller开始关注新连接对应channel_上的可读事件。
  • 主线程执行完TcpServer::newConnection函数,也会继续阻塞在poller_->poll,等待新连接到来。

代码地址:https://github.com/Cheeron955/mymuduo/tree/master

好了~ 有关于服务器的连接建立,我们就分析到这,连接建立以后,所有线程都处于阻塞在poller_->poll上,等待事件发生,主线程阻塞等待新连接的到来,那么消息是如何读取的呢?我们在下一节进行剖析,下一节见~
  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值