muduo源码解析28-网络库6:acceptor类

acceptor类:

说明:

之前reactor模型他们的主要任务是封装了
while(1)
{
poll();
handleEvent();
}

这肯定是有问题的,因为我们socket API构建服务器的步骤不可能是这三步啊,
应该是
创建socket()--->绑定套接字地址bind()--->监听listen()--->然后才是
while(1)
{
poll();
handleEvent();
}

accpetor的作用不难猜出来对前三步socket(),bind(),listen()实现了封装
但是不仅仅是这些,acceptor实现了对于接受一个连接acceptor()操作的封装.
这个acceptor是为tcpserver服务的,不过在这里我们也可以用acceptor简单的实现接受一个连接

acceptor.h

#ifndef ACCEPTOR_H
#define ACCEPTOR_H

#include"net/channel.h"
#include"net/socket.h"

namespace mymuduo {

namespace net {

class eventloop;
class inetaddress;

class acceptor:noncopyable
{
public:
    typedef std::function<void(int sockfd,const inetaddress&)> NewConnectionCallback;
    //构造器负责创建一个TCP服务器,创建套接字->绑定->监听,三个步骤
    acceptor(eventloop* loop,const inetaddress& listenaddr,bool reuseport=true);
    //析构函数,负责关闭套接字,取消m_acceptChannel上所有的网络事件,表示退出和不再接受处理任务网络事件.
    ~acceptor();

    //设置接受连接时的回调函数,在成功接收一个客户机的连接时调用 m_newConnectionCallback
    void setNewConnectionCallback(const NewConnectionCallback& cb)
    {
        m_newConnectionCallback=cb;
    }

    //是否正在监听
    bool listenning() const{return m_listenning;}
    void listen();  //监听操作

private:
    void handleRead();  //处理服务器accept

    eventloop* m_loop;                              //acceptor所属于的那个eventloop
    Socket m_acceptSocket;                          //接受连接的Socket,即server socket
    channel m_accpetChannel;                        //m_acceptSocket对应的的channel
    NewConnectionCallback m_newConnectionCallback;  //新连接建立时所调用的回调函数
    bool m_listenning;                              //是否正在监听
    int m_idleFd;                          //::open("/dev/null",O_RDONLY|O_CLOEXEC)

};

}//namespace net

}//namespace mymuduo

#endif // ACCEPTOR_H

acceptor.cpp:

#include "acceptor.h"

#include"base/logging.h"
#include"net/eventloop.h"
#include"net/inetaddress.h"
#include"net/socketsops.h"

#include<errno.h>
#include<fcntl.h>
#include<unistd.h>

namespace mymuduo {

namespace net {

//构造器负责创建一个TCP服务器,创建套接字->绑定->监听,三个步骤. 接受连接的步骤在acceptor::handleRead()中
//回调acceptor::handleRead()来处理
acceptor::acceptor(eventloop* loop,const inetaddress& listenaddr,bool reuseport)
    :m_loop(loop),
      m_acceptSocket(sockets::createNonblockingOrDie(listenaddr.family())),
      m_accpetChannel(m_loop,m_acceptSocket.fd()),
      m_listenning(false),
      m_idleFd(::open("/dev/null",O_RDONLY|O_CLOEXEC))
{
    assert(m_idleFd>=0);
    //设置地址重用,端口重用,绑定Socket和inetaddress
    m_acceptSocket.setReuseAddr(true);
    m_acceptSocket.setReusePort(reuseport);
    m_acceptSocket.bindAddress(listenaddr);
    //设置读事件回调函数,一旦poller::poll发生读事件,channel就会回调这个handleRead来处理
    m_accpetChannel.setReadCallback(std::bind(&acceptor::handleRead,this));
}

//析构函数,负责关闭套接字,取消m_acceptChannel上所有的网络事件,表示退出和不再接受处理任务网络事件.
acceptor::~acceptor()
{
    m_accpetChannel.disableAll();
    m_accpetChannel.remove();
    ::close(m_idleFd);
}

//在套接字m_acceptSocket上监听
void acceptor::listen()
{
    m_loop->assertInLoopThread();   //保证eventloop所在的线程是当前线程
    m_listenning=true;
    m_acceptSocket.listen();            //Socket::listen()
    //channel注册读网络事件,把这个channel更新到poller中的m_pollfds上面
    m_accpetChannel.enableReading();
}

//m_accpetChannel处理读网络事件(连接到来),接受一个连接
void acceptor::handleRead()
{
    m_loop->assertInLoopThread();
    inetaddress peeraddr;       //连接进来的客户机的套接字地址
    int connfd=m_acceptSocket.accept(&peeraddr);    //客户机套接字:connfd,客户机套接字地址:peeraddr
    if(connfd>=0)               //accept成功
    {
        //回调函数被定义就调用回调函数
        if(m_newConnectionCallback)
            m_newConnectionCallback(connfd,peeraddr);
        else
            sockets::close(connfd);     //关闭套接字
    }else                       //accpet失败
    {
        LOG_SYSERR << "in Acceptor::handleRead";
        // Read the section named "The special problem of
        // accept()ing when you can't" in libev's doc.
        // By Marc Lehmann, author of libev.
        if (errno == EMFILE)
        {
            ::close(m_idleFd);
            m_idleFd = ::accept(m_acceptSocket.fd(), NULL, NULL);
            ::close(m_idleFd);
            m_idleFd = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
        }
    }
}


}//namespace net

}//namespace mymuduo

注意:

acceptor内部是有一个专门的acceptsocket和accpetchannel的,因此就是利用这个acceptorchannel关联到这个acceptsocket上面去,再给acceptorchannel注册读网络事件,这个时候,acceptorsocket就会被更新到poller中的m_pollfds中,只要m_acceptsocket上发生了可读网络事件(accept),eventloop就能根据当前activeChannels找到这个acceptorchannel进行channel::handleEvent(),最终执行到acceptorchannel绑定的那个回调函数 setReadCallback(std::bind(&acceptor::handleRead,this));

不难看出来,只要一有连接进来了,channel就回去回调acceptor::handleRead(),然后接受一个连接。

测试:

#include "net/eventloopthreadpool.h"
#include "net/eventloop.h"
#include"net/acceptor.h"
#include"net/inetaddress.h"
#include"net/socketsops.h"
#include"base/logging.h"

#include <stdio.h>
#include <unistd.h>

using namespace mymuduo;
using namespace mymuduo::net;

eventloop* g_loop;

void newConnection(int sockfd, const inetaddress& peeraddr)
{
    printf("newConnection() accepted a new connection from%s\n",
           peeraddr.toIpPort().data());
    ::write(sockfd,"nihao",5);
    sockets::close(sockfd);

    g_loop->quit();

}

int main()
{
    //logger::setLogLevel(logger::TRACE);

    printf("main() : pid = %d\n",getpid());
    inetaddress listenAddr("192.168.1.103",12306);
    eventloop loop;
    g_loop=&loop;
    //构造函数中创建套接字,绑定,设置m_acceptChannel可读事件回调acceptor::handleRead()
    acceptor acceptor1(&loop,listenAddr);
    acceptor1.setNewConnectionCallback(newConnection);
    acceptor1.listen();//监听
    loop.loop();

}

打印结果:

直接运行肯定是没反应的,因为还需要一个client程序来配套使用。

这里直接利用 linux终端命令 telnet 192.168.1.103 12306 来模仿client去连接这个server,然后打印如下:

main() : pid = 16704
newConnection() accepted a new connection from192.168.1.103:37980

 

posted @ 2020-09-02 12:38  WoodInEast  阅读(204)  评论(0编辑  收藏  举报