使用独立 Asio 和 OpenSSL 实现 HTTPS 通信

使用独立 Asio 和 OpenSSL 实现 HTTPS 通信的完整指南

引言

在现代网络编程中,HTTPS 通信已成为安全数据传输的标准。C++ 开发者经常面临实现 HTTPS 客户端的挑战,特别是在选择网络库和配置依赖方面。本文将详细介绍如何使用独立 Asio(非 Boost 版本)结合 OpenSSL 实现 HTTPS 通信,涵盖从环境配置到实际代码实现的完整流程。

第一部分:环境搭建

1.1 安装 OpenSSL

Windows 系统:

  1. 访问 OpenSSL for Windows 下载页面
  2. 选择适合的版本(推荐 Win32 OpenSSL v3.x.x)
  3. 运行安装程序,记录安装路径(如 C:\OpenSSL-Win32
  4. 将 OpenSSL 的 bin 目录添加到系统 PATH 环境变量

1.2 获取独立 Asio

独立 Asio 是 Boost.Asio 的独立版本,不依赖 Boost 库:

  1. 访问 Asio 官网
  2. 下载最新版本的独立 Asio
  3. 解压到项目目录,如 E:\Project\ExLib\asio

1.3 配置开发环境

Visual Studio 配置:

  1. 项目属性 → C/C++ → 常规 → 附加包含目录:

    复制代码
    E:\Project\ExLib\asio\include
    C:\OpenSSL-Win64\include
  2. 项目属性 → 链接器 → 常规 → 附加库目录:

    复制代码
    C:\OpenSSL-Win64\lib
  3. 项目属性 → 链接器 → 输入 → 附加依赖项:

    复制代码
    libssl.lib;libcrypto.lib;ws2_32.lib;crypt32.lib;

CMake 配置:

cmake 复制代码
cmake_minimum_required(VERSION 3.15)
project(AsioHttpsClient)

set(CMAKE_CXX_STANDARD 11)

# 查找 OpenSSL
find_package(OpenSSL REQUIRED)

# 包含 Asio 头文件
include_directories(${PROJECT_SOURCE_DIR}/../ExLib/asio/include)

add_executable(AsioHttpsClient main.cpp)

# 链接库
target_link_libraries(AsioHttpsClient PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
)

if(WIN32)
    target_link_libraries(AsioHttpsClient PRIVATE ws2_32 crypt32)
endif()

第二部分:实现 HTTPS 客户端

2.1 基本 HTTPS POST 请求实现

cpp 复制代码
// HttpsClient.hpp
#pragma once

#include <asio.hpp>
#include <asio/ssl.hpp>
#include <string>
#include <memory>
#include <functional>

namespace ssl = asio::ssl;

class HttpsClient {
public:
    using ResponseCallback = std::function<void(int status, 
                                                const std::string& headers,
                                                const std::string& body)>;
    using ErrorCallback = std::function<void(const std::string& error)>;

    HttpsClient(asio::io_context& io_context, 
                bool verify_certificate = true);
    
    void post(const std::string& host,
              const std::string& port,
              const std::string& path,
              const std::string& data,
              const std::map<std::string, std::string>& headers = {},
              ResponseCallback response_callback = nullptr,
              ErrorCallback error_callback = nullptr);

    void set_certificate_file(const std::string& cert_file);

private:
    void on_resolve(const asio::error_code& ec,
                    asio::ip::tcp::resolver::results_type endpoints);
    void on_connect(const asio::error_code& ec);
    void on_handshake(const asio::error_code& ec);
    void on_write(const asio::error_code& ec, size_t bytes_transferred);
    void on_read_status_line(const asio::error_code& ec, size_t bytes_transferred);
    void on_read_headers(const asio::error_code& ec, size_t bytes_transferred);
    void on_read_body(const asio::error_code& ec, size_t bytes_transferred);

    asio::io_context& io_context_;
    ssl::context ssl_context_;
    std::unique_ptr<ssl::stream<asio::ip::tcp::socket>> socket_;
    asio::ip::tcp::resolver resolver_;
    
    std::string host_;
    std::string port_;
    std::string path_;
    std::string request_data_;
    std::map<std::string, std::string> custom_headers_;
    
    asio::streambuf request_;
    asio::streambuf response_;
    
    ResponseCallback response_callback_;
    ErrorCallback error_callback_;
    
    int http_status_code_;
    std::string response_headers_;
    std::string response_body_;
    bool verify_certificate_;
    std::string cert_file_;
};
cpp 复制代码
// HttpsClient.cpp
#include "HttpsClient.hpp"
#include <iostream>
#include <sstream>

HttpsClient::HttpsClient(asio::io_context& io_context, bool verify_certificate)
    : io_context_(io_context)
    , ssl_context_(ssl::context::tls_client)
    , resolver_(io_context)
    , verify_certificate_(verify_certificate) {
    
    // 设置 SSL 选项
    if (verify_certificate) {
        ssl_context_.set_default_verify_paths();
        ssl_context_.set_verify_mode(ssl::verify_peer);
    } else {
        ssl_context_.set_verify_mode(ssl::verify_none);
    }
}

void HttpsClient::set_certificate_file(const std::string& cert_file) {
    cert_file_ = cert_file;
    ssl_context_.load_verify_file(cert_file);
    ssl_context_.set_verify_mode(ssl::verify_peer);
}

void HttpsClient::post(const std::string& host,
                       const std::string& port,
                       const std::string& path,
                       const std::string& data,
                       const std::map<std::string, std::string>& headers,
                       ResponseCallback response_callback,
                       ErrorCallback error_callback) {
    
    host_ = host;
    port_ = port;
    path_ = path;
    request_data_ = data;
    custom_headers_ = headers;
    response_callback_ = response_callback;
    error_callback_ = error_callback;
    
    // 创建 socket
    socket_ = std::make_unique<ssl::stream<asio::ip::tcp::socket>>(
        io_context_, ssl_context_);
    
    // 设置 SNI 主机名
    if (!SSL_set_tlsext_host_name(socket_->native_handle(), host.c_str())) {
        if (error_callback_) {
            error_callback_("Failed to set SNI hostname");
        }
        return;
    }
    
    // 开始异步解析
    resolver_.async_resolve(host, port,
        [this](const asio::error_code& ec,
               asio::ip::tcp::resolver::results_type endpoints) {
            on_resolve(ec, endpoints);
        });
}

void HttpsClient::on_resolve(const asio::error_code& ec,
                            asio::ip::tcp::resolver::results_type endpoints) {
    if (!ec) {
        // 异步连接
        asio::async_connect(socket_->lowest_layer(), endpoints,
            [this](const asio::error_code& ec,
                   const asio::ip::tcp::endpoint&) {
                on_connect(ec);
            });
    } else if (error_callback_) {
        error_callback_("Resolve error: " + ec.message());
    }
}

void HttpsClient::on_connect(const asio::error_code& ec) {
    if (!ec) {
        // 异步 SSL 握手
        socket_->async_handshake(ssl::stream_base::client,
            [this](const asio::error_code& ec) {
                on_handshake(ec);
            });
    } else if (error_callback_) {
        error_callback_("Connect error: " + ec.message());
    }
}

void HttpsClient::on_handshake(const asio::error_code& ec) {
    if (!ec) {
        // 构建 HTTP 请求
        std::ostream request_stream(&request_);
        request_stream << "POST " << path_ << " HTTP/1.1\r\n";
        request_stream << "Host: " << host_ << "\r\n";
        request_stream << "Content-Type: application/json\r\n";
        request_stream << "Content-Length: " << request_data_.length() << "\r\n";
        
        // 添加自定义头部
        for (const auto& header : custom_headers_) {
            request_stream << header.first << ": " << header.second << "\r\n";
        }
        
        request_stream << "Connection: close\r\n";
        request_stream << "\r\n";
        request_stream << request_data_;
        
        // 异步发送请求
        asio::async_write(*socket_, request_,
            [this](const asio::error_code& ec, size_t bytes_transferred) {
                on_write(ec, bytes_transferred);
            });
    } else if (error_callback_) {
        error_callback_("Handshake error: " + ec.message());
    }
}

void HttpsClient::on_write(const asio::error_code& ec, size_t bytes_transferred) {
    if (!ec) {
        // 异步读取状态行
        asio::async_read_until(*socket_, response_, "\r\n",
            [this](const asio::error_code& ec, size_t bytes_transferred) {
                on_read_status_line(ec, bytes_transferred);
            });
    } else if (error_callback_) {
        error_callback_("Write error: " + ec.message());
    }
}

void HttpsClient::on_read_status_line(const asio::error_code& ec,
                                     size_t bytes_transferred) {
    if (!ec) {
        std::istream response_stream(&response_);
        std::string http_version;
        response_stream >> http_version >> http_status_code_;
        
        std::string status_message;
        std::getline(response_stream, status_message);
        
        // 异步读取头部
        asio::async_read_until(*socket_, response_, "\r\n\r\n",
            [this](const asio::error_code& ec, size_t bytes_transferred) {
                on_read_headers(ec, bytes_transferred);
            });
    } else if (error_callback_) {
        error_callback_("Read status line error: " + ec.message());
    }
}

void HttpsClient::on_read_headers(const asio::error_code& ec,
                                 size_t bytes_transferred) {
    if (!ec) {
        std::istream response_stream(&response_);
        std::string header;
        
        while (std::getline(response_stream, header) && header != "\r") {
            response_headers_ += header + "\n";
        }
        
        // 如果响应体中还有数据,先读取
        if (response_.size() > 0) {
            std::ostringstream oss;
            oss << &response_;
            response_body_ += oss.str();
        }
        
        // 继续读取响应体
        asio::async_read(*socket_, response_,
            asio::transfer_at_least(1),
            [this](const asio::error_code& ec, size_t bytes_transferred) {
                on_read_body(ec, bytes_transferred);
            });
    } else if (error_callback_) {
        error_callback_("Read headers error: " + ec.message());
    }
}

void HttpsClient::on_read_body(const asio::error_code& ec,
                              size_t bytes_transferred) {
    if (!ec) {
        // 将缓冲区数据添加到响应体
        std::ostringstream oss;
        oss << &response_;
        response_body_ += oss.str();
        
        // 继续读取
        asio::async_read(*socket_, response_,
            asio::transfer_at_least(1),
            [this](const asio::error_code& ec, size_t bytes_transferred) {
                on_read_body(ec, bytes_transferred);
            });
    } else if (ec == asio::error::eof) {
        // 读取完成
        if (response_callback_) {
            response_callback_(http_status_code_, 
                              response_headers_, 
                              response_body_);
        }
        
        // 关闭连接
        asio::error_code ignored_ec;
        socket_->shutdown(ignored_ec);
    } else if (error_callback_) {
        error_callback_("Read body error: " + ec.message());
    }
}

2.2 主程序示例

cpp 复制代码
// Main.cpp
#include "HttpsClient.hpp"
#include <iostream>
#include <asio.hpp>

// 同步版本示例
void synchronous_https_post() {
    try {
        asio::io_context io_context;
        
        // 创建 SSL 上下文
        ssl::context ctx(ssl::context::tls_client);
        
        // 对于测试,可以禁用证书验证
        ctx.set_verify_mode(ssl::verify_none);
        
        // 对于生产环境,应该启用证书验证
        // ctx.set_default_verify_paths();
        // ctx.set_verify_mode(ssl::verify_peer);
        
        // 创建 SSL 流
        ssl::stream<asio::ip::tcp::socket> socket(io_context, ctx);
        
        // 设置 SNI
        SSL_set_tlsext_host_name(socket.native_handle(), "httpbin.org");
        
        // 解析和连接
        asio::ip::tcp::resolver resolver(io_context);
        auto endpoints = resolver.resolve("httpbin.org", "443");
        asio::connect(socket.next_layer(), endpoints);
        
        // SSL 握手
        socket.handshake(ssl::stream_base::client);
        
        // 发送 HTTP 请求
        std::string request = 
            "POST /post HTTP/1.1\r\n"
            "Host: httpbin.org\r\n"
            "Content-Type: application/json\r\n"
            "Content-Length: 29\r\n"
            "User-Agent: AsioHttpsClient/1.0\r\n"
            "Accept: application/json\r\n"
            "Connection: close\r\n"
            "\r\n"
            R"({"name": "test", "value": 123})";
        
        asio::write(socket, asio::buffer(request));
        
        // 读取响应
        asio::streambuf response;
        asio::read_until(socket, response, "\r\n");
        
        // 解析状态行
        std::istream response_stream(&response);
        std::string http_version;
        unsigned int status_code;
        std::string status_message;
        
        response_stream >> http_version >> status_code;
        std::getline(response_stream, status_message);
        
        std::cout << "Status: " << status_code << " " << status_message << "\n";
        
        // 读取头部
        asio::read_until(socket, response, "\r\n\r\n");
        
        std::string header;
        while (std::getline(response_stream, header) && header != "\r") {
            std::cout << header << "\n";
        }
        
        // 读取响应体
        asio::error_code ec;
        while (asio::read(socket, response, asio::transfer_at_least(1), ec)) {
            std::cout << &response;
        }
        
        std::cout << "\n";
        
        // 关闭连接
        socket.shutdown();
        
    } catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
    }
}

