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