You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 21:27:34