从TRAS Connection::send分析EPOLLOUT触发时机
首先,Tars默认采用ET
//构造函数
TC_Epoller(bool bEt = true);
//封装epoll_ctrl
void TC_Epoller::ctrl(int fd, long long data, __uint32_t events, int op)
{
struct epoll_event ev;
ev.data.u64 = data;
if(_et)
{
ev.events = events | EPOLLET;
}
else
{
ev.events = events;
}
epoll_ctl(_iEpollfd, op, fd, &ev);
}
下面开始分析发包函数:
int TC_EpollServer::NetThread::Connection::send(const string& buffer, const string &ip, uint16_t port, bool byEpollOut)
{
const bool isUdp = (_lfd == -1);
if(isUdp)
{
int iRet = _sock.sendto((const void*) buffer.c_str(), buffer.length(), ip, port, 0);
if(iRet < 0)
{
_pBindAdapter->getEpollServer()->error("[TC_EpollServer::Connection] send [" + _ip + ":" + TC_Common::tostr(_port) + "] error");
return -1;
}
return 0;
}
if (byEpollOut)
{// 3.等待 缓冲区重新可写时,自动触发EPOLLOUT,epoll_wait执行NetThread::processNet,发剩余的包数据
int bytes = this->send(_sendbuffer);
if (bytes == -1)
{
_pBindAdapter->getEpollServer()->debug("send [" + _ip + ":" + TC_Common::tostr(_port) + "] close connection by peer.");
return -1;
}
this->adjustSlices(_sendbuffer, bytes);
_pBindAdapter->getEpollServer()->info("byEpollOut [" + _ip + ":" + TC_Common::tostr(_port) + "] send bytes " + TC_Common::tostr(bytes));
}
else
{// 4. 如果连接下一个发送包进来时,还未发送完毕,则把当前需要发送的包“粘贴”到未发送buffer中
const size_t kChunkSize = 8 * 1024 * 1024;
if (!_sendbuffer.empty())
{
TC_BufferPool* pool = _pBindAdapter->getEpollServer()->getNetThreadOfFd(_sock.getfd())->_bufferPool;
// avoid too big chunk
for (size_t chunk = 0; chunk * kChunkSize < buffer.size(); chunk ++)
{
size_t needs = std::min<size_t>(kChunkSize, buffer.size() - chunk * kChunkSize);
TC_Slice slice = pool->Allocate(needs);
::memcpy(slice.data, buffer.data() + chunk * kChunkSize, needs);
slice.dataLen = needs;
_sendbuffer.push_back(slice);
}
}
else //1.NetThread::send强制触发EPOLLOUT, epoll_wait执行NetThread::processPipe,第一次发包
{
int bytes = this->tcpSend(buffer.data(), buffer.size());
if (bytes == -1)
{
_pBindAdapter->getEpollServer()->debug("send [" + _ip + ":" + TC_Common::tostr(_port) + "] close connection by peer.");
return -1;
}
else if (bytes < static_cast<int>(buffer.size()))
{ //2.发不完的包数据,写入_sendbuffer;
const char* remainData = &buffer[bytes];
const size_t remainLen = buffer.size() - static_cast<size_t>(bytes);
TC_BufferPool* pool = _pBindAdapter->getEpollServer()->getNetThreadOfFd(_sock.getfd())->_bufferPool;
// avoid too big chunk
for (size_t chunk = 0; chunk * kChunkSize < remainLen; chunk ++)
{
size_t needs = std::min<size_t>(kChunkSize, remainLen - chunk * kChunkSize);
TC_Slice slice = pool->Allocate(needs);
::memcpy(slice.data, remainData + chunk * kChunkSize, needs);
slice.dataLen = needs;
_sendbuffer.push_back(slice);
}
// end
_pBindAdapter->getEpollServer()->info("EAGAIN[" + _ip + ":" + TC_Common::tostr(_port) +
", to sent bytes " + TC_Common::tostr(remainLen) +
", total sent " + TC_Common::tostr(buffer.size()));
}
}
}
size_t toSendBytes = 0;
for (const auto& slice : _sendbuffer)
{
toSendBytes += slice.dataLen;
}
if (toSendBytes >= 8 * 1024)
{
_pBindAdapter->getEpollServer()->info("big _sendbuffer > 8K");
size_t iBackPacketBuffLimit = _pBindAdapter->getBackPacketBuffLimit();
if(iBackPacketBuffLimit != 0 && toSendBytes >= iBackPacketBuffLimit)
{
_pBindAdapter->getEpollServer()->error("send [" + _ip + ":" + TC_Common::tostr(_port) + "] buffer too long close.");
clearSlices(_sendbuffer);
return -2;
}
}
//需要关闭链接
if(_bClose && _sendbuffer.empty())
{
_pBindAdapter->getEpollServer()->debug("send [" + _ip + ":" + TC_Common::tostr(_port) + "] close connection by user.");
return -2;
}
return 0;
}
1.NetThread::send强制触发EPOLLOUT, epoll_wait执行NetThread::processPipe,第一次发包
2.发不完的包数据,分片成TC_Slice, 写入_sendbuffer
3.上一次缓冲区写满,缓冲区不可写;当对端读取了数据,缓冲区重新可写时,自动触发EPOLLOUT,epoll_wait执行NetThread::processNet,使用writev发剩余的包数据
4.如果连接下一个发送包进来时,上一个包片还未发送完毕,则把当前需要发送的包切片,“粘贴”到未发送_sendbuffer中
总结:ET模式下,EPOLLOUT有以下两种触发时机:
1.epoll_ctrl设置event为EPOLLOUT强制触发
2.上一次发送缓冲区写满时,等待发送缓冲区重新可写时,EPOLLOUT自动触发
PS: LT模式下,EPOLLOUT相关问题
一道腾讯后台开发的面试题(refer: http://kimi.it/515.html) 使用Linuxepoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?
第一种最普遍的方式: 需要向socket写数据的时候才把socket加入epoll,等待可写事件。 接受到可写事件后,调用write或者send发送数据。 当所有数据都写完后,把socket移出epoll。
这种方式的缺点是,即使发送很少的数据,也要把socket加入epoll,写完后在移出epoll,有一定操作代价。
一种改进的方式: 开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。
这种方式的优点是:数据不多的时候可以避免epoll的事件处理,提高效率。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 聊聊claudb的Database
- 聊聊claudb的SlaveReplication
- flutter doctor 卡死
- Angular如何自定义attribute指令
- 聊聊claudb的MasterReplication
- k8s 之yaml文件基本格式
- 你可能不知道的pandas的5个基本技巧
- Node 脚本遭遇异常时如何安全退出
- flutter Running Gradle task 'assembleDebug'
- 如何使用 docker 高效部署 Node 应用
- fish-redux框架路由配置报错问题
- Flutter fish-redux 简单使用
- Flutter 项目.gitignore配置
- js和object的常见操作,持续更新中...
- 常见编程模式之快慢指针