// 异步版本示例
void asynchronous_https_post() {
    asio::io_context io_context;
    
    HttpsClient client(io_context, false); // 禁用证书验证用于测试
    
    std::cout << "Sending asynchronous HTTPS POST request...\n";
    
    client.post(
        "httpbin.org",          // 主机
        "443",                  // 端口
        "/post",                // 路径
        R"({"name": "async_test", "value": 456})", // 数据
        {                       // 自定义头部
            {"User-Agent", "AsioHttpsClient/2.0"},
            {"X-Custom-Header", "CustomValue"}
        },
        // 响应回调
        [](int status, const std::string& headers, const std::string& body) {
            std::cout << "\n=== Response ===\n";
            std::cout << "Status: " << status << "\n";
            std::cout << "Headers:\n" << headers;
            std::cout << "Body:\n" << body << "\n";
        },
        // 错误回调
        [](const std::string& error) {
            std::cerr << "Error: " << error << "\n";
        }
    );
    
    // 运行 IO 上下文
    io_context.run();
}

int main() {
    std::cout << "1. Synchronous HTTPS POST:\n";
    synchronous_https_post();
    
    std::cout << "\n2. Asynchronous HTTPS POST:\n";
    asynchronous_https_post();
    
    return 0;
}

第三部分:高级主题

