引言

在前几篇教程中,我们学习了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;
}

关键技术点

  1. 连接管理 - 使用std::set存储所有活动连接,方便广播消息和管理连接状态。
  2. 线程安全 - 使用asio::io_context::strand确保对共享资源的安全访问。
  3. 消息队列 - 每个连接维护一个写入消息队列,确保消息按顺序发送。
  4. 命令处理 - 实现简单的命令系统,支持用户交互和服务器管理。
  5. 多线程优化 - 在多个线程中运行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;
}

关键技术点

  1. HTTP协议解析 - 实现了基本的HTTP请求解析器,支持解析请求行、头部和消息体。
  2. 静态文件服务 - 支持从文件系统提供静态文件,并根据文件扩展名设置正确的Content-Type。
  3. 简单的动态内容 - 支持处理简单的动态路径,如/hello
  4. 安全考虑 - 实现了防止目录遍历攻击的措施。
  5. 错误处理 - 提供了基本的错误处理机制,返回适当的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;
}

关键技术点

  1. 自定义协议 - 设计了简单的文件传输协议,支持上传、下载、列出文件和退出等命令。
  2. 大文件传输 - 通过分块传输支持大文件传输,避免一次性加载整个文件到内存。
  3. 进度显示 - 实时显示文件传输进度,提升用户体验。
  4. 并发处理 - 服务器使用异步I/O和多线程处理多个客户端连接。
  5. 文件系统操作 - 使用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;
}

关键技术点

  1. 双向数据转发 - 实现了客户端和服务器之间的双向数据转发。
  2. 连接管理 - 为每个客户端连接创建一个到目标服务器的连接。
  3. 错误处理 - 处理各种网络错误,确保资源正确释放。
  4. 日志记录 - 实现了简单的日志记录功能,记录连接状态和数据传输情况。
  5. 多线程优化 - 在多个线程中运行io_context,提高代理服务器的并发处理能力。

总结

本文通过四个实际项目案例,展示了如何在真实世界的应用程序开发中使用Asio C++库。这些案例涵盖了不同的应用场景,包括多人聊天服务器、HTTP服务器、文件传输应用和网络代理,每个案例都包含了完整的代码实现和关键技术点的解释。

通过这些案例,我们可以看到Asio库的强大功能和灵活性,它可以用于构建各种类型的网络应用程序,从简单的聊天服务器到复杂的文件传输应用。同时,我们也学习了一些在实际项目中使用Asio的最佳实践,如连接管理、错误处理、多线程优化等。

要成为一名优秀的Asio开发者,除了掌握基础API和概念外,还需要通过实践不断积累经验,特别是在处理复杂的网络场景和性能优化方面。希望本文的案例能够帮助读者更好地理解和应用Asio库,构建高性能、可靠的网络应用程序。

在下一篇教程中,我们将探讨Asio与其他C++库和框架的集成,以及如何在现代C++项目中充分利用Asio的优势。

更多推荐