Asio C++零基础入门(十):Asio C++实际项目应用案例
引言
在前几篇教程中,我们学习了Asio C++库的基础知识、高级特性和性能优化技巧。本文将通过几个实际项目案例,展示如何将Asio应用到真实世界的应用程序开发中。这些案例涵盖了不同的应用场景,包括聊天服务器、HTTP服务器、文件传输应用和网络代理,旨在帮助读者理解如何在实际项目中有效地使用Asio。
案例一:多人聊天服务器
聊天服务器是网络编程中常见的应用场景,非常适合展示Asio的异步I/O能力和多用户处理能力。
设计概述
我们将实现一个基于TCP的多人聊天服务器,支持以下功能:
- 多个客户端同时连接
- 客户端之间的消息广播
- 用户名注册和显示
- 简单的聊天命令(如列出在线用户)
代码实现
1. 连接类(Connection)
#include <asio.hpp>
#include <string>
#include <memory>
#include <vector>
class ChatServer; // 前向声明
class Connection : public std::enable_shared_from_this<Connection> {
public:
using pointer = std::shared_ptr<Connection>;
static pointer create(asio::io_context& io_context, ChatServer* server) {
return pointer(new Connection(io_context, server));
}
asio::ip::tcp::socket& socket() {
return socket_;
}
// 启动连接处理
void start() {
// 首先请求客户端发送用户名
asio::async_write(socket_, asio::buffer("Please enter your username:\n"),
[self = shared_from_this()](const asio::error_code& ec, std::size_t /*length*/) {
if (!ec) {
self->read_username();
}
});
}
// 发送消息给客户端
void send_message(const std::string& message) {
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(message);
if (!write_in_progress) {
do_write();
}
}
// 获取用户名
const std::string& username() const {
return username_;
}
private:
Connection(asio::io_context& io_context, ChatServer* server)
: socket_(io_context), server_(server), strand_(io_context) {
}
// 读取用户名
void read_username() {
asio::async_read_until(socket_, username_buffer_, '\n',
[self = shared_from_this()](const asio::error_code& ec, std::size_t /*length*/) {
if (!ec) {
std::istream is(&self->username_buffer_);
std::getline(is, self->username_);
// 去除换行符和回车符
self->username_.erase(
std::remove_if(self->username_.begin(), self->username_.end(),
[](char c) { return c == '\r' || c == '\n'; }),
self->username_.end());
// 通知服务器有新用户连接
self->server_->on_new_connection(self);
// 开始读取聊天消息
self->read_message();
} else {
// 处理错误,关闭连接
self->server_->on_disconnection(self);
}
});
}
// 读取聊天消息
void read_message() {
asio::async_read_until(socket_, read_buffer_, '\n',
[self = shared_from_this()](const asio::error_code& ec, std::size_t /*length*/) {
if (!ec) {
std::istream is(&self->read_buffer_);
std::string message;
std::getline(is, message);
// 去除换行符和回车符
message.erase(
std::remove_if(message.begin(), message.end(),
[](char c) { return c == '\r' || c == '\n'; }),
message.end());
// 处理消息
self->server_->process_message(self, message);
// 继续读取下一条消息
self->read_message();
} else {
// 处理错误,关闭连接
self->server_->on_disconnection(self);
}
});
}
// 写入消息到客户端
void do_write() {
asio::async_write(socket_,
asio::buffer(write_msgs_.front().data(), write_msgs_.front().size()),
asio::bind_executor(strand_,
[self = shared_from_this()](const asio::error_code& ec, std::size_t /*length*/) {
if (!ec) {
self->write_msgs_.pop_front();
if (!self->write_msgs_.empty()) {
self->do_write();
}
} else {
// 处理错误,关闭连接
self->server_->on_disconnection(self);
}
}));
}
asio::ip::tcp::socket socket_;
ChatServer* server_;
asio::io_context::strand strand_;
asio::streambuf read_buffer_;
asio::streambuf username_buffer_;
std::string username_;
std::deque<std::string> write_msgs_;
};
2. 聊天服务器类(ChatServer)
class ChatServer {
public:
ChatServer(asio::io_context& io_context, short port)
: io_context_(io_context),
acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) {
do_accept();
}
// 处理新连接
void on_new_connection(Connection::pointer connection) {
// 将连接添加到连接列表
connections_.insert(connection);
// 广播新用户加入消息
std::string welcome_message = "*** User " + connection->username() + " has joined the chat!\n";
broadcast_message(welcome_message, nullptr);
// 发送欢迎消息给新用户
connection->send_message("Welcome to the chat server!\n");
connection->send_message("Type /help for available commands.\n");
}
// 处理连接断开
void on_disconnection(Connection::pointer connection) {
if (connections_.find(connection) != connections_.end()) {
// 广播用户离开消息
std::string leave_message = "*** User " + connection->username() + " has left the chat!\n";
broadcast_message(leave_message, nullptr);
// 从连接列表中移除
connections_.erase(connection);
}
}
// 处理消息
void process_message(Connection::pointer sender, const std::string& message) {
// 检查是否是命令
if (!message.empty() && message[0] == '/') {
process_command(sender, message);
} else {
// 普通消息,广播给其他用户
std::string formatted_message = sender->username() + ": " + message + "\n";
broadcast_message(formatted_message, sender);
}
}
// 广播消息给所有客户端
void broadcast_message(const std::string& message, Connection::pointer exclude) {
for (auto& connection : connections_) {
if (connection != exclude) {
connection->send_message(message);
}
}
}
private:
// 接受新连接
void do_accept() {
acceptor_.async_accept(
[this](const asio::error_code& ec, asio::ip::tcp::socket socket) {
if (!ec) {
// 创建新连接并启动
auto connection = Connection::create(io_context_, this);
connection->socket() = std::move(socket);
connection->start();
}
// 继续接受下一个连接
do_accept();
});
}
// 处理命令
void process_command(Connection::pointer sender, const std::string& command) {
std::istringstream iss(command);
std::string cmd;
iss >> cmd;
if (cmd == "/help") {
sender->send_message("Available commands:\n");
sender->send_message(" /help - Show this help message\n");
sender->send_message(" /list - List all online users\n");
sender->send_message(" /quit - Disconnect from the server\n");
} else if (cmd == "/list") {
sender->send_message("Online users (" + std::to_string(connections_.size()) + "):\n");
for (auto& connection : connections_) {
sender->send_message(" " + connection->username() + "\n");
}
} else if (cmd == "/quit") {
sender->send_message("Goodbye!\n");
// 断开连接会在异步操作完成后触发on_disconnection
} else {
sender->send_message("Unknown command: " + cmd + ". Type /help for available commands.\n");
}
}
asio::io_context& io_context_;
asio::ip::tcp::acceptor acceptor_;
std::set<Connection::pointer> connections_;
};
3. 主函数
int main(int argc, char* argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: chat_server <port>" << std::endl;
return 1;
}
asio::io_context io_context;
// 创建并运行服务器
ChatServer server(io_context, std::atoi(argv[1]));
// 在多个线程中运行io_context以提高性能
std::vector<std::thread> threads;
const std::size_t num_threads = std::thread::hardware_concurrency();
for (std::size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([&io_context]() {
io_context.run();
});
}
// 等待所有线程完成
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
关键技术点
- 连接管理 - 使用
std::set存储所有活动连接,方便广播消息和管理连接状态。 - 线程安全 - 使用
asio::io_context::strand确保对共享资源的安全访问。 - 消息队列 - 每个连接维护一个写入消息队列,确保消息按顺序发送。
- 命令处理 - 实现简单的命令系统,支持用户交互和服务器管理。
- 多线程优化 - 在多个线程中运行
io_context,提高服务器的并发处理能力。
案例二:简单HTTP服务器
HTTP服务器是Web开发的基础,下面我们将使用Asio实现一个简单的HTTP服务器。
设计概述
我们的HTTP服务器将支持以下功能:
- 处理GET请求
- 静态文件服务
- 简单的动态响应
- HTTP/1.1基本功能
代码实现
1. HTTP请求和响应类
#include <asio.hpp>
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <sstream>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
// HTTP请求类
struct HttpRequest {
std::string method;
std::string uri;
std::string version;
std::map<std::string, std::string> headers;
std::string body;
};
// HTTP响应类
struct HttpResponse {
std::string version = "HTTP/1.1";
int status_code = 200;
std::string status_message = "OK";
std::map<std::string, std::string> headers;
std::string body;
// 构建响应字符串
std::string to_string() const {
std::stringstream ss;
ss << version << " " << status_code << " " << status_message << "\r\n";
for (const auto& [key, value] : headers) {
ss << key << ": " << value << "\r\n";
}
ss << "Content-Length: " << body.size() << "\r\n";
ss << "\r\n" << body;
return ss.str();
}
};
// HTTP请求解析器
class HttpRequestParser {
public:
HttpRequestParser() : state_(MethodStart) {}
// 解析请求数据
bool parse(HttpRequest& request, const char* data, std::size_t length) {
for (std::size_t i = 0; i < length; ++i) {
if (!consume(request, data[i])) {
return false;
}
}
return state_ == Body || state_ == Completed;
}
// 检查解析是否完成
bool is_done() const {
return state_ == Completed;
}
private:
// 状态枚举
enum State {
MethodStart,
Method,
UriStart,
Uri,
VersionStart,
Version,
HeadersStart,
HeaderName,
HeaderValueStart,
HeaderValue,
ExpectingNewline1,
ExpectingNewline2,
Body,
Completed
} state_;
// 消费字符并更新状态
bool consume(HttpRequest& request, char c) {
switch (state_) {
case MethodStart:
if (!isspace(c)) {
state_ = Method;
request.method.push_back(c);
}
return true;
case Method:
if (c == ' ') {
state_ = UriStart;
} else {
request.method.push_back(c);
}
return true;
case UriStart:
if (!isspace(c)) {
state_ = Uri;
request.uri.push_back(c);
}
return true;
case Uri:
if (c == ' ') {
state_ = VersionStart;
} else {
request.uri.push_back(c);
}
return true;
case VersionStart:
if (!isspace(c)) {
state_ = Version;
request.version.push_back(c);
}
return true;
case Version:
if (c == '\r') {
state_ = ExpectingNewline1;
} else {
request.version.push_back(c);
}
return true;
case ExpectingNewline1:
if (c == '\n') {
state_ = HeadersStart;
} else {
return false;
}
return true;
case HeadersStart:
if (c == '\r') {
state_ = ExpectingNewline2;
} else if (!isspace(c)) {
current_header_name_.push_back(c);
state_ = HeaderName;
}
return true;
case HeaderName:
if (c == ':') {
state_ = HeaderValueStart;
} else {
current_header_name_.push_back(c);
}
return true;
case HeaderValueStart:
if (!isspace(c)) {
current_header_value_.push_back(c);
state_ = HeaderValue;
}
return true;
case HeaderValue:
if (c == '\r') {
request.headers[current_header_name_] = current_header_value_;
current_header_name_.clear();
current_header_value_.clear();
state_ = ExpectingNewline1;
} else {
current_header_value_.push_back(c);
}
return true;
case ExpectingNewline2:
if (c == '\n') {
// 检查是否有Content-Length头部,如果有则需要读取body
auto it = request.headers.find("Content-Length");
if (it != request.headers.end()) {
content_length_ = std::stoi(it->second);
state_ = Body;
} else {
state_ = Completed;
}
} else {
return false;
}
return true;
case Body:
request.body.push_back(c);
if (request.body.size() >= content_length_) {
state_ = Completed;
}
return true;
case Completed:
return false;
default:
return false;
}
}
std::string current_header_name_;
std::string current_header_value_;
std::size_t content_length_ = 0;
};
2. HTTP连接类
class HttpConnection : public std::enable_shared_from_this<HttpConnection> {
public:
using pointer = std::shared_ptr<HttpConnection>;
static pointer create(asio::io_context& io_context, const std::string& document_root) {
return pointer(new HttpConnection(io_context, document_root));
}
asio::ip::tcp::socket& socket() {
return socket_;
}
void start() {
do_read();
}
private:
HttpConnection(asio::io_context& io_context, const std::string& document_root)
: socket_(io_context), document_root_(document_root) {}
// 读取请求数据
void do_read() {
auto self = shared_from_this();
socket_.async_read_some(asio::buffer(buffer_),
[this, self](const asio::error_code& ec, std::size_t bytes_transferred) {
if (!ec) {
// 解析请求
if (parser_.parse(request_, buffer_.data(), bytes_transferred)) {
// 请求解析完成,处理请求
handle_request();
} else {
// 继续读取数据
do_read();
}
}
});
}
// 处理请求
void handle_request() {
HttpResponse response;
try {
// 目前只支持GET方法
if (request_.method != "GET") {
response.status_code = 405;
response.status_message = "Method Not Allowed";
response.body = "Method not allowed";
} else {
// 处理GET请求
handle_get_request(response);
}
} catch (const std::exception& e) {
response.status_code = 500;
response.status_message = "Internal Server Error";
response.body = std::string("Internal server error: ") + e.what();
}
// 设置默认头部
response.headers["Server"] = "Asio HTTP Server";
response.headers["Content-Type"] = "text/html";
response.headers["Connection"] = "close";
// 发送响应
send_response(response);
}
// 处理GET请求
void handle_get_request(HttpResponse& response) {
// 规范化URI
std::string path = request_.uri;
if (path == "/") {
path = "/index.html";
}
// 防止目录遍历攻击
if (path.find("..") != std::string::npos) {
response.status_code = 403;
response.status_message = "Forbidden";
response.body = "Access forbidden";
return;
}
// 构建文件路径
std::string file_path = document_root_ + path;
// 检查文件是否存在
if (!fs::exists(file_path) || !fs::is_regular_file(file_path)) {
// 检查是否是特殊路径
if (path == "/hello") {
// 处理动态内容
response.body = "<html><body><h1>Hello, World!</h1></body></html>";
} else {
// 文件不存在
response.status_code = 404;
response.status_message = "Not Found";
response.body = "File not found";
}
return;
}
// 读取文件内容
std::ifstream file(file_path, std::ios::binary);
if (!file) {
response.status_code = 500;
response.status_message = "Internal Server Error";
response.body = "Failed to open file";
return;
}
// 读取文件内容到响应体
std::stringstream buffer;
buffer << file.rdbuf();
response.body = buffer.str();
// 设置正确的Content-Type
std::string extension = fs::path(file_path).extension().string();
response.headers["Content-Type"] = get_mime_type(extension);
}
// 获取MIME类型
std::string get_mime_type(const std::string& extension) {
if (extension == ".html" || extension == ".htm") return "text/html";
else if (extension == ".css") return "text/css";
else if (extension == ".js") return "application/javascript";
else if (extension == ".json") return "application/json";
else if (extension == ".png") return "image/png";
else if (extension == ".jpg" || extension == ".jpeg") return "image/jpeg";
else if (extension == ".gif") return "image/gif";
else if (extension == ".txt") return "text/plain";
else return "application/octet-stream";
}
// 发送响应
void send_response(const HttpResponse& response) {
auto self = shared_from_this();
std::string response_str = response.to_string();
asio::async_write(socket_, asio::buffer(response_str.data(), response_str.size()),
[this, self](const asio::error_code& ec, std::size_t /*bytes_transferred*/) {
if (!ec) {
// 响应发送完成后关闭连接
asio::error_code ignored_ec;
socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
});
}
asio::ip::tcp::socket socket_;
std::string document_root_;
enum { max_length = 8192 };
char buffer_[max_length];
HttpRequest request_;
HttpRequestParser parser_;
};
3. HTTP服务器类
class HttpServer {
public:
HttpServer(asio::io_context& io_context, short port, const std::string& document_root)
: io_context_(io_context),
acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
document_root_(document_root) {
do_accept();
}
private:
void do_accept() {
auto connection = HttpConnection::create(io_context_, document_root_);
acceptor_.async_accept(connection->socket(),
[this, connection](const asio::error_code& ec) {
if (!ec) {
connection->start();
}
do_accept();
});
}
asio::io_context& io_context_;
asio::ip::tcp::acceptor acceptor_;
std::string document_root_;
};
4. 主函数
int main(int argc, char* argv[]) {
try {
if (argc != 3) {
std::cerr << "Usage: http_server <port> <document_root>" << std::endl;
return 1;
}
asio::io_context io_context;
// 创建并运行服务器
HttpServer server(io_context, std::atoi(argv[1]), argv[2]);
// 运行io_context
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
关键技术点
- HTTP协议解析 - 实现了基本的HTTP请求解析器,支持解析请求行、头部和消息体。
- 静态文件服务 - 支持从文件系统提供静态文件,并根据文件扩展名设置正确的Content-Type。
- 简单的动态内容 - 支持处理简单的动态路径,如
/hello。 - 安全考虑 - 实现了防止目录遍历攻击的措施。
- 错误处理 - 提供了基本的错误处理机制,返回适当的HTTP状态码。
案例三:文件传输应用
文件传输是网络应用中的常见需求,下面我们将实现一个基于Asio的文件传输应用。
设计概述
我们的文件传输应用将支持以下功能:
- 客户端向服务器上传文件
- 客户端从服务器下载文件
- 文件传输进度显示
- 大文件传输支持
代码实现
1. 文件传输协议
#include <asio.hpp>
#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
// 文件传输命令
enum class FileCommand {
UPLOAD, // 上传文件
DOWNLOAD, // 下载文件
LIST, // 列出文件
EXIT // 退出
};
// 协议消息
struct FileMessage {
FileCommand command = FileCommand::EXIT;
std::string filename;
std::uint64_t file_size = 0;
// 序列化消息
std::vector<char> serialize() const {
std::vector<char> buffer(sizeof(FileCommand) + sizeof(std::uint64_t) +
filename.size() + 1);
// 序列化命令
std::memcpy(buffer.data(), &command, sizeof(FileCommand));
// 序列化文件名长度和文件名
std::uint64_t filename_len = filename.size();
std::memcpy(buffer.data() + sizeof(FileCommand), &filename_len, sizeof(std::uint64_t));
std::memcpy(buffer.data() + sizeof(FileCommand) + sizeof(std::uint64_t),
filename.c_str(), filename.size());
// 序列化文件大小
std::memcpy(buffer.data() + sizeof(FileCommand) + sizeof(std::uint64_t) + filename.size(),
&file_size, sizeof(std::uint64_t));
return buffer;
}
// 反序列化消息
static FileMessage deserialize(const char* data) {
FileMessage message;
// 反序列化命令
std::memcpy(&message.command, data, sizeof(FileCommand));
// 反序列化文件名长度和文件名
std::uint64_t filename_len;
std::memcpy(&filename_len, data + sizeof(FileCommand), sizeof(std::uint64_t));
message.filename.assign(data + sizeof(FileCommand) + sizeof(std::uint64_t), filename_len);
// 反序列化文件大小
std::memcpy(&message.file_size,
data + sizeof(FileCommand) + sizeof(std::uint64_t) + filename_len,
sizeof(std::uint64_t));
return message;
}
// 获取消息大小
static std::size_t message_size() {
return sizeof(FileCommand) + sizeof(std::uint64_t) + 256 + sizeof(std::uint64_t); // 假设文件名最长256字节
}
};
2. 文件传输客户端
class FileClient {
public:
FileClient(asio::io_context& io_context, const std::string& server_ip, short server_port)
: io_context_(io_context),
socket_(io_context),
server_endpoint_(asio::ip::address::from_string(server_ip), server_port) {
}
// 连接到服务器
bool connect() {
asio::error_code ec;
socket_.connect(server_endpoint_, ec);
if (ec) {
std::cerr << "Failed to connect to server: " << ec.message() << std::endl;
return false;
}
std::cout << "Connected to server " << server_endpoint_ << std::endl;
return true;
}
// 上传文件
void upload_file(const std::string& local_path, const std::string& remote_filename) {
try {
// 打开文件
std::ifstream file(local_path, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file: " << local_path << std::endl;
return;
}
// 获取文件大小
file.seekg(0, std::ios::end);
std::uint64_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
// 发送上传命令和文件信息
FileMessage message;
message.command = FileCommand::UPLOAD;
message.filename = remote_filename;
message.file_size = file_size;
auto buffer = message.serialize();
asio::write(socket_, asio::buffer(buffer));
std::cout << "Uploading file: " << local_path << " ("
<< (file_size / 1024.0) << " KB)" << std::endl;
// 发送文件数据
std::uint64_t bytes_sent = 0;
char data_buffer[8192];
while (file.read(data_buffer, sizeof(data_buffer)) || file.gcount() > 0) {
std::size_t bytes_read = file.gcount();
asio::write(socket_, asio::buffer(data_buffer, bytes_read));
bytes_sent += bytes_read;
// 显示进度
double progress = static_cast<double>(bytes_sent) / file_size * 100.0;
std::cout << "Progress: " << std::fixed << std::setprecision(2)
<< progress << "%\r";
std::cout.flush();
}
std::cout << "\nFile uploaded successfully." << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception during file upload: " << e.what() << std::endl;
}
}
// 下载文件
void download_file(const std::string& remote_filename, const std::string& local_path) {
try {
// 发送下载命令和文件名
FileMessage message;
message.command = FileCommand::DOWNLOAD;
message.filename = remote_filename;
message.file_size = 0;
auto buffer = message.serialize();
asio::write(socket_, asio::buffer(buffer));
// 接收文件信息
char response_buffer[FileMessage::message_size()];
asio::read(socket_, asio::buffer(response_buffer));
FileMessage response = FileMessage::deserialize(response_buffer);
if (response.file_size == 0) {
std::cerr << "File not found on server: " << remote_filename << std::endl;
return;
}
// 打开本地文件
std::ofstream file(local_path, std::ios::binary);
if (!file) {
std::cerr << "Failed to create file: " << local_path << std::endl;
return;
}
std::cout << "Downloading file: " << remote_filename << " ("
<< (response.file_size / 1024.0) << " KB)" << std::endl;
// 接收文件数据
std::uint64_t bytes_received = 0;
char data_buffer[8192];
while (bytes_received < response.file_size) {
std::size_t bytes_to_read = std::min(static_cast<std::size_t>(response.file_size - bytes_received),
sizeof(data_buffer));
std::size_t bytes_read = asio::read(socket_, asio::buffer(data_buffer, bytes_to_read));
file.write(data_buffer, bytes_read);
bytes_received += bytes_read;
// 显示进度
double progress = static_cast<double>(bytes_received) / response.file_size * 100.0;
std::cout << "Progress: " << std::fixed << std::setprecision(2)
<< progress << "%\r";
std::cout.flush();
}
std::cout << "\nFile downloaded successfully." << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception during file download: " << e.what() << std::endl;
}
}
// 列出服务器文件
void list_files() {
try {
// 发送列出文件命令
FileMessage message;
message.command = FileCommand::LIST;
auto buffer = message.serialize();
asio::write(socket_, asio::buffer(buffer));
// 接收文件列表大小
std::uint64_t list_size;
asio::read(socket_, asio::buffer(&list_size, sizeof(list_size)));
// 接收文件列表
std::vector<char> list_buffer(list_size);
asio::read(socket_, asio::buffer(list_buffer));
std::string file_list(list_buffer.begin(), list_buffer.end());
std::cout << "Files on server:\n" << file_list << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception during listing files: " << e.what() << std::endl;
}
}
// 退出连接
void exit() {
try {
// 发送退出命令
FileMessage message;
message.command = FileCommand::EXIT;
auto buffer = message.serialize();
asio::write(socket_, asio::buffer(buffer));
// 关闭连接
socket_.shutdown(asio::ip::tcp::socket::shutdown_both);
socket_.close();
std::cout << "Disconnected from server." << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception during exit: " << e.what() << std::endl;
}
}
private:
asio::io_context& io_context_;
asio::ip::tcp::socket socket_;
asio::ip::tcp::endpoint server_endpoint_;
};
3. 文件传输服务器
class FileServer {
public:
FileServer(asio::io_context& io_context, short port, const std::string& file_directory)
: io_context_(io_context),
acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
file_directory_(file_directory) {
// 确保文件目录存在
std::filesystem::create_directories(file_directory);
do_accept();
}
private:
void do_accept() {
auto socket = std::make_shared<asio::ip::tcp::socket>(io_context_);
acceptor_.async_accept(*socket, [this, socket](const asio::error_code& ec) {
if (!ec) {
std::cout << "Client connected: " << socket->remote_endpoint() << std::endl;
handle_client(socket);
}
do_accept();
});
}
void handle_client(std::shared_ptr<asio::ip::tcp::socket> socket) {
auto client_session = std::make_shared<ClientSession>(io_context_, socket, file_directory_);
client_session->start();
}
// 客户端会话类
class ClientSession {
public:
ClientSession(asio::io_context& io_context,
std::shared_ptr<asio::ip::tcp::socket> socket,
const std::string& file_directory)
: io_context_(io_context),
socket_(socket),
file_directory_(file_directory) {
}
void start() {
do_read_message();
}
private:
void do_read_message() {
auto self = shared_from_this();
socket_->async_read_some(
asio::buffer(buffer_, FileMessage::message_size()),
[this, self](const asio::error_code& ec, std::size_t bytes_transferred) {
if (!ec) {
FileMessage message = FileMessage::deserialize(buffer_);
// 根据命令类型处理
switch (message.command) {
case FileCommand::UPLOAD:
handle_upload(message);
break;
case FileCommand::DOWNLOAD:
handle_download(message);
break;
case FileCommand::LIST:
handle_list();
break;
case FileCommand::EXIT:
std::cout << "Client disconnected: " << socket_->remote_endpoint() << std::endl;
socket_->close();
break;
default:
std::cerr << "Unknown command" << std::endl;
do_read_message();
break;
}
} else {
std::cout << "Client disconnected: " << socket_->remote_endpoint() << std::endl;
socket_->close();
}
});
}
void handle_upload(const FileMessage& message) {
try {
// 构建文件路径
std::string file_path = file_directory_ + "/" + message.filename;
// 创建文件
std::ofstream file(file_path, std::ios::binary);
if (!file) {
std::cerr << "Failed to create file: " << file_path << std::endl;
do_read_message();
return;
}
std::cout << "Receiving file: " << message.filename << " ("
<< (message.file_size / 1024.0) << " KB)" << std::endl;
// 接收文件数据
receive_file_data(file, message.file_size);
} catch (const std::exception& e) {
std::cerr << "Exception during file upload: " << e.what() << std::endl;
do_read_message();
}
}
void receive_file_data(std::ofstream& file, std::uint64_t file_size) {
auto self = shared_from_this();
// 计算要读取的字节数
std::size_t bytes_to_read = std::min(static_cast<std::uint64_t>(sizeof(buffer_)),
file_size - bytes_received_);
socket_->async_read_some(
asio::buffer(buffer_, bytes_to_read),
[this, self, &file, file_size](const asio::error_code& ec, std::size_t bytes_transferred) {
if (!ec) {
// 写入文件
file.write(buffer_, bytes_transferred);
bytes_received_ += bytes_transferred;
// 显示进度
if (file_size > 0) {
double progress = static_cast<double>(bytes_received_) / file_size * 100.0;
std::cout << "Progress: " << std::fixed << std::setprecision(2)
<< progress << "%\r";
std::cout.flush();
}
// 检查是否接收完成
if (bytes_received_ < file_size) {
receive_file_data(file, file_size);
} else {
std::cout << "\nFile received successfully: " << bytes_received_ << " bytes" << std::endl;
bytes_received_ = 0;
do_read_message();
}
} else {
std::cerr << "Error receiving file data: " << ec.message() << std::endl;
bytes_received_ = 0;
do_read_message();
}
});
}
void handle_download(const FileMessage& message) {
try {
// 构建文件路径
std::string file_path = file_directory_ + "/" + message.filename;
// 检查文件是否存在
std::ifstream file(file_path, std::ios::binary);
if (!file) {
std::cerr << "File not found: " << file_path << std::endl;
// 发送文件不存在的响应
FileMessage response;
response.command = FileCommand::DOWNLOAD;
response.filename = message.filename;
response.file_size = 0;
auto buffer = response.serialize();
asio::write(*socket_, asio::buffer(buffer));
do_read_message();
return;
}
// 获取文件大小
file.seekg(0, std::ios::end);
std::uint64_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
// 发送文件信息
FileMessage response;
response.command = FileCommand::DOWNLOAD;
response.filename = message.filename;
response.file_size = file_size;
auto buffer = response.serialize();
asio::write(*socket_, asio::buffer(buffer));
std::cout << "Sending file: " << message.filename << " ("
<< (file_size / 1024.0) << " KB)" << std::endl;
// 发送文件数据
send_file_data(file, file_size);
} catch (const std::exception& e) {
std::cerr << "Exception during file download: " << e.what() << std::endl;
do_read_message();
}
}
void send_file_data(std::ifstream& file, std::uint64_t file_size) {
auto self = shared_from_this();
// 读取文件数据
file.read(buffer_, sizeof(buffer_));
std::size_t bytes_read = file.gcount();
if (bytes_read > 0) {
// 发送数据
asio::async_write(
*socket_,
asio::buffer(buffer_, bytes_read),
[this, self, &file, file_size](const asio::error_code& ec, std::size_t bytes_transferred) {
if (!ec) {
bytes_sent_ += bytes_transferred;
// 显示进度
double progress = static_cast<double>(bytes_sent_) / file_size * 100.0;
std::cout << "Progress: " << std::fixed << std::setprecision(2)
<< progress << "%\r";
std::cout.flush();
// 继续发送数据
if (bytes_sent_ < file_size) {
send_file_data(file, file_size);
} else {
std::cout << "\nFile sent successfully: " << bytes_sent_ << " bytes" << std::endl;
bytes_sent_ = 0;
do_read_message();
}
} else {
std::cerr << "Error sending file data: " << ec.message() << std::endl;
bytes_sent_ = 0;
do_read_message();
}
});
}
}
void handle_list() {
try {
std::stringstream file_list;
// 遍历文件目录
for (const auto& entry : std::filesystem::directory_iterator(file_directory_)) {
if (std::filesystem::is_regular_file(entry)) {
// 获取文件信息
std::string filename = entry.path().filename().string();
std::uintmax_t file_size = std::filesystem::file_size(entry);
file_list << filename << " (" << (file_size / 1024.0) << " KB)\n";
}
}
std::string list_str = file_list.str();
std::uint64_t list_size = list_str.size();
// 发送文件列表大小
asio::write(*socket_, asio::buffer(&list_size, sizeof(list_size)));
// 发送文件列表
asio::write(*socket_, asio::buffer(list_str.data(), list_str.size()));
std::cout << "Sent file list to client" << std::endl;
do_read_message();
} catch (const std::exception& e) {
std::cerr << "Exception during listing files: " << e.what() << std::endl;
do_read_message();
}
}
asio::io_context& io_context_;
std::shared_ptr<asio::ip::tcp::socket> socket_;
std::string file_directory_;
enum { max_buffer_size = 8192 };
char buffer_[max_buffer_size];
std::uint64_t bytes_received_ = 0;
std::uint64_t bytes_sent_ = 0;
};
asio::io_context& io_context_;
asio::ip::tcp::acceptor acceptor_;
std::string file_directory_;
};
4. 主函数(服务器)
int main(int argc, char* argv[]) {
try {
if (argc != 3) {
std::cerr << "Usage: file_server <port> <file_directory>" << std::endl;
return 1;
}
asio::io_context io_context;
// 创建并运行服务器
FileServer server(io_context, std::atoi(argv[1]), argv[2]);
// 在多个线程中运行io_context
std::vector<std::thread> threads;
const std::size_t num_threads = std::thread::hardware_concurrency();
for (std::size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([&io_context]() {
io_context.run();
});
}
// 等待所有线程完成
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
关键技术点
- 自定义协议 - 设计了简单的文件传输协议,支持上传、下载、列出文件和退出等命令。
- 大文件传输 - 通过分块传输支持大文件传输,避免一次性加载整个文件到内存。
- 进度显示 - 实时显示文件传输进度,提升用户体验。
- 并发处理 - 服务器使用异步I/O和多线程处理多个客户端连接。
- 文件系统操作 - 使用C++17的
std::filesystem库处理文件和目录操作。
案例四:简单网络代理
网络代理是一种常见的网络应用,用于转发网络请求。下面我们将实现一个简单的TCP代理。
设计概述
我们的网络代理将支持以下功能:
- TCP连接转发
- 基本的连接管理
- 简单的日志记录
- 支持多个并发连接
代码实现
#include <asio.hpp>
#include <string>
#include <memory>
#include <iostream>
#include <fstream>
#include <mutex>
#include <ctime>
// 日志记录器
class Logger {
public:
Logger(const std::string& log_file = "proxy.log") : log_file_(log_file) {
}
template<typename... Args>
void log(Args&&... args) {
std::lock_guard<std::mutex> lock(mutex_);
// 获取当前时间
std::time_t now = std::time(nullptr);
char time_buf[20];
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
// 打开日志文件
std::ofstream file(log_file_, std::ios::app);
if (file.is_open()) {
file << "[" << time_buf << "] ";
log_impl(file, std::forward<Args>(args)...);
file << std::endl;
}
// 同时输出到控制台
std::cout << "[" << time_buf << "] ";
log_impl(std::cout, std::forward<Args>(args)...);
std::cout << std::endl;
}
private:
template<typename T>
void log_impl(T& stream) {
// 递归终止条件
}
template<typename T, typename... Args>
void log_impl(T& stream, T&& first, Args&&... rest) {
stream << std::forward<T>(first);
log_impl(stream, std::forward<Args>(rest)...);
}
std::string log_file_;
std::mutex mutex_;
};
// TCP代理类
class TcpProxy {
public:
TcpProxy(asio::io_context& io_context, short listen_port,
const std::string& target_host, short target_port)
: io_context_(io_context),
acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), listen_port)),
target_host_(target_host),
target_port_(target_port),
logger_() {
logger_.log("TCP Proxy started on port ", listen_port, ", forwarding to ",
target_host_, ":", target_port_);
do_accept();
}
private:
// 接受新连接
void do_accept() {
// 创建客户端和目标服务器的socket
auto client_socket = std::make_shared<asio::ip::tcp::socket>(io_context_);
auto server_socket = std::make_shared<asio::ip::tcp::socket>(io_context_);
acceptor_.async_accept(*client_socket,
[this, client_socket, server_socket](const asio::error_code& ec) {
if (!ec) {
// 获取客户端地址
std::string client_address = client_socket->remote_endpoint().address().to_string();
unsigned short client_port = client_socket->remote_endpoint().port();
logger_.log("Accepted connection from ", client_address, ":", client_port);
// 连接到目标服务器
asio::ip::tcp::resolver resolver(io_context_);
resolver.async_resolve(target_host_, std::to_string(target_port_),
[this, client_socket, server_socket, client_address, client_port](
const asio::error_code& ec, asio::ip::tcp::resolver::results_type results) {
if (!ec) {
asio::async_connect(*server_socket, results,
[this, client_socket, server_socket, client_address, client_port](
const asio::error_code& ec, asio::ip::tcp::endpoint) {
if (!ec) {
logger_.log("Connected to target server for client ",
client_address, ":", client_port);
// 开始转发数据
start_transfer(client_socket, server_socket, client_address, client_port);
} else {
logger_.log("Failed to connect to target server for client ",
client_address, ":", client_port, ": ", ec.message());
// 关闭客户端连接
client_socket->close();
}
});
} else {
logger_.log("Failed to resolve target host for client ",
client_address, ":", client_port, ": ", ec.message());
// 关闭客户端连接
client_socket->close();
}
});
}
// 继续接受下一个连接
do_accept();
});
}
// 开始数据传输
void start_transfer(std::shared_ptr<asio::ip::tcp::socket> client_socket,
std::shared_ptr<asio::ip::tcp::socket> server_socket,
const std::string& client_address, unsigned short client_port) {
// 从客户端读取数据并转发到服务器
transfer_data(client_socket, server_socket, "client->server",
client_address, client_port);
// 从服务器读取数据并转发到客户端
transfer_data(server_socket, client_socket, "server->client",
client_address, client_port);
}
// 传输数据
void transfer_data(std::shared_ptr<asio::ip::tcp::socket> source,
std::shared_ptr<asio::ip::tcp::socket> destination,
const std::string& direction, const std::string& client_address,
unsigned short client_port) {
auto buffer = std::make_shared<std::vector<char>>(8192);
source->async_read_some(asio::buffer(*buffer),
[this, source, destination, buffer, direction, client_address, client_port](
const asio::error_code& ec, std::size_t bytes_transferred) {
if (!ec && bytes_transferred > 0) {
// 记录传输的数据量
logger_.log("Transferred ", bytes_transferred, " bytes ", direction,
" for client ", client_address, ":", client_port);
// 转发数据
asio::async_write(*destination, asio::buffer(*buffer, bytes_transferred),
[this, source, destination, buffer, direction, client_address, client_port](
const asio::error_code& ec, std::size_t /*bytes_transferred*/) {
if (!ec) {
// 继续传输数据
transfer_data(source, destination, direction,
client_address, client_port);
} else {
// 发生错误,关闭连接
logger_.log("Error writing data ", direction, " for client ",
client_address, ":", client_port, ": ", ec.message());
close_sockets(source, destination);
}
});
} else {
// 连接关闭或发生错误
if (ec != asio::error::eof) {
logger_.log("Error reading data ", direction, " for client ",
client_address, ":", client_port, ": ", ec.message());
} else {
logger_.log("Connection closed ", direction, " for client ",
client_address, ":", client_port);
}
close_sockets(source, destination);
}
});
}
// 关闭socket
void close_sockets(std::shared_ptr<asio::ip::tcp::socket> socket1,
std::shared_ptr<asio::ip::tcp::socket> socket2) {
asio::error_code ignored_ec;
if (socket1->is_open()) {
socket1->shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
socket1->close(ignored_ec);
}
if (socket2->is_open()) {
socket2->shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
socket2->close(ignored_ec);
}
}
asio::io_context& io_context_;
asio::ip::tcp::acceptor acceptor_;
std::string target_host_;
short target_port_;
Logger logger_;
};
int main(int argc, char* argv[]) {
try {
if (argc != 4) {
std::cerr << "Usage: tcp_proxy <listen_port> <target_host> <target_port>" << std::endl;
return 1;
}
asio::io_context io_context;
// 创建并运行代理服务器
TcpProxy proxy(io_context, std::atoi(argv[1]), argv[2], std::atoi(argv[3]));
// 在多个线程中运行io_context
std::vector<std::thread> threads;
const std::size_t num_threads = std::thread::hardware_concurrency();
for (std::size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([&io_context]() {
io_context.run();
});
}
// 等待所有线程完成
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
关键技术点
- 双向数据转发 - 实现了客户端和服务器之间的双向数据转发。
- 连接管理 - 为每个客户端连接创建一个到目标服务器的连接。
- 错误处理 - 处理各种网络错误,确保资源正确释放。
- 日志记录 - 实现了简单的日志记录功能,记录连接状态和数据传输情况。
- 多线程优化 - 在多个线程中运行
io_context,提高代理服务器的并发处理能力。
总结
本文通过四个实际项目案例,展示了如何在真实世界的应用程序开发中使用Asio C++库。这些案例涵盖了不同的应用场景,包括多人聊天服务器、HTTP服务器、文件传输应用和网络代理,每个案例都包含了完整的代码实现和关键技术点的解释。
通过这些案例,我们可以看到Asio库的强大功能和灵活性,它可以用于构建各种类型的网络应用程序,从简单的聊天服务器到复杂的文件传输应用。同时,我们也学习了一些在实际项目中使用Asio的最佳实践,如连接管理、错误处理、多线程优化等。
要成为一名优秀的Asio开发者,除了掌握基础API和概念外,还需要通过实践不断积累经验,特别是在处理复杂的网络场景和性能优化方面。希望本文的案例能够帮助读者更好地理解和应用Asio库,构建高性能、可靠的网络应用程序。
在下一篇教程中,我们将探讨Asio与其他C++库和框架的集成,以及如何在现代C++项目中充分利用Asio的优势。
更多推荐

所有评论(0)