上一节我们剖析到了连接是如何建立[muduo网络库]——EchoServer之连接建立(剖析muduo网络库核心部分、设计思想)的,建立连接以后,线程都处于阻塞在poller_->poll
的epoll_wait
上,主线程等待新连接的到来,子线程此时也在监听以连接用户的状态,也就是消息的到来。
消息到来以后,线程是如何处理的呢?那么这一节我们来一步一步的剖析一下这个过程。
消息读取
- 在上一节我们提到,当mainLoop接受新连接请求之后,实例化了一个Acceptor对象,并将这个Tcp连接封装成TcpConnection对象,并使用
TcpConnectionPtr
智能指针来管理它。TcpConnection对象在构造函数中,主要就是封装了连接套接字的fd、连接套接字的channel_,以及注册了相关的回调方法在对应的channel_内,其中尤其要关注:如何关闭连接的回调conn->shutdown
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection,this,std::placeholders::_1)
);
- 当TcpConnection对象建立完毕之后,mainLoop的Acceptor会将这个
TcpConnection
对象中的channel_
通过轮询算法注册到某一个SubLoop中,每一个Channel封装着一个fd及fd感兴趣的事件和事件监听器监听到该fd实际发生的事件,开始监听Channel_
上的可读事件。 - 当新接收的连接有消息到来时,对应的子线程Poller就会检测到该连接对应的
Channel_
可读,于是就会解除阻塞,去执行该channel上的可读事件,即TcpConnection::handleRead()
void TcpConnection::handleRead(TimeStamp receiveTime)
{
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(),&savedErrno);
if(n > 0)
{
//已建立连接的用户,有可读事件发生了,调用用户传入的回调操作onMessage
//shared_from_this()获取了当前TcpConnection对象的智能指针
messageCallback_(shared_from_this(),&inputBuffer_,receiveTime);
}
else if(n==0) //客户端断开
{
handleClose();
}
else
{
errno = savedErrno;
LOG_ERROR("TcpConnection::hanleRead");
handleError();
}
}
- 先定义了一个
savedErrno
,并且如果在后续数据的读取拷贝过程中发生错误,将错误信息保存在savedErrno
中,并调用TcpConnection::handleError( )
来处理。 TcpConnection::handleRead( )
函数首先调用inputBuffer_.readFd(channel_->fd(),&savedErrno)
,该函数底层调用系统的readv( )
,将Tcp接收缓冲区数据拷贝到用户定义的缓冲区inputBuffer_
。如果在读取拷贝的过程中发生了什么错误,这个错误信息就会保存在savedErrno中。- 当读取的数据大于0时,那么会接着调用
messageCallback_
处理函数。对于messageCallback_
,是调用用户传入的回调操作onMessage
server_.setMessageCallback(
std::bind(&EchoServer::onMessage,this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
);
readFd( )
返回值等于0,说明客户端连接关闭,这时候应该调用TcpConnection::handleClose( )
来处理连接关闭事件
发送消息
- 在用户定义的
onMessage
中
void onMessage(const TcpConnectionPtr &conn,
Buffer *buf,
TimeStamp time)
{
//用户定义的可读事件处理函数:当一个Tcp连接发生了可读事件就把它这个接收到的消息原封不动的还回去
std::string msg = buf->retrieveAllAsString();
conn->send(msg);
conn->shutdown(); //关闭写端EPOLLHUP=> closeCallback
}
- 我们可以看出,在获得对方接收的消息后,调用
conn->send(msg)
,实际上是调用TcpConnection::send()
;
void TcpConnection::send(const std::string &buf) //直接引用buffer
{
if(state_ == kConnected)
{
if(loop_->isInLoopThread())
{
sendInLoop(buf.c_str(),buf.size());
}
else
{
loop_->runInLoop(std::bind(&TcpConnection::sendInLoop
, this
, buf.c_str()
, buf.size()
));
}
}
}
- 接着内部调用了
TcpConnection::sendInLoop()
void TcpConnection::sendInLoop(const void* data, size_t len)
{
ssize_t nwrote = 0; // 已经发送的数据长度
size_t remaining = len; //未发送的数据
bool faultError = false; //记录是否产生错误
//之前调用过connection的shutdown 不能在发送了
if(state_ == kDisconnected)
{
LOG_ERROR("disconnected,give up writing!");
return ;
}
//channel 第一次开始写数据,且缓冲区没有待发送数据
if(!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
nwrote = ::write(channel_->fd(),data,len);
if(nwrote >= 0)
{
remaining = len - nwrote;
if(remaining == 0 && writeCompleteCallback_)
{
//一次性数据全部发送完成,就不要再给channel设置epollout事件了
loop_->queueInLoop(
std::bind(writeCompleteCallback_,shared_from_this()));
}
}
else
{
nwrote = 0;
if(errno != EWOULDBLOCK) //用于非阻塞模式,不需要重新读或者写
{
LOG_ERROR("TcpConnection::sendInLoop");
if(errno == EPIPE || errno == ECONNRESET) //SIGPIPE RESET
{
faultError = true;
}
}
}
}
if(!faultError && remaining > 0)
{
//目前发送缓冲区剩余的待发送数据的长度
size_t oldlen = outputBuffer_.readableBytes();
if(oldlen + remaining >= highWaterMark_
&& oldlen < highWaterMark_
&& highWaterMark_)
{
loop_->queueInLoop(
std::bind(highWaterMarkCallback_,shared_from_this(),oldlen + remaining)
);
}
outputBuffer_.append((char*)data + nwrote,remaining);
if(!channel_->isWriting())
{
channel_->enableWriting(); //注册channel写事件,否则poller不会向channel通知epollout
}
}
}
- 先判断
outputBuffer_
中是否还有数据需要发送; - 如果没有,则直接把要发送的数据msg调用系统的
write()
发送出去,如果一次性发送完成了,调用writeCompleteCallback_
; - 接着1,如果有,说明当前这一次write ,并没有把数据全发送出去,剩余的数据需要保存到缓冲区
outputBuffer_
当中,给channel注册epollout
事件,poller
发现tcp发送缓冲区有空间,会通知相应的socket-channel,调用相应的writeCallback()
回调方法,也就是调用TcpConnection::handleWrite()
,把发送缓冲区中数据全部发送出去
void TcpConnection::handleWrite()
{
if(channel_->isWriting())
{
int savedErrno = 0;
ssize_t n = outputBuffer_.writeFd(channel_->fd(),&savedErrno);
if(n > 0)
{
outputBuffer_.retrieve(n); //处理了n个
if(outputBuffer_.readableBytes() == 0) //发送完成
{
channel_->disableWriting(); //不可写了
if(writeCompleteCallback_)
{
//唤醒loop对应的thread线程,执行回调
loop_->queueInLoop(
std::bind(writeCompleteCallback_,shared_from_this())
);
}
if(state_ == kDisconnecting)
{
shutdownInLoop();// 在当前loop中删除TcpConnection
}
}
}
else
{
LOG_ERROR("TcpConnection::handleWrite");
}
}
else
{
LOG_ERROR("TcpConnection fd=%d is down, no more writing \n",channel_->fd());
}
}
- 接着调用
Buffer::writeFd
,实际上,内部也是调用了系统的write()
函数,将数据写入到Tcp发送缓冲区,如果Tcp发送缓冲区还没法容纳剩余的未发送数据,就继续保持可写事件的监听。 - 当数据全部拷贝到Tcp发送缓冲区之后,就会调用用户自定义的写完后的事件处理函数,并且移除该TcpConnection在事件监听器上的可写事件。为什么是可写事件在另一篇博客中剖析过了[muduo网络库]——muduo库TcpConnection类,万字总结(剖析muduo网络库核心部分、设计思想)。
代码地址:https://github.com/Cheeron955/mymuduo/tree/master