/*
在网络IO密集型的高并发异步非阻塞程序里,网络的IO吞吐量一般远远跟不上CPU的处理能力,
程序在发送大量数据时,会导致TCP的发送缓冲区满,应用层在调用send或write等函数时会返回 EAGAIN(重试)。
由于程序是异步非阻塞的,需要把没有发送完的数据保存起来,等TCP发送缓冲区有空闲空间时重新触把保存的数据写到发送缓冲区里。
这里我们介绍一个把没有发送完的数据保存到队列里,并在发送缓冲区空闲时或定时器定时的触发的相关处理函数
*/
//存储任务消息的队列
std::queue<STTask> task_queue;
//网络套接字
int net_fd = -1;
struct STTask
{
unsigned int snd;
std::string data;
STTask()
{
snd = 0;
data.clear();
}
/*
描述:创建消息的带参构造函数
参数:
msg: 消息指针
msg_len: 消息的长度
snd_len: 已发送消息的长度
*/
STTask(const char* msg, int msg_len, int snd_len):
data(msg,msg_len),
snd(snd_len)
{
}
void Reset()
{
snd = 0;
}
};
/*
描述:创建消息的带参构造函数
参数:
fd: 套接字描述符
buff: 缓冲区指针
len: 消息的长度
*/
int sendToTcp(int fd, char* buff, const int len)
{
int ret;
int _snd=0;
int _len = len;
errno = 0;
while(_len > 0)
{
ret = send(fd, (void*)((char*)buff + _snd), _len, 0);
if ( ret <= 0 )
{
if ( errno == EINTR || errno == EAGAIN )
{
break;
}
else
{
printf("ret:%d, errno:%d, desc:%s\n", ret, errno, strerror(errno));
return -1;
}
}
_len -= ret;
_snd += ret;
}
return _snd;
}
/*
描述:重新激活发送数据(缓冲区有可写事件可调用此函数发送未发送的队列任务消息)
参数:
无
*/
int ReactivateSend()
{
if(task_queue.empty())
{
//没有数据可发送, 删除写事件(也就是只设置可读、关闭、错误等事件)
//注:设置事件
return 0;
}
int count = sendQueueTask();
if(task_queue.empty())
{
//没有数据可发送, 删除写事件(也就是只设置可读、关闭、错误等事件)
//注:设置事件
return 0;
}
return 0;
}
描述:循环发送队列里的任务数据
参数:
无
*/
int sendQueueTask()
{
int task_len = 0;
int task_num = 0;
while(!task_queue.empty())
{
//取出队列头的消息,这里要保证顺序
STTask & task = task_queue.front();
if(task.snd > 0)
{
task_len = task.data.size() - task.snd;
}
else
{
task_len = task.data.size();
}
//注:net_fd在这之前需要先调用socket(), connect()等函数初始化
int send_len = sendToTcp(net_fd, (char*)task.data.c_str() + task.snd, task_len);
if(send_len < 0 )
{
//发送错误关闭缓冲区
//注:关闭套接字描述符
return task_num ;
}
if(send_len != task_len)
{
//消息没发送完成, 消息不取出来,只设置已发送的长度并打开写事件
//注:设置事件(设置可写、可读、关闭、错误等事件)
task_queue.front().snd += send_len;
return task_num;
}
++task_num;
task_queue.pop();
}
return task_num;
}
描述:创建消息的带参构造函数
参数:
msg: 消息指针
msg_len: 消息的长度
snd_len: 已发送消息的长度
*/
int createTask(const char* msg, int msg_len, int snd)
{
STTask task(msg, msg_len, snd);
task_queue.push(task);
return 0;
}
描述:发送消息函数(所有的消息重这里发送)
参数:
buff: 消息缓冲区指针
len: 消息的长度
*/
int sendMessage(char* buff, int len)
{
if(buff == 0 || len <=0)
{
return -1 ;
}
int snd = 0;
//任务队列如果为空,先偿试一下是否能发送出来
if(task_queue.empty())
{
snd = sendToTcp(net_fd, buff, len);
if( snd < 0 )
{
//发生错误,不用构造,(这里如果消息不能丢,就需要保存)
//注:关闭套接字描述符
return -1;
}
if(snd != len)
{
//消息没发送完成, 创建消息任务并打开写事件
createTask(buff, len, snd);
//注:设置事件(设置可写、可读、关闭、错误等事件)
}
}
else
{
//任务队列不为空,需要放在队列里,这里为了保证消息的顺序
createTask(buff, len);
snd = len;
}
return snd;
}
在网络IO密集型的高并发异步非阻塞程序里,网络的IO吞吐量一般远远跟不上CPU的处理能力,
程序在发送大量数据时,会导致TCP的发送缓冲区满,应用层在调用send或write等函数时会返回 EAGAIN(重试)。
由于程序是异步非阻塞的,需要把没有发送完的数据保存起来,等TCP发送缓冲区有空闲空间时重新触把保存的数据写到发送缓冲区里。
这里我们介绍一个把没有发送完的数据保存到队列里,并在发送缓冲区空闲时或定时器定时的触发的相关处理函数
*/
//存储任务消息的队列
std::queue<STTask> task_queue;
//网络套接字
int net_fd = -1;
struct STTask
{
unsigned int snd;
std::string data;
STTask()
{
snd = 0;
data.clear();
}
/*
描述:创建消息的带参构造函数
参数:
msg: 消息指针
msg_len: 消息的长度
snd_len: 已发送消息的长度
*/
STTask(const char* msg, int msg_len, int snd_len):
data(msg,msg_len),
snd(snd_len)
{
}
void Reset()
{
snd = 0;
}
};
/*
描述:创建消息的带参构造函数
参数:
fd: 套接字描述符
buff: 缓冲区指针
len: 消息的长度
*/
int sendToTcp(int fd, char* buff, const int len)
{
int ret;
int _snd=0;
int _len = len;
errno = 0;
while(_len > 0)
{
ret = send(fd, (void*)((char*)buff + _snd), _len, 0);
if ( ret <= 0 )
{
if ( errno == EINTR || errno == EAGAIN )
{
break;
}
else
{
printf("ret:%d, errno:%d, desc:%s\n", ret, errno, strerror(errno));
return -1;
}
}
_len -= ret;
_snd += ret;
}
return _snd;
}
/*
描述:重新激活发送数据(缓冲区有可写事件可调用此函数发送未发送的队列任务消息)
参数:
无
*/
int ReactivateSend()
{
if(task_queue.empty())
{
//没有数据可发送, 删除写事件(也就是只设置可读、关闭、错误等事件)
//注:设置事件
return 0;
}
int count = sendQueueTask();
if(task_queue.empty())
{
//没有数据可发送, 删除写事件(也就是只设置可读、关闭、错误等事件)
//注:设置事件
return 0;
}
return 0;
}
描述:循环发送队列里的任务数据
参数:
无
*/
int sendQueueTask()
{
int task_len = 0;
int task_num = 0;
while(!task_queue.empty())
{
//取出队列头的消息,这里要保证顺序
STTask & task = task_queue.front();
if(task.snd > 0)
{
task_len = task.data.size() - task.snd;
}
else
{
task_len = task.data.size();
}
//注:net_fd在这之前需要先调用socket(), connect()等函数初始化
int send_len = sendToTcp(net_fd, (char*)task.data.c_str() + task.snd, task_len);
if(send_len < 0 )
{
//发送错误关闭缓冲区
//注:关闭套接字描述符
return task_num ;
}
if(send_len != task_len)
{
//消息没发送完成, 消息不取出来,只设置已发送的长度并打开写事件
//注:设置事件(设置可写、可读、关闭、错误等事件)
task_queue.front().snd += send_len;
return task_num;
}
++task_num;
task_queue.pop();
}
return task_num;
}
描述:创建消息的带参构造函数
参数:
msg: 消息指针
msg_len: 消息的长度
snd_len: 已发送消息的长度
*/
int createTask(const char* msg, int msg_len, int snd)
{
STTask task(msg, msg_len, snd);
task_queue.push(task);
return 0;
}
描述:发送消息函数(所有的消息重这里发送)
参数:
buff: 消息缓冲区指针
len: 消息的长度
*/
int sendMessage(char* buff, int len)
{
if(buff == 0 || len <=0)
{
return -1 ;
}
int snd = 0;
//任务队列如果为空,先偿试一下是否能发送出来
if(task_queue.empty())
{
snd = sendToTcp(net_fd, buff, len);
if( snd < 0 )
{
//发生错误,不用构造,(这里如果消息不能丢,就需要保存)
//注:关闭套接字描述符
return -1;
}
if(snd != len)
{
//消息没发送完成, 创建消息任务并打开写事件
createTask(buff, len, snd);
//注:设置事件(设置可写、可读、关闭、错误等事件)
}
}
else
{
//任务队列不为空,需要放在队列里,这里为了保证消息的顺序
createTask(buff, len);
snd = len;
}
return snd;
}