上一节我们剖析到了服务器的启动[muduo网络库]——从EchoServer开始启动服务器(剖析muduo网络库核心部分、设计思想),启动服务器以后,所有线程都处于阻塞在poller_->poll
的epoll_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