3.1 处理 SSL 证书验证

cpp 复制代码
// 证书验证回调示例
bool certificate_verification_callback(bool preverified,
                                      ssl::verify_context& ctx) {
    // 可以在这里添加自定义的证书验证逻辑
    
    char subject_name[256];
    X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
    X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
    
    std::cout << "Verifying certificate for: " << subject_name << "\n";
    
    // 示例:检查证书是否自签名
    if (X509_check_issued(cert, cert) == X509_V_OK) {
        std::cout << "Certificate is self-signed\n";
    }
    
    return preverified; // 返回 true 表示验证通过
}

// 在 SSL 上下文中使用
ssl::context ctx(ssl::context::tls_client);
ctx.set_verify_mode(ssl::verify_peer);
ctx.set_verify_callback(certificate_verification_callback);

3.2 超时处理

cpp 复制代码
class HttpsClientWithTimeout : public HttpsClient {
public:
    HttpsClientWithTimeout(asio::io_context& io_context,
                          bool verify_certificate = true,
                          int timeout_seconds = 30)
        : HttpsClient(io_context, verify_certificate)
        , timeout_seconds_(timeout_seconds)
        , timer_(io_context) {}
    
    void post(const std::string& host,
              const std::string& port,
              const std::string& path,
              const std::string& data,
              const std::map<std::string, std::string>& headers = {},
              ResponseCallback response_callback = nullptr,
              ErrorCallback error_callback = nullptr) {
        
        // 设置超时定时器
        timer_.expires_after(std::chrono::seconds(timeout_seconds_));
        timer_.async_wait([this, error_callback](const asio::error_code& ec) {
            if (!ec) {
                // 超时发生
                if (socket_) {
                    asio::error_code ignored_ec;
                    socket_->lowest_layer().close(ignored_ec);
                }
                if (error_callback) {
                    error_callback("Request timeout");
                }
            }
        });
        
        // 调用基类的 post 方法
        HttpsClient::post(host, port, path, data, headers,
                         response_callback,
                         error_callback);
    }
    
private:
    int timeout_seconds_;
    asio::steady_timer timer_;
};

