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