1. Channel
LT 在需要关注writable 事件才关注以防造成busy loop;
+ bool isWriting() const { return _events & kWriteEvent; }
2. TcpConnection
+ void sendInLoop(const std::string&);
+ void shutdownInLoop();
+ Buffer _outBuffer;
目前TcpConnection 有四个状态,状态间的转移条件如上;
shutdown 关闭写通道,底层的系统调用如下:
::shutdown(sockfd, SHUT_WR);
TcpConnection 监听的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();
}
}
因为新增了TcpConnection 状态,handleClose 和 connectDestroyed assert 也要改变条件;
send() 在IO 线程中可以直接调用,在非IO 线程中要queueInLoop 从而产生复制开销;
void TcpConnection::send(const std::string &message) {
if (_state == kConnected) {
if (_loop->isInLoopThread()) {
sendInLoop(message);
} else {
_loop->runInLoop(
std::bind(&TcpConnection::sendInLoop, this, message));
}
}
}
void TcpConnection::sendInLoop(const std::string &message) {
_loop->assertInLoopThread();
ssize_t nwrote = 0;
if (!_channel->isWriting() && _outputBuffer.readableBytes() == 0) {
nwrote = ::write(_channel->fd(), message.data(), message.size());
if (nwrote >= 0) {
if (implicit_cast<size_t>(nwrote) < message.size()) {
LOG_TRACE << "I am going to write more data";
}
} else {
nwrote = 0;
if (errno != EWOULDBLOCK) {
LOG_SYSERR << "TcpConnection::sendInLoop";
}
}
}
assert(nwrote >= 0);
if (implicit_cast<size_t>(nwrote) < message.size()) {
_outputBuffer.append(message.data() + nwrote, message.size() - nwrote)
if (!_channel->isWriting()) {
_channel->enableWriting();
}
}
}
注意到sendInLoop 只调用一次write(未开始写并且没有残余数据方可进行),不然会造成乱序;
没有发送完的或者前面有数据残留,保存到Buffer 中开启_channel 写通道,使poller 轮询到writeCallback 将Buffer 中的数据写完;(在TcpConnection 构造也要设置)
_channel->setWriteCallback(std::bind(&TcpConnection::handleWrite, this));
void TcpConnection::handleWrite() {
_loop->assertInLoopThread();
if (_channel->isWriting()) {
ssize_t n = ::write(_channel->fd(), _outputBuffer.peek(),
_outputBuffer.readableBytes());
if (n > 0) {
_outputBuffer.retrieve(n);
if (_outputBuffer.readableBytes() == 0) {
_channel->disableWriting();
if (_state == kDisconnecting) {
shutdownInLoop();
}
} else {
LOG_TRACE << "I am going to write more data";
}
} else {
LOG_SYSERR << "TcpConnection::handleWrite";
}
} else {
LOG_TRACE << "Connection is down, no more writing";
}
}
handleWrite 只要不被关闭就可以持续发完Buffer 中的数据,shutdown 可以关闭写通道但要保证Buffer 中的数据全部发送完;
当Buffer 数据读完即使通道禁止发送,若此时连接正在关闭则调用shutdown 改变socket 属性;(禁止发送后不影响socket 通讯,当又有send 调用时即可恢复)