3.3 连接池和连接复用

cpp 复制代码
class HttpsConnectionPool {
public:
    HttpsConnectionPool(asio::io_context& io_context,
                       size_t pool_size,
                       const std::string& host,
                       const std::string& port,
                       bool verify_certificate = true)
        : io_context_(io_context)
        , host_(host)
        , port_(port)
        , verify_certificate_(verify_certificate) {
        
        // 初始化连接池
        for (size_t i = 0; i < pool_size; ++i) {
            available_connections_.push_back(
                std::make_shared<HttpsConnection>(io_context_, 
                                                 verify_certificate_));
        }
    }
    
    std::shared_ptr<HttpsConnection> acquire() {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (available_connections_.empty()) {
            // 如果池为空,创建新连接
            return std::make_shared<HttpsConnection>(io_context_,
                                                    verify_certificate_);
        }
        
        auto connection = available_connections_.back();
        available_connections_.pop_back();
        return connection;
    }
    
    void release(std::shared_ptr<HttpsConnection> connection) {
        std::lock_guard<std::mutex> lock(mutex_);
        available_connections_.push_back(connection);
    }
    
private:
    asio::io_context& io_context_;
    std::string host_;
    std::string port_;
    bool verify_certificate_;
    
    std::vector<std::shared_ptr<HttpsConnection>> available_connections_;
    std::mutex mutex_;
};

