muduo中的TcpConnection算是整个框架中的大头吧,因为每个客户端连接对应一个Channel和一个TcpConnection,而之前已经说了,Channel只是一个通道,那么对于客户端连接的各种处理,基本都是在TcpConnection上完成的了。
这个类的cpp文件算是muduo里比较长的了,有400+行。
注意我参考的代码时flamingo的代码,是基于muduo,或者会有些出入。
private:
EventLoop* loop_;//这个TcoConnection在哪个EventLoop里
const string name_;
StateE state_; // FIXME: use atomic variable
// we don't expose those classes to client.
std::shared_ptr<Socket> socket_;//这个连接对应的socket
std::shared_ptr<Channel> channel_;//对应的Channel
const InetAddress localAddr_;//这里的地址信息
const InetAddress peerAddr_;//对面的地址信息
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;//在TcoConnection中的handleread函数中被调用。
WriteCompleteCallback writeCompleteCallback_;
HighWaterMarkCallback highWaterMarkCallback_;
CloseCallback closeCallback_;
size_t highWaterMark_;
Buffer inputBuffer_;//输入和输出的缓冲
Buffer outputBuffer_; // FIXME: use list as output buffer.
我给主要的都加上了注释,相信理解起来会比较轻松。接下来我们看看初始化。
TcpConnection::TcpConnection(EventLoop* loop, const string& nameArg, int sockfd, const InetAddress& localAddr, const InetAddress& peerAddr)
: loop_(loop),
name_(nameArg),
state_(kConnecting),
socket_(new Socket(sockfd)),//new了一个新的socket,对应这条连接
channel_(new Channel(loop, sockfd)),//new了一个新的Channel
localAddr_(localAddr),
peerAddr_(peerAddr),
highWaterMark_(64 * 1024 * 1024)//高水位为64M,暂时还没去彻底研究buffer
{ //绑定回调函数,当Channel收到请求,需要调用对应的事件函数时,就会调用下面的函数,也是我们需要特别关心的函数
channel_->setReadCallback(std::bind(&TcpConnection::handleRead, this, std::placeholders::_1));
channel_->setWriteCallback(std::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(std::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(std::bind(&TcpConnection::handleError, this));
LOGD("TcpConnection::ctor[%s] at 0x%x fd=%d", name_.c_str(), this, sockfd);
socket_->setKeepAlive(true);
同样的,我加上了注释。
接下来我们看看上面绑定的回调函数,它们是具体处理消息的函数,算是最重要的函数了。
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
int32_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
//messageCallback_指向CTcpSession::OnRead(const std::shared_ptr& conn, Buffer* pBuffer, Timestamp receiveTime)
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//喊客户代码来拿数据了
}
else if (n == 0)
{
handleClose();//关闭连接
}
else
{
errno = savedErrno;
LOGSYSE("TcpConnection::handleRead");
handleError();
}
}
代码逻辑不复杂,只是int32_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
比较难理解,现在卡在了readv函数上,有待进一步解决。
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting())//暂不明
{
int32_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());//向socket中写入 outputBuffer_.readableBytes个字节的buffer内的内容
if (n > 0)//n是写入的字节数
{
outputBuffer_.retrieve(n);//buffer释放n个字节的空间,也不能说释放,buffer是通过移动指针来操作区间的。
if (outputBuffer_.readableBytes() == 0)//当没有数据了
{
channel_->disableWriting();//那你都没数据了,还怎么写?,所以disable掉
if (writeCompleteCallback_)
{
loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));//回调告诉写完了
}
if (state_ == kDisconnecting)//暂不明
{
shutdownInLoop();//最终调用到socket.shutdown,优雅的关闭连接。需要进一步探讨
}
}
}
else
{
LOGSYSE("TcpConnection::handleWrite");
// if (state_ == kDisconnecting)
// {
// shutdownInLoop();
// }
//added by zhangyl 2019.05.06
handleClose();
}
}
else
{
LOGD("Connection fd = %d is down, no more writing", channel_->fd());
}
}
突然发现把东西写在代码的注释里,比说一大堆话,清晰的多,但还是要总结一下吧。
总的来说就是向socket写入了n个数据,readableBytes返回的是buffer里面可以读到多少个字节,那么对于输出buffer来说,其实就是可以从buffer中取多少个字节,然后往socket里写。
有很多的标记位,比如state_,event_等,暂时还没理清他们的关系。
这个函数是用来关闭TcpConnection的。
void TcpConnection::handleClose()
{
//在Linux上当一个链接出了问题,会同时触发handleError和handleClose
//为了避免重复关闭链接,这里判断下当前状态
//已经关闭了,直接返回
if (state_ == kDisconnected)
return;
loop_->assertInLoopThread();
LOGD("fd = %d state = %s", channel_->fd(), stateToString());
//assert(state_ == kConnected || state_ == kDisconnecting);
// we don't close fd, leave it to dtor, so we can find leaks easily.
setState(kDisconnected);//设置状态为切断的
channel_->disableAll();//
TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);//这里跟建立连接时一样,再次调用了这个回调函数,所以在用户代码中,需要进行分情况,看到底时建立连接还是断开连接
// must be the last line
closeCallback_(guardThis);//调用关闭连接的回调函数,最终调用到TcpConnection::connectDestroyed
}
流程是,输出日志,设置状态,关闭通道,通知客户代码关闭连接,最后调用TcpConnection的销毁连接代码。整体还是不难理解的,就是要注意的一点是,最后销毁TcpConnection的时候先回调到了TcpServer中,然后再通过EventLoop执行了TcpConnection的销毁函数,可以看出TcpServer才是老大哈哈,代码功能,解耦还是做的不错滴。
这个handleClose函数一般是在别的函数里被调用的,比如强制关闭。
send函数比较好理解,就是发送数据用的,这是TcpConnection提供给外部的一个接口。我们会发现有好多send函数和sendInLoop函数。send算是接口,最终都是调用到sendInLoop函数,由sendInLoop函数来执行具体的发送操作。
先来看看sendInLoop函数的代码
void TcpConnection::sendInLoop(const string & message)
{
sendInLoop(message.c_str(), message.size());//将string的传入参数拆分后在放入真正的发送函数中。
}
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
int32_t nwrote = 0;
size_t remaining = len;
bool faultError = false;
if (state_ == kDisconnected)
{
LOGW("disconnected, give up writing");
return;
}
// if no thing in output queue, try writing directly
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
nwrote = sockets::write(channel_->fd(), data, len);
//TODO: 打印threadid用于调试,后面去掉
//std::stringstream ss;
//ss << std::this_thread::get_id();
//LOGI << "send data in threadID = " << ss;
if (nwrote >= 0)//表示,发送成功
{
remaining = len - nwrote;//剩下多少没写的
if (remaining == 0 && writeCompleteCallback_)
{
loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));//回调告诉客户代码:我写完了
}
}
else // nwrote < 0//出问题了
{
nwrote = 0;
if (errno != EWOULDBLOCK)
{
LOGSYSE("TcpConnection::sendInLoop");
if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
{
faultError = true;
}
}
}
}
//assert(remaining <= len);
if (remaining > len)//出现的情况是,nwrote小于0,所以退出了
return;
if (!faultError && remaining > 0)
{
size_t oldLen = outputBuffer_.readableBytes();
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{
loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));//高水位了
}
outputBuffer_.append(static_cast<const char*>(data) + nwrote, remaining);//把没写的添加到输出缓存区
if (!channel_->isWriting())
{
channel_->enableWriting();
}
}
}
我们来看一看其中一个send函数的代码
void TcpConnection::send(const void* data, int len)
{
if (state_ == kConnected)
{
if (loop_->isInLoopThread())//如果是当前这个线程,就直接调用sendInLoop函数去发送数据
{
sendInLoop(data, len);
}
else//如果不是当前这个线程,则扔给 eventLoop去处理
{
string message(static_cast<const char*>(data), len);
loop_->runInLoop(
std::bind(static_cast<void (TcpConnection::*)(const string&)>(&TcpConnection::sendInLoop),
this, // FIXME
message));
}
}
}
逻辑比较明确了,send函数作为接口,接受客户代码的调用,然后调用sendInLoop函数,来执行具体的操作。并没有什么逻辑上的难点,就是要注意判断不同的情况,具体在代码中注释了。
简单分析一个点:当输出缓冲区有数据的时候,会append到输出缓冲区中,靠的是第一,remain=len>0,第二,faultError 为false,因为不会执行第一个if,所以不会有机会被改成true。那么就会执行到后面的append函数。
注意我参考的代码和陈硕muduo的源码有些不同的地方,不过本质还是差不多的。
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
if (state_ != kConnecting)
{
//一定不能走这个分支
return;
}
setState(kConnected);//将state状态设置为已连接
channel_->tie(shared_from_this());//shared_from_this功能为返回一个当前类的std::share_ptr
//假如正在执行这行代码时,对端关闭了连接
if (!channel_->enableReading())
{
LOGE("enableReading failed.");
//setState(kDisconnected);
handleClose();
return;
}
//connectionCallback_指向void XXServer::OnConnection(const std::shared_ptr& conn)
connectionCallback_(shared_from_this());
}
这个函数比较简单,设置状态,然后调用连接建立完成的回调。Channel->tie_是一个std::weak_ptr
,可以认为是和Channel绑在一起的对象,为什么里面是void呢,之前的文章也说过,一个Channel对应一个TcpConnection,或者对应一个Acceptor。所以是void,因为这样可以指向不同的数据类型。