如何在接受器循环中从 ENFILE 中恢复

How to recover from ENFILE in acceptor loop

本文关键字:ENFILE 恢复 循环      更新时间:2023-10-16

我写了一个测试来显示asio async_accept的问题我有一台服务器,可以永远打开与它的任何连接在接受许多连接(在我的例子中为 1017)之后,下一个连接失败,出现"打开的文件过多"错误。然后,对async_accept的任何调用都会立即调用处理程序

这是误会吗?

我使用 debian 7 amd64

生成文件

CXX=clang++ -O2
OBJ= main.o server.o
LIBS=-lboost_system -lboost_thread
all: server
server: $(OBJ)
    $(CXX) -o server $(OBJ) $(LIBS)
main.o: main.cpp
    $(CXX) -c main.cpp
server.o: server.hpp server.cpp
    $(CXX) -c server.cpp
clean:
    rm -f *.o *~
distclean: clean
    rm -f server

服务器.hpp

#include <boost/asio.hpp>
class server
{
    public:
        server();
        void run();
    private:
        void start_accept();
        void handle_accept(const boost::system::error_code& e);
        boost::asio::io_service ios;
        boost::asio::ip::tcp::acceptor acceptor_;
        std::vector<boost::asio::ip::tcp::socket*> sockets;
};

服务器.cpp

#include <boost/bind.hpp>
#include "server.hpp"
server::server(): acceptor_(ios)
{
    boost::asio::ip::tcp::resolver resolver(ios);
    boost::asio::ip::tcp::resolver::query query("0.0.0.0", "5050");
    boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
    acceptor_.open(endpoint.protocol());
    acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
    acceptor_.bind(endpoint);
    acceptor_.listen();
    start_accept();
}
void server::run()
{
    ios.run();
}
void server::start_accept()
{
    boost::asio::ip::tcp::socket *s=new  boost::asio::ip::tcp::socket(ios);
    sockets.push_back(s);
    acceptor_.async_accept(*s,
            boost::bind(&server::handle_accept, this,
                boost::asio::placeholders::error));
}
void server::handle_accept(const boost::system::error_code& e)
{
    if(e)
    {
        std::cerr<<"e.message() = "<<e.message()<<std::endl;
        boost::asio::ip::tcp::socket *s=sockets.back();
        s->close();
        delete s;
        sockets.pop_back();
    }
    static int i=1;
    std::cerr<<"i = "<<i++<<std::endl;
    start_accept();
}

主.cpp

#include "server.hpp"
int main()
{
    server s;
    s.run();
    return 0;
}

我的测试是

for x in `seq $1`;do nc 127.0.0.1 5050 & done

server::handle_accept结束时,即使套接字出错,您也会将异步接受提交回 IO 服务队列。

我不确定这是您想要的,但我可以通过在接受失败时重新启动侦听器来使其"工作"(恢复)。(请注意,如果在多个线程上运行服务,这将需要一些同步)。

住在科里鲁

#include <boost/asio.hpp>
class server
{
    public:
        server();
        void run();
    private:
        bool start_listen();
        void start_accept();
        void handle_accept(boost::system::error_code e);
        boost::asio::io_service ios;
        boost::asio::ip::tcp::acceptor acceptor_;
        std::vector<boost::asio::ip::tcp::socket*> sockets;
};
#include <boost/bind.hpp>
server::server(): acceptor_(ios)
{
    start_listen();
    start_accept();
}
void server::run()
{
    ios.run();
}
bool server::start_listen()
{
    boost::system::error_code e;
    boost::asio::ip::tcp::endpoint endpoint { {}, 5050 };
    acceptor_.open(endpoint.protocol());
    acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
    acceptor_.bind(endpoint, e);
    if (e)
        return false;
    acceptor_.listen();
    return true;
}
void server::start_accept()
{
    boost::asio::ip::tcp::socket *s=new  boost::asio::ip::tcp::socket(ios);
    sockets.push_back(s);
    acceptor_.async_accept(*s, boost::bind(&server::handle_accept, this, boost::asio::placeholders::error));
}
#include <iostream>
void server::handle_accept(boost::system::error_code e)
{
    if(e)
    {
        std::cerr<<"e.message() = "<<e.message()<<std::endl;
        boost::asio::ip::tcp::socket *s=sockets.back();
        s->close();
        delete s;
        sockets.pop_back();
        acceptor_.close();
        if (!start_listen())
            return;
    }
    static int i=1;
    std::cerr<<"i = "<<i++<<std::endl;
    start_accept();
}
int main()
{
    server s;
    s.run();
}

当您以这种方式打开套接字时

boost::asio::ip::tcp::socket *s=new  boost::asio::ip::tcp::socket(ios);

套接字没有打开,只是创建了一个对象。当您尝试接受它时,您达到了打开文件描述符限制,因此您会收到"打开文件太多"错误。然后,您只需将其从vector中删除,并创建另一个无法在接受中打开的套接字。我认为这就是错误的原因。

更新:顺便说一下,这个函数

void server::start_accept()
{
    boost::asio::ip::tcp::socket *s=new  boost::asio::ip::tcp::socket(ios);
    sockets.push_back(s);
    acceptor_.async_accept(*s,
        boost::bind(&server::handle_accept, this,
            boost::asio::placeholders::error));
}

似乎可能存在内存泄漏,如果在方法中生成异常push_backs将丢失。

更新:如果您以这种方式重写handle_accept,则 inifinte 循环是固定的:

void server::handle_accept(const boost::system::error_code& e)
{
    if(e)
    {
      auto size = 2;
      for (int i = 0; i < size; ++i) {
        std::cerr<<"e.message() = "<<e.message()<<std::endl;
        boost::asio::ip::tcp::socket *s=sockets.back();
        s->close();
        delete s;
        sockets.pop_back();
      }
    }
    static int i=1;
    std::cerr<<"i = "<<i++<<std::endl;
    start_accept();
}