class HttpsConnection {
    // 连接封装类,支持连接复用
};

第四部分:常见问题解决

4.1 OpenSSL 安装问题

问题: "无法打开包括文件: 'openssl/conf.h'"
解决方案:

  1. 确保 OpenSSL 正确安装
  2. 在项目设置中添加正确的包含目录
  3. 检查 OpenSSL 版本兼容性

4.2 SSL 证书验证失败

问题: "certificate verify failed"
解决方案:

  1. 对于测试环境:禁用证书验证
  2. 对于生产环境:
    • 使用 set_default_verify_paths() 加载系统证书
    • 或使用 load_verify_file() 加载自定义证书
    • curl CA 证书 下载证书包

4.3 内存泄漏问题

问题: Asio 和 OpenSSL 内存泄漏
解决方案:

  1. 确保正确关闭 SSL 连接
  2. 使用智能指针管理资源
  3. 在程序退出时调用 OpenSSL 清理函数
cpp 复制代码
// 程序初始化
void initialize_openssl() {
    OPENSSL_init_ssl(0, nullptr);
}

// 程序清理
void cleanup_openssl() {
    EVP_cleanup();
}

测试程序

cpp 复制代码
// Main.cpp - 简化版HTTPS客户端

#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00//0x0601
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#endif

#include <asio.hpp>
#include <asio/ssl.hpp>
#include <iostream>
#include <string>
#include <sstream>

using asio::ip::tcp;
namespace ssl = asio::ssl;

std::string create_https_post_request(const std::string& host,
    const std::string& path,
    const std::string& data) {
    std::ostringstream request;
    request << "POST " << path << " HTTP/1.1\r\n";
    request << "Host: " << host << "\r\n";
    request << "Content-Type: application/json\r\n";
    request << "Content-Length: " << data.length() << "\r\n";
    request << "Accept: application/json\r\n";
    request << "User-Agent: AsioClient/1.0\r\n";
    request << "Connection: close\r\n";
    request << "\r\n";
    request << data;

    return request.str();
}

// SSL证书验证回调(总是返回true)
bool verify_certificate(bool preverified, ssl::verify_context& ctx) {
    // 不进行证书验证,总是返回true
    return true;
}

int main() {
    try {
        // URL信息
        std::string host = "example.com";
        std::string path = "/api/check";
        int port = 443;

        // POST 数据
        std::string post_data = R"({"action": "check", "value": 123})";

        // 创建IO上下文和SSL上下文
        asio::io_context io_context;
        ssl::context ssl_context(ssl::context::tls_client);

        // 禁用证书验证 - 关键修改
        ssl_context.set_verify_mode(ssl::verify_none);

        // 或者使用自定义验证回调
        // ssl_context.set_verify_mode(ssl::verify_peer);
        // ssl_context.set_verify_callback(verify_certificate);

        // 创建SSL流
        ssl::stream<tcp::socket> ssl_stream(io_context, ssl_context);

        // 设置SNI主机名
        if (!SSL_set_tlsext_host_name(ssl_stream.native_handle(), host.c_str())) {
            asio::error_code ec{ static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category() };
            throw asio::system_error(ec);
        }

        // 解析主机名
        tcp::resolver resolver(io_context);
        auto endpoints = resolver.resolve(host, std::to_string(port));

        // 连接
        asio::connect(ssl_stream.lowest_layer(), endpoints);

        // SSL握手
        ssl_stream.handshake(ssl::stream_base::client);

        std::cout << "SSL handshake successful!" << std::endl;

        // 创建HTTP请求
        std::string request = create_https_post_request(host, path, post_data);

        // 发送请求
        asio::write(ssl_stream, asio::buffer(request));

        // 读取响应
        asio::streambuf response;
        asio::read_until(ssl_stream, response, "\r\n");

        // 检查响应状态
        std::istream response_stream(&response);
        std::string http_version;
        unsigned int status_code;
        std::string status_message;

        response_stream >> http_version >> status_code;
        std::getline(response_stream, status_message);

        std::cout << "Response: " << http_version << " " << status_code
            << " " << status_message << std::endl;

        // 读取响应头
        asio::read_until(ssl_stream, response, "\r\n\r\n");

        std::cout << "Headers:" << std::endl;
        std::string header;
        while (std::getline(response_stream, header) && header != "\r") {
            std::cout << header << std::endl;
        }

        // 读取响应体
        std::cout << "\nResponse body:" << std::endl;

        if (response.size() > 0) {
            std::cout << &response;
        }

        asio::error_code error;
        while (asio::read(ssl_stream, response,
            asio::transfer_at_least(1), error)) {
            std::cout << &response;
        }

        if (error != asio::error::eof) {
            std::cout << "Read error: " << error.message() << std::endl;
        }

        std::cout << std::endl;

        // 关闭连接
        ssl_stream.shutdown();

    }
    catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;

        // 输出详细的错误信息
        asio::error_code ec;
        if ((ec = asio::error_code(::ERR_get_error(),
            asio::error::get_ssl_category()))) {
            std::cerr << "SSL Error: " << ec.message() << std::endl;
        }

        return 1;
    }

    return 0;
}

结果

结论

使用独立 Asio 和 OpenSSL 实现 HTTPS 通信需要正确处理 SSL/TLS 握手、证书验证和异步 I/O 操作。本文提供了从环境配置到高级功能的完整解决方案,包括:

  1. 环境搭建:OpenSSL 安装和 Asio 配置
  2. 基础实现:同步和异步 HTTPS POST 请求
  3. 高级功能:证书验证、超时处理、连接池
  4. 问题解决:常见错误和解决方案

通过本文的指南,你应该能够在项目中成功实现 HTTPS 通信,并根据需求进行定制扩展。记住,在生产环境中始终启用证书验证以确保通信安全。

相关推荐
三两肉2 小时前
HTTPS 优化完整方案解析
网络协议·https·tcl
weixin_462446232 小时前
mkcert 本地 HTTPS 证书全平台教程
网络协议·http·https
华纳云IDC服务商2 小时前
HTTPS是否能防止网站被劫持?
网络协议·http·https
唐古乌梁海2 小时前
HTTP/HTTPS 协议基础详解
网络协议·http·https
小豪GO!2 小时前
TCP八股
网络·网络协议·tcp/ip
m0_748245922 小时前
HTTP 协议概述
网络·网络协议·http
00后程序员张3 小时前
iOS APP 性能测试工具,监控CPU,实时日志输出
android·ios·小程序·https·uni-app·iphone·webview
小码吃趴菜3 小时前
http实现服务器与浏览器通信
网络·网络协议·http
汤愈韬3 小时前
防火墙双机热备01(主备模式)
网络·网络协议·网络安全·security·huawei