服务器上有如下代码:
bool recv_handler(connection_t &connection)
{
int fd = connection.get_fd();
char temp_buffer[2048];
while (true)
{
// 清空缓冲区
bzero(temp_buffer, 2048);
// 设置非阻塞标志MSG_DONTWAIT
ssize_t recv_ret = recv(fd, temp_buffer, 2048, MSG_DONTWAIT);
if (recv_ret != -1)
{
// 输入connection缓冲区
connection._in_buffer.append(temp_buffer, recv_ret);
}
else
{
// 内核缓冲区读完了
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
// 被信号打断
else if (errno == EINTR)
continue;
else
{
// 异常处理
// ...
return false;
}
}
}
// 完成一次et触发的读取: I/O完成
// ...
return true;
}
每次遇到用户疯狂刷新,服务器就会瘫痪。
ps aux |grep 发现进程还在,top 查一下发现cpu占用100%。推断应该是阻塞了。
使用gdb启动,触发bug后按^C终止程序 ,再bt命令 查看栈帧。发现底层运行reactorIO模型的线程阻塞在了recv()上。
我就很懵逼了,看了看代码 不仅fcntl为fd设置了非阻塞模式:
还在调用recv时设置了MSG_DONTWAIT,用man手册说明查看,应该设置了便不会阻塞。
出错应该返回-1。
后来我只能耐心的把recv的说明仔细地看了一遍,终于发现了问题
当对端突然关闭连接,不会返回-1并且设置错误码。而是仅仅返回0。
修改后的代码可以幼爽起来了:
bool recv_handler(connection_t &connection)
{
int fd = connection.get_fd();
char temp_buffer[2048];
while (true)
{
bzero(temp_buffer, 2048);
ssize_t recv_ret = recv(fd, temp_buffer, 2048, MSG_DONTWAIT);
if (recv_ret > 0)
{
// 输入connection缓冲区
connection._in_buffer.append(temp_buffer, recv_ret);
}
else
{
if (recv_ret == 0)
{
// 对端关闭就无需在处理报文直接退出
log(INFO, "recv_client_quit");
connection.error_handler();
return true;
}
else
{
// 内核缓冲区读完了
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
// 被信号打断
else if (errno == EINTR)
continue;
else
{
// 异常处理
if (connection.is_func_exist_error())
connection.error_handler();
return false;
}
}
}
//...
}