c++ 如何让boost::asio::acceptor在关闭连接后再次接受连接?

bn31dyow  于 2023-03-25  发布在  其他
关注(0)|答案(1)|浏览(184)

我的服务器想限制并发建立的会话(TCP套接字)的数量。当达到最大会话/连接数时,我只需close()接受者。当其他会话关闭时,我想再次只需open()接受者。
通过构造函数创建接受器,将端点,协议和端口作为我的类成员init-list的一部分:

, mEndPoint( boost::asio::ip::tcp::v4(), port )
, mAcceptor( io_context, mEndPoint )

然后,后来:

mAcceptor.async_accept(
                          [this]( boost::system::error_code ec, boost::asio::ip::tcp::socket socket )
                          {... }
                       );

会话可以成功建立。当达到最大会话数时,我只需通过mAcceptor.close();(在完成处理程序中)关闭接受器-新客户端无法建立连接,正如预期的那样(得到“Connection refused”错误)。
现在,当调用mAcceptor.open( mEndPoint.protocol() );并调用同一个mAcceptor.async_accept(...)时,立即抛出一个异常system:22
缺少了什么?
我尝试调用mAcceptor.bind( endpoint );,结果出现异常address already in use
我试过mAcceptor.wait( boost::asio::socket_base::wait_type::wait_read );,没有区别。
当调用mAcceptor.listen();时也是一样的--当一个新的客户端试图建立一个连接时,它会得到“Connection refused”--在服务器端,异步lambda不会被调用。
我错过了什么?

cyvaqqii

cyvaqqii1#

你需要开始监听接收器。
我尝试调用mAcceptor.bind(endpoint);,这导致异常地址已在使用中。
然后使用SO_REUSEADDR选项。
下面是一个简单的自包含示例:

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
using namespace std::literals;
namespace asio = boost::asio;
using asio::ip::tcp;

void accept_n(tcp::acceptor& acc, unsigned n, std::string const& identification) {
    for (std::string msg; n--; msg.clear()) {
        auto conn = acc.accept();
        auto len  = read_until(conn, asio::dynamic_buffer(msg), "\n");
        std::cout << identification << " accepted #" << n           //
                  << " (msg: " << quoted(msg.substr(0, len)) << ")" //
                  << " from " << conn.remote_endpoint() << std::endl;
        write(conn, asio::buffer(identification + "\n"));
    }
}

int main() {
    asio::io_context ioc;
    tcp::endpoint const listen_ep{{}, 8989};
    tcp::acceptor acc(ioc, listen_ep);
    acc.set_option(tcp::acceptor::reuse_address(true));

    accept_n(acc, 5, "first");

    std::cout << " === closing " << std::endl;
    acc.close();

    std::cout << " === re-opening " << std::endl;
    acc.open(tcp::v4());
    acc.set_option(tcp::acceptor::reuse_address(true));
    acc.bind(listen_ep);
    acc.listen();

    accept_n(acc, 5, "second");
    
    std::cout << " === done " << std::endl;
}

实时演示:Live On Coliru

不要重复自己

为了代码简洁,你可以通过不使用简写结构来使两个accept相同:

int main() {
    asio::io_context ioc;
    tcp::endpoint const listen_ep{{}, 8989};
    tcp::acceptor acc(ioc /*, listen_ep*/); // no listen here

    for (std::string const identification : {"first", "second", "third"}) {
        if (acc.is_open()) {
            std::cout << " === closing " << std::endl;
            acc.close();
        }

        std::cout << " === (re-)opening " << std::endl;
        acc.open(tcp::v4());
        acc.set_option(tcp::acceptor::reuse_address(true));
        acc.bind(listen_ep);
        acc.listen();

        accept_n(acc, 5, "second");
    }
}

当然,这只是回避了一个问题,为什么要重用接受器。在实践中,您只需封装所有内容:

struct Service {
    asio::io_context    ioc_;
    tcp::endpoint const ep_{{}, 8989};
    tcp::acceptor       acc_{ioc_};

    void accept_n(unsigned n, std::string const& identification) {
        if (acc_.is_open()) {
            std::cout << " === closing " << std::endl;
            acc_.close();
        }

        std::cout << " === (re-)opening " << std::endl;
        acc_.open(tcp::v4());
        acc_.set_option(tcp::acceptor::reuse_address(true));
        acc_.bind(ep_);
        acc_.listen();

        for (std::string msg; n--; msg.clear()) {
            auto conn = acc_.accept();
            auto len  = read_until(conn, asio::dynamic_buffer(msg), "\n");
            std::cout << identification << " accepted #" << n               //
                      << " (msg: " << quoted(msg.substr(0, len - 1)) << ")" //
                      << " from " << conn.remote_endpoint() << std::endl;
            write(conn, asio::buffer(identification + "\n"));
        }
    }
};

int main() {
    for (std::string const identification : {"first", "second", "third"})
        Service().accept_n(5, identification);
}

我想不出任何理由

int main() {
    Service svc;
    for (std::string const identification : {"first", "second", "third"})
        svc.accept_n(5, identification);
}

客观上会更好/更有用。

不关闭更简单

您甚至可能不需要关闭接受器:

**第一个e第一个f第一个x

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;

struct Service {
    asio::io_context    ioc_;
    tcp::endpoint const ep_{{}, 8989};
    tcp::acceptor       acc_{ioc_, ep_};

    void accept_n(unsigned n, std::string const& identification) {
        for (std::string msg; n--; msg.clear()) {
            auto conn = acc_.accept();
            auto len  = read_until(conn, asio::dynamic_buffer(msg), "\n");
            std::cout << identification << " accepted #" << n               //
                      << " (msg: " << quoted(msg.substr(0, len - 1)) << ")" //
                      << " from " << conn.remote_endpoint() << std::endl;
            write(conn, asio::buffer(identification + "\n"));
        }
    }
};

int main() {
    Service svc;
    for (std::string const identification : {"first", "second", "third"})
        svc.accept_n(5, identification);
}

生活:

相关问题