前面说了链接前的准备工作,下面说一下链接的过程。
当一个链接发起的时候,EventLoop首先将产生的就绪事件的文件描述符取出,并且取出对应的该文件描述符对应的Channel,看一个Channel结构中记录了什么。
Channel 基本的成员变量
//三个静态变量
static const int kNoneEvent; //没有事件
static const int kReadEvent; //读事件
static const int kWriteEvent; //写事件
//事件循环loop
EventLoop* loop_;
//文件描述符
const int fd_;
//监听的事件
int events_;
//就绪的事件
int revents_; // it's the received event types of epoll or poll
//当前文件描述符在 文件描述符数组中的位置
int index_; // used by Poller.
bool logHup_;
//注册的回调函数
//读事件的回调函数
ReadEventCallback readCallback_;
//写事件的回调函数
EventCallback writeCallback_;
//关闭事件的回调函数
EventCallback closeCallback_;
//错误事件的回调删数
EventCallback errorCallback_;
Channel中有该文件描述符,监听的文件描述符的事件,该文件描述符上现在就绪的事件,还有各个事件所对应的回调函数,Channel回调函数中包含了对文件描述符处理的的一切。看一下Channel 的事件分发函数。
handleEvent事件分发
//该函数最终调用handleEventWithGuard 函数
//receiveTime 事件就绪的事件,当有事件发生的时候,需要更新定时器
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard;
if (tied_)
{
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
//具体处理到达的事件,通过回调函数进行事件的分发
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
//记录日志
LOG_TRACE << reventsToString();
//下面根据事件的不同,调用相应的回调函数,
//这里我们看一下读事件的回调函数
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
//当有读事件到达的时候,调用注册的readCallBack函数。
//在Acceptor 中我们知道,注册回调函数为Acceptor::handleRead
//下面直接看一下Acceptor::handleRead 函数
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
Acceptor::handleRead 处理读事件
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//FIXME loop until no more
//有新连接到达时调用accept接收一个新的链接
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
//如果注册了回调函数,则调用回调函数,将客户端的地址和新产生的文件描述符传入
//在前面TcpServer构造函数中,我们看到这里注册了回调函数 TcpServer::newConnection
if (newConnectionCallback_)
{
//回调tcpserver 的newConnection
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);
}
}
}
TcpServer::newConnection 处理新的连接
//该函数的功能
//对新产生的文件描述符加入的事件循环中,并产生一个新的Channel 对象管理这个新产生
//的文件描述符,设置一系列的对应的回调函数,写入日志
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
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
//在TcpConnection 有一个Channel对象,用于对新产生的文件描述符产生的事件进行分发
//一个文件描述符对应一个Channel 对象
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
//将对象放到map 表中,这里用来管理所有的连接对象
connections_[connName] = conn;
//设置回调函数
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
//这里将新产生的文件描述符添加到事件循环中
//监听当前的conn对象
ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}
到此为止,已经可以与一个客户端建立连接了,并且可以监听连接后的新的文件描述符,与客户端进行通信。