muduo中的Acceptor类的主要功能是socket、bind、listen,Acceptor用于accept接受TCP连接
一般来说,在上层应用程序中,我们不直接使用Acceptor,而是把它作为TcpServer的成员
TcpServer还包含了一个TcpConnection列表这是一个已连接列表。TcpConnection与Acceptor类似,有两个重要的数据成员,Socket与Channel。
所以说,Acceptor用于accept接受TCP连接。Acceptor的数据成员包括Socket、Channel,Acceptor的socket是listening socket(即server socket)。Channel用于观察此socket的readable事件,并回调Acceptor::handleRead(),后者调用acceptor来接受新连接,并回调用户callback。
下面分析一个程序的执行步骤:
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
printf("onConnection(): new connection [%s] from %s\n",
conn->name().c_str(),
conn->peerAddress().toIpPort().c_str());
}
else
{
printf("onConnection(): connection [%s] is down\n",
conn->name().c_str());
}
}
void onMessage(const TcpConnectionPtr& conn,
const char* data,
ssize_t len)
{
printf("onMessage(): received %zd bytes from connection [%s]\n",
len, conn->name().c_str());
}
int main()
{
printf("main(): pid = %d\n", getpid());
//构造一个地址对象
InetAddress listenAddr(8888);
EventLoop loop;
TcpServer server(&loop, listenAddr, "TestServer");
//连接到来的回调函数
server.setConnectionCallback(onConnection);
//消息到来的回调函数
server.setMessageCallback(onMessage);
//启动
server.start();
loop.loop();
}
分析:
程序后面的acceptor_->setNewConnectionCallback(boost::bind(&TcpServer::newConnection, this, _1, _2));这个回调函数的注册对应的是acceptor_,也就是说Acceptor的handleRead()执行后会调用上层的(也就是TcpServer)newConnection函数。
TcpServer server(&loop, listenAddr, "TestServer");
TcpServer::TcpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& nameArg,
Option option)
: loop_(CHECK_NOTNULL(loop)),
ipPort_(listenAddr.toIpPort()),
name_(nameArg),
acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
threadPool_(new EventLoopThreadPool(loop, name_)),
connectionCallback_(defaultConnectionCallback),
messageCallback_(defaultMessageCallback),
nextConnId_(1)
{
//对accptor_设置一个回调函数
//Acceptor::handleRead函数中会回调用TcpServer::newConnection
//_1对应的是socket文件描述符,_2对应的是对等方的地址
acceptor_->setNewConnectionCallback(
boost::bind(&TcpServer::newConnection, this, _1, _2));
}
启动,该函数可以跨线程调用,主要就是要执行Acceptor中的listen,也就是监听
server.start();
//该函数可以跨线程调用
void TcpServer::start()
{
if (started_.getAndSet(1) == 0)
{
threadPool_->start(threadInitCallback_);
//判断是否处于监听状态
assert(!acceptor_->listenning());
loop_->runInLoop(
//现在服务器处于监听的状态了
boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
}
}
关注监听套接字对应通道的可读事件
void Acceptor::listen()
{
loop_->assertInLoopThread();
listenning_ = true;
//监听
acceptSocket_.listen();
//关注可读事件,TcpConnection所对应的通道加入到Poller关注
acceptChannel_.enableReading();
}
事件循环
loop.loop();
void EventLoop::loop()
{
.....
//遍历活动通道进行处理
for (ChannelList::iterator it = activeChannels_.begin();
it != activeChannels_.end(); ++it)
{
//当前的处理通道
currentActiveChannel_ = *it;
//调用handleEvent处理通道
currentActiveChannel_->handleEvent(pollReturnTime_);
}
.....
}
当监听到一个连接到来的时候,在TcpServer.cc的构造函数中的setNewConnctionCallback,对Acceptor的acceptor_设置一个回调函数,当一个连接事件到来的时候,Poller的poll函数返回通道,调用了通道的handleEvent(),又调用了Acceptor的handleRead(),在TcpServer中注册的回调函数是TcpServer::newConnection
所以先执行的Acceptor中的handleRead(),然后再执行TcpServer中的newConnection()
在handleRead()中得到已连接套接字,并传递给TcpServer的函数newConnection()
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
//准备一个地址,接受对等方地址
InetAddress peerAddr;
//FIXME loop until no more
//得到了一个连接
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
//如果上层定义了回调函数,则执行
if (newConnectionCallback_)
{
//传递套接字以及对等方的地址
newConnectionCallback_(connfd, peerAddr);
}
else
{
sockets::close(connfd);
}
}
//失败
else
{
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(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
//接受完以后关闭掉
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
在函数newConnection()中创建连接对象并放入map容器进行管理,然后调用TcpConnection中的connectEstablished,进行连接的管理
//一个新的连接之后
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
//按照轮叫的方式选择一个EventLoop
EventLoop* ioLoop = threadPool_->getNextLoop();
char buf[64];
snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
++nextConnId_;
//连接名称
string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
//构造本地地址
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
//创建一个连接对象
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
//将连接对象放到一个map容器中
connections_[connName] = conn;
对这个连接对象设置回调函数
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
//不能够在当前线程中调用,应该让ioLoop所属的IO线程调用这个连接
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}
在这个函数中关注已连接通道的可读事件,此时是已连接状态,回调用户注册的连接建立函数。这个是在TcpServer的newConnection函数中注册的。
//当连接到来的时候
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
//tie的参数是shared_ptr,shared_from_this()获得一个自身的share_ptr对象
channel_->tie(shared_from_this());
//TcpConnection所对应的通道加入到Poller关注
channel_->enableReading();
//这是用户的回调函数
connectionCallback_(shared_from_this());
}
上面这个程序在连接到来的时候,将TcpConnection所对应的通道加入到Poller关注,此时,如果有可读事件发生,就会由handleEvent()->handleRead(),就可以处理连接通道中的可读事件了
读走数据并且执行回调函数messageCallback_(),这个函数是由用户注册到TcpServer,然后由TcpServer.cc中的newConnection()函数又注册到TcpConnection中。
//当一个消息到来的时候,调用handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
//shared_from_this()的作用是转为shared_ptr
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
//处理连接断开
else if (n == 0)
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
一个服务器维护一个已连接列表,当一个连接断开的时候,TcpConnection中的通道处于活跃状态,EventLopp的事件循环返回这个活跃的通道,并且调用handleEvent函数来处理,回调TcpConnection的handleRead函数,在这个函数中,又调用了read,这个时候read返回为0,又调用了handleClose函数,这个函数中又回调了TcpConnection的removeConnection,erase将这个连接对象从列表中移除。按照正常的思路,我们还应该将这个对象销毁掉,但是在这里我们不能立即销毁这个连接对象,如果销毁了这个对象,TcpConnection所包含的Channel对象也就跟着销毁了。而当前正在调用这个Channel对象的handleEvent函数,而这个Channel对象又销毁了,就会出现coredump。因而这个不能销毁TcpConnection对象,也就是说TcpConnection对象的生存期应该长于HandleEvent函数,如果做到这一点,可以利用shared_ptr来管理TcpConnection对象。
当连接到来,创建一个TcpConnection对象,立刻用shared_ptr来管理,这时候引用计数为1。
在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给tie_,因为是弱引用,所以引用计数不会加1。
当连接关闭,调用了Channel的handleEvent函数,在这个函数中,将tie_提升,得到一个shared_ptr对象,此时引用计数为2。
erase从列表中移除,引用计数减1,还剩1,因而TcpConnection对象不会销毁。
此时,会调用queueInLoop将connectDestroyed放到EventLoop的functors中,这个时候引用计数加1,变为2。handleEvent函数返回之后,它所提升的那个shared_ptr对象销毁了,引用计数减1,变为1。boost::function调用用户的函数connCb,引用计数减1变为0。TcpConnection对象就销毁了。