OK了兄弟们。
今天咱们分析一手,muduo 网络库的——TcpConnection 类
非常关键的一个类。它里面绑定了关于对应的 tcp 连接需要进行的操作,包括读写(并设有两个对应的缓冲区),连接,断开等等。它是 server 和对应的 client 的媒介,起到一个承上启下,尊老爱幼的效果。
头文件如下:
class TcpConnection : noncopyable,
public std::enable_shared_from_this<TcpConnection>
{
public:
TcpConnection(EventLoop* loop,
const string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);
~TcpConnection();
EventLoop* getLoop() const { return loop_; }//值得注意的是,这里的loop是subloop
const string& name() const { return name_; }
const InetAddress& localAddress() const { return localAddr_; }
const InetAddress& peerAddress() const { return peerAddr_; }
bool connected() const { return state_ == kConnected; }
bool disconnected() const { return state_ == kDisconnected; }
// return true if success.
bool getTcpInfo(struct tcp_info*) const;
string getTcpInfoString() const;
void send(const void* message, int len);
void send(const StringPiece& message);
void send(Buffer* message); // this one will swap data
void shutdown(); // NOT thread safe, no simultaneous calling
// void shutdownAndForceCloseAfter(double seconds); // NOT thread safe, no simultaneous calling
void forceClose();
void forceCloseWithDelay(double seconds);
void setTcpNoDelay(bool on);
// reading or not
void startRead();
void stopRead();
bool isReading() const { return reading_; }; // NOT thread safe, may race with start/stopReadInLoop
void setContext(const boost::any& context)
{ context_ = context; }
const boost::any& getContext() const
{ return context_; }
boost::any* getMutableContext()
{ return &context_; }
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
void setWriteCompleteCallback(const WriteCompleteCallback& cb)
{ writeCompleteCallback_ = cb; }
void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
{ highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }
/// Advanced interface
Buffer* inputBuffer()
{ return &inputBuffer_; }
Buffer* outputBuffer()
{ return &outputBuffer_; }
/// Internal use only.
void setCloseCallback(const CloseCallback& cb)
{ closeCallback_ = cb; }
// called when TcpServer accepts a new connection
void connectEstablished(); // should be called only once
// called when TcpServer has removed me from its map
void connectDestroyed(); // should be called only once
private:
enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
void handleRead(Timestamp receiveTime);
void handleWrite();
void handleClose();
void handleError();
// void sendInLoop(string&& message);
void sendInLoop(const StringPiece& message);
void sendInLoop(const void* message, size_t len);
void shutdownInLoop();
// void shutdownAndForceCloseInLoop(double seconds);
void forceCloseInLoop();
void setState(StateE s) { state_ = s; }
const char* stateToString() const;
void startReadInLoop();
void stopReadInLoop();
EventLoop* loop_;
const string name_;
StateE state_; // FIXME: use atomic variable
bool reading_;
// we don't expose those classes to client.
std::unique_ptr<Socket> socket_;
std::unique_ptr<Channel> channel_;
const InetAddress localAddr_;
const InetAddress peerAddr_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
HighWaterMarkCallback highWaterMarkCallback_;
CloseCallback closeCallback_;
size_t highWaterMark_;
Buffer inputBuffer_;
Buffer outputBuffer_; // FIXME: use list<Buffer> as output buffer.
boost::any context_;
// FIXME: creationTime_, lastReceiveTime_
// bytesReceived_, bytesSent_
};
太多代码了,没事,凑字数用的。
我们细嚼慢咽。
我们先看构造函数:
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
reading_(true),
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
localAddr_(localAddr),
peerAddr_(peerAddr),
highWaterMark_(64*1024*1024)
{
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true);
}
主要是对一些变量做了初始化,然后给对应的 channel 设置上对应的回调函数。
Send 函数:
void TcpConnection::send(const StringPiece& message)
{
if (state_ == kConnected)
{
if (loop_->isInLoopThread())//如果在线程中就直接运行
{
sendInLoop(message);
}
else //不在目标线程中就丢进目标线程的回调处理队列中
{
void (TcpConnection::*fp)(const StringPiece& message) = &TcpConnection::sendInLoop;
loop_->runInLoop(
std::bind(fp,
this, // FIXME
message.as_string()));
//std::forward<string>(message)));
}
}
}
抛砖引玉啊,内容不多,核心部分在 sendInLoop 里面。
sendInLoop 函数:
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
ssize_t nwrote = 0;
size_t remaining = len;//还剩多少没写完
bool faultError = false;
if (state_ == kDisconnected)
{
LOG_WARN << "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);
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)
{
LOG_SYSERR << "TcpConnection::sendInLoop";
if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
{
faultError = true;
}
}
}
}
assert(remaining <= len);
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();//设为可写,在文件描述符可写之后会通知我们来把输出缓冲区里没写完的继续往里面写
}
}
}
高水位回调内容:通知应用停止发送数据,避免缓冲区溢出。(大家不要在写了,咕噜咕噜咕噜,发送不完了,咕噜咕噜。)
下面我们看一下设置给 channel 的回调函数都做了哪些事情。
handleRead 函数:
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);//直接读到读缓冲区里
if (n > 0)
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//普通信息
}
else if (n == 0)
{
handleClose();//断开连接的信息
}
else // n < 0,出错了
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
直接就是往缓冲区里读。( 好奇缓冲区功能的读者大大可以去别的文档看看昂,我感觉我不一定会出 [老实] )
handleWrite 函数:
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting())
{
ssize_t n = sockets::write(channel_->fd(),
outputBuffer_.peek(), //倾情解说:返回缓冲区中可读的起始位置
outputBuffer_.readableBytes()); //倾情解说×2:返回缓冲区中可读内容的长度
if (n > 0)
{
outputBuffer_.retrieve(n); //倾情解说×3:其实就是更新可读指示标的位置,更新上文中的可读的起始位置
if (outputBuffer_.readableBytes() == 0)//读光了
{
channel_->disableWriting();//很重要的事情,写完了就关闭对可写事件的关心
if (writeCompleteCallback_)
{
loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
}
if (state_ == kDisconnecting)//断开了
{
shutdownInLoop();//这个函数有说法,后面会讲到
}
}
}
else//后面是一些错误处理,不用看了兄弟
{
LOG_SYSERR << "TcpConnection::handleWrite";
// if (state_ == kDisconnecting)
// {
// shutdownInLoop();
// }
}
}
else
{
LOG_TRACE << "Connection fd = " << channel_->fd()
<< " is down, no more writing";
}
}
发现了吗,这个函数会和前面的 Send 函数以及 sendInLoop 函数打出一套热血沸腾的组合技,最好的三人组总是以这样的形式出现。
下面,我们整理一下三兄弟的工作流程:
(大哥,刘玄德)Send 函数:准备开始搞事,判断是在哪个线程找并到正确的线程做事(你想与我一起匡扶汉室吗)。
(二哥,关云长)sendInLoop 函数:可以直接写一部分数据,如果写不完了就存缓冲区里面,等 handleWrite 函数来写。缓冲区写的太多了了,还可以发起高水位回调(史称水淹七军)。
(三哥,张翼德)handleWrite 函数:只要可写事件发生,说明系统缓冲区又能写了,我就接着写。(我为什么是三哥?)
“关二爷在上,我兄弟三人不求同年同日生,但求同年同月同日死!”
handleClose 函数和 handleError 函数:
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << 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);
}
void TcpConnection::handleError() //
{
int err = sockets::getSocketError(channel_->fd());
LOG_ERROR << "TcpConnection::handleError [" << name_
<< "] - SO_ERROR = " << err << " " << strerror_tl(err);
}
以上是我们设置给 channel 的四个回调函数,他们会在对应的套接字触发对应的情况时被调用。
下面说一下 shutdown 这个函数:
void TcpConnection::shutdown()
{
if (state_ == kConnected)
{
setState(kDisconnecting);
loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));
}
}
void TcpConnection::shutdownInLoop()
{
loop_->assertInLoopThread();
if (!channel_->isWriting())//要写的已经写完了
{
socket_->shutdownWrite();//只关闭了对该套接字的写端
}
}
这里能看出,muduo网络库服务端比较喜欢被动,也就是只能被动断开连接。只有在读回调里面,读到 0 才会触发handleClose。handleClose,也只会通过shutdown来单方面的关闭写端,未关闭读读端,避免了客户端此时仍有数据在来的路上导致我们读不到的情况。
那么客户端是怎么 close 的呢?
TcpClient 函数在析构的时候,会调用它的对应的 TcpConnection::forceClose 函数。
如下:
void TcpConnection::forceClose()
{
// FIXME: use compare and swap
if (state_ == kConnected || state_ == kDisconnecting)
{
setState(kDisconnecting);
loop_->queueInLoop(std::bind(&TcpConnection::forceCloseInLoop, shared_from_this()));
}
}
void TcpConnection::forceCloseWithDelay(double seconds)
{
if (state_ == kConnected || state_ == kDisconnecting)
{
setState(kDisconnecting);
loop_->runAfter(
seconds,
makeWeakCallback(shared_from_this(),
&TcpConnection::forceClose)); // not forceCloseInLoop to avoid race condition
}
}
void TcpConnection::forceCloseInLoop()
{
loop_->assertInLoopThread();
if (state_ == kConnected || state_ == kDisconnecting)
{
// as if we received 0 byte in handleRead();
handleClose();
}
}
OK了兄弟们。
咱们今天的内容就分析到这了。还有一些零零碎碎的内容,相信大家一看就懂了。