MST

星途 面试题库

面试题:网络编程中C++的libevent与Boost.Asio在基础使用场景下性能差异

在一个简单的网络服务器场景,使用libevent和Boost.Asio分别实现一个能够处理少量并发连接并进行简单数据收发的功能,然后阐述在这种基础场景下两者可能出现性能差异的原因。
32.7万 热度难度
后端开发网络编程

知识考点

AI 面试

面试题答案

一键面试

使用libevent实现

  1. 初始化libevent
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <iostream>
#include <string>

void read_callback(struct bufferevent* bev, void* ctx) {
    char buffer[1024];
    struct evbuffer* input = bufferevent_get_input(bev);
    int n = evbuffer_remove(input, buffer, sizeof(buffer));
    if (n > 0) {
        buffer[n] = '\0';
        std::string data(buffer);
        std::cout << "Received: " << data << std::endl;
        // 回显数据
        bufferevent_write(bev, buffer, n);
    }
}

void event_callback(struct bufferevent* bev, short events, void* ctx) {
    if (events & BEV_EVENT_EOF) {
        std::cout << "Connection closed." << std::endl;
        bufferevent_free(bev);
    } else if (events & BEV_EVENT_ERROR) {
        std::cerr << "Error occurred." << std::endl;
        bufferevent_free(bev);
    }
}

int main() {
    struct event_base* base = event_base_new();
    if (!base) {
        std::cerr << "Could not initialize libevent." << std::endl;
        return 1;
    }

    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(9999);
    sin.sin_addr.s_addr = INADDR_ANY;

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        std::cerr << "Socket creation failed." << std::endl;
        event_base_free(base);
        return 1;
    }

    if (bind(listenfd, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
        std::cerr << "Bind failed." << std::endl;
        close(listenfd);
        event_base_free(base);
        return 1;
    }

    if (listen(listenfd, 10) < 0) {
        std::cerr << "Listen failed." << std::endl;
        close(listenfd);
        event_base_free(base);
        return 1;
    }

    auto accept_callback = [](evutil_socket_t fd, short event, void* arg) {
        struct event_base* base = (struct event_base*)arg;
        struct sockaddr_storage ss;
        socklen_t slen = sizeof(ss);
        int clientfd = accept(fd, (struct sockaddr*)&ss, &slen);
        if (clientfd < 0) {
            std::cerr << "Accept failed." << std::endl;
            return;
        }

        struct bufferevent* bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE);
        if (!bev) {
            std::cerr << "Bufferevent creation failed." << std::endl;
            close(clientfd);
            return;
        }

        bufferevent_setcb(bev, read_callback, nullptr, event_callback, nullptr);
        bufferevent_enable(bev, EV_READ | EV_WRITE);
    };

    struct event* listen_event = event_new(base, listenfd, EV_READ | EV_PERSIST, accept_callback, base);
    if (!listen_event) {
        std::cerr << "Event creation failed." << std::endl;
        close(listenfd);
        event_base_free(base);
        return 1;
    }

    event_add(listen_event, nullptr);
    event_base_dispatch(base);

    event_free(listen_event);
    close(listenfd);
    event_base_free(base);
    return 0;
}
  1. 代码说明
    • 首先初始化libeventevent_base
    • 创建监听套接字并绑定到指定端口。
    • 当有新连接时,accept_callback函数被调用,创建bufferevent用于数据收发,并设置读取回调read_callback和事件回调event_callback
    • read_callback函数读取数据并回显,event_callback处理连接关闭和错误事件。

使用Boost.Asio实现

#include <iostream>
#include <boost/asio.hpp>

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(boost::asio::io_context& io_context) : socket_(io_context) {}

    boost::asio::ip::tcp::socket& socket() {
        return socket_;
    }

    void start() {
        read();
    }

private:
    void read() {
        auto self(shared_from_this());
        boost::asio::async_read_until(socket_, buffer_, '\n',
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::string data;
                    std::istream is(&buffer_);
                    std::getline(is, data);
                    std::cout << "Received: " << data << std::endl;
                    write(data);
                } else {
                    std::cerr << "Read error: " << ec.message() << std::endl;
                }
            });
    }

    void write(const std::string& data) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data + "\n"),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    read();
                } else {
                    std::cerr << "Write error: " << ec.message() << std::endl;
                }
            });
    }

    boost::asio::ip::tcp::socket socket_;
    boost::asio::streambuf buffer_;
};

class Server {
public:
    Server(boost::asio::io_context& io_context, unsigned short port)
        : acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
          socket_(io_context) {
        start_accept();
    }

private:
    void start_accept() {
        acceptor_.async_accept(socket_,
            [this](boost::system::error_code ec) {
                if (!ec) {
                    std::make_shared<Session>(acceptor_.get_executor().context())->start();
                } else {
                    std::cerr << "Accept error: " << ec.message() << std::endl;
                }
                start_accept();
            });
    }

    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ip::tcp::socket socket_;
};

main函数中使用:

int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 9999);

        std::vector<std::thread> threads;
        for (std::size_t i = 0; i < std::thread::hardware_concurrency(); ++i) {
            threads.emplace_back([&io_context]() { io_context.run(); });
        }

        for (auto& thread : threads) {
            thread.join();
        }
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }
    return 0;
}
  1. 代码说明
    • Session类负责单个连接的数据收发,使用异步读写操作。read函数读取数据直到遇到换行符,然后调用write函数回显数据。
    • Server类负责监听新连接,当有新连接时创建Session对象并启动。
    • main函数中,创建io_contextServer对象,并使用多个线程运行io_context以处理并发。

性能差异原因

  1. 事件模型和调度
    • libevent:采用传统的事件驱动模型,其事件调度器在处理少量并发连接时表现良好。但在高并发场景下,由于其事件队列管理和调度算法相对简单,可能会出现性能瓶颈。例如,在处理大量事件时,遍历事件队列可能会花费较多时间。
    • Boost.Asio:基于io_contextstrand等机制实现了更灵活和高效的事件调度。io_context可以管理多个异步操作,strand用于保证特定序列的操作按顺序执行,减少线程安全问题的同时,在多线程环境下处理并发连接有更好的性能表现。对于少量并发连接,这种机制的优势可能不明显,但随着并发量增加,其性能优势逐渐体现。
  2. 线程模型
    • libevent:默认是单线程处理事件,虽然可以通过多线程扩展,但需要用户手动管理线程间的同步和资源共享。在处理少量并发连接时,单线程模型可以减少线程切换开销,但在需要利用多核性能时,多线程扩展相对复杂,处理不当可能导致性能下降。
    • Boost.Asio:天生支持多线程,通过io_context.run()可以在多个线程中并行处理事件,充分利用多核CPU的性能。在处理少量并发连接时,多线程可能带来一定的线程创建和管理开销,但在并发量增加时,多核利用能力使其性能更具优势。
  3. 代码复杂度和灵活性
    • libevent:代码相对底层,开发者需要手动处理更多细节,如套接字操作、事件绑定等。对于简单场景,这种方式可能增加代码量和出错概率,但对于特定优化需求,有更大的操作空间。
    • Boost.Asio:提供了更高级的抽象,如async_read_untilasync_write等函数,使代码更简洁易读。但其抽象层次较高,在一些极端优化场景下,可能无法像libevent那样直接操作底层资源。在简单网络服务器场景下,Boost.Asio的简洁性可能带来一定开发效率提升,但在性能调优方面,可能需要更深入理解其底层机制。