Linux下Socket的accept()哪些错误属于致命错误?
问题描述
我正在用C++的Boost.Asio编写TCP服务器,Linux环境下底层基于POSIX sockets。已经实现了循环接受新连接,在ip::tcp::acceptor::async_accept(对应POSIX的accept函数)成功后初始化套接字并生成会话。
现在需要对async_accept返回的错误码做三类划分:
- 非致命错误:完全由客户端导致(比如断开连接、协议违规),出现后应继续监听,避免恶意客户端引发DoS
- 致命错误:由编程或服务器配置错误导致,出现后需退出监听循环并终止程序
- 服务器过载错误:因负载过高导致
accept()失败,出现后不应终止监听循环
需要明确如何判断错误码所属类别,同时希望参考已解决此问题的开源应用/库(不限Boost.Asio,直接用POSIX API的C项目也可),程序需具备可移植性,以Linux为主要目标平台。
补充:该问题同样适用于直接使用POSIX API的C程序,Boost.Asio会完整传递多数底层错误码。
补充2:附上修改后的Boost.Asio TCP回显服务器示例,需实现server::is_fatal_error函数的判断逻辑:
// // async_tcp_echo_server.cpp // ~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // *** Modified by Emile Cormier for discussion purposes *** #include <cstdlib> #include <iostream> #include <memory> #include <system_error> #include <utility> #include <boost/asio.hpp> using boost::asio::ip::tcp; class session : public std::enable_shared_from_this<session> { public: session(tcp::socket socket) : socket_(std::move(socket)) {} void start() {do_read();} private: void do_read() { auto self(shared_from_this()); socket_.async_read_some(boost::asio::buffer(data_, max_length), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) do_write(length); }); } void do_write(std::size_t length) { auto self(shared_from_this()); boost::asio::async_write(socket_, boost::asio::buffer(data_, length), [this, self](boost::system::error_code ec, std::size_t /*length*/) { if (!ec) do_read(); }); } tcp::socket socket_; enum { max_length = 1024 }; char data_[max_length]; }; class server { public: server(boost::asio::io_context& io_context, short port) : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { do_accept(); } private: static bool is_fatal_error(boost::system::error_code ec) { // *** How do I classify the error as fatal so that it bails out *** // *** of the listen loop? *** return false; // Return something so that it compiles. } void do_accept() { acceptor_.async_accept( [this](boost::system::error_code ec, tcp::socket socket) { if (!ec) { std::make_shared<session>(std::move(socket))->start(); } else if (is_fatal_error(ec)) { // Break out of listening loop if error is due to problem // on server side (e.g. misconfiguration) std::cerr << "Fatal error: " << ec.message() << std::endl; throw std::system_error{ec}; } do_accept(); }); } tcp::acceptor acceptor_; }; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: async_tcp_echo_server <port>\n"; return 1; } boost::asio::io_context io_context; server s(io_context, std::atoi(argv[1])); io_context.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }
补充3:需考虑服务器过载导致的accept()失败场景,此类情况不能终止监听循环。
解决方案
错误分类判断逻辑
1. 致命错误(需终止程序)
这类错误源于服务器自身的配置或编程错误,无法通过重试恢复,必须终止监听循环:
EACCES:绑定端口需要特权但程序未获取,或套接字路径权限不足EBADF:acceptor的文件描述符无效(比如已被错误关闭)EINVAL:acceptor未处于监听状态(未调用listen或重复关闭)ENOTSOCK:文件描述符对应的不是套接字(编程错误)EFAULT:内核无法访问传入的地址参数(严重内存错误)ENOBUFS/ENOMEM:系统内存彻底耗尽,无恢复可能的极端情况
对应Boost.Asio的实现代码:
#include <errno.h> static bool is_fatal_error(boost::system::error_code ec) { switch(ec.value()) { case EACCES: case EBADF: case EINVAL: case ENOTSOCK: case EFAULT: case ENOBUFS: case ENOMEM: return true; default: return false; } }
2. 非致命错误(忽略并继续监听)
这类错误完全由客户端行为导致,不影响服务器正常运行,直接重试即可:
ECONNABORTED:客户端在连接建立前主动断开EPROTO:客户端发送非法数据包引发协议错误EINTR:accept被信号中断(可直接重试)
3. 服务器过载错误(重试即可恢复)
这类错误是临时状态,服务器负载下降后可恢复,无需终止监听:
EMFILE:进程打开的文件描述符达到上限ENFILE:系统级文件描述符耗尽
可移植性优化
如果需要跨UNIX-like平台兼容,建议使用Boost.Asio提供的平台无关错误条件,避免直接依赖POSIX错误码数值,例如:
static bool is_fatal_error(boost::system::error_code ec) { using namespace boost::asio::error; return ec == access_denied || ec == bad_descriptor || ec == invalid_argument || ec == not_socket || ec == fault || ec == no_buffer_space || ec == out_of_memory; }
参考开源项目
- Nginx:高性能HTTP服务器,其连接处理模块对
accept错误有完善分类,仅在严重系统错误时终止服务,客户端错误直接忽略 - Redis:网络层代码细致区分临时错误与致命错误,确保服务在过载或客户端异常时持续运行
- libevent:事件驱动库的
evconnlistener模块,明确了哪些错误需要重试、哪些需要终止,逻辑清晰可参考
内容的提问来源于stack exchange,提问作者Emile Cormier
相关产品推荐
相关产品推荐

