下面我将为你提供一个完整的、分步的指南,教你如何在本地搭建一个网页,并通过 HP-Socket 与之交互,我们将创建一个最简单的例子:网页向 HP-Socket 服务器发送一条消息,服务器收到后回复“Hello from HP-Socket!”

hpsocket在本地搭建网页
(图片来源网络,侵删)

整体架构

这个项目的架构非常清晰:

+----------------+      HTTP/JSON      +---------------------+
|  本地浏览器     | <------------------> |   HP-Socket 服务器  |
| (index.html)   |                     |   (C++ 应用程序)    |
+----------------+                     +----------+----------+
                                              |
                                       TCP/IP
                                              |
+----------------+                           |
|  开发机/本机    | <-------------------------+
+----------------+
  1. 前端: 一个简单的 HTML 文件,包含一个输入框、一个按钮和用于显示结果的区域,它使用 JavaScript 的 fetch API 向本机的一个特定端口发送 HTTP 请求。
  2. 后端: 一个使用 HP-Socket 库编写的 C++ 控制台应用程序,它会监听一个 TCP 端口,接收来自前端的 HTTP 请求,解析后,再通过 HTTP 响应将数据返回给前端。

第一步:准备工作

在开始之前,请确保你已经安装了以下工具:

  1. C++ 编译器: 如 Visual Studio (推荐) 或 MinGW。
  2. HP-Socket 库: 你需要从 HP-Socket 官网 下载并解压,我们使用 HP-Socket-*.zip 版本即可。
  3. 一个本地 Web 服务器: 这是最关键的一步!
    • 为什么需要? 出于安全原因,现代浏览器禁止网页直接通过 file:// 协议加载时向任意地址发起网络请求(即 "跨域限制" 或 CORS),你必须通过一个 Web 服务器来加载你的 HTML 文件。
    • 如何搭建?
      • 最简单方法: 如果你使用 VS Code,安装 Live Server 扩展,右键你的 HTML 文件,选择 "Open with Live Server",它会自动启动一个本地服务器,并在浏览器中打开你的页面。
      • 其他方法: 使用 Python (自带简单服务器)、Node.js 的 http-server 等。

第二步:创建前端网页 (HTML + JavaScript)

在你的项目文件夹中创建一个名为 index.html 的文件,这个文件包含了用户界面和与后端通信的逻辑。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">HP-Socket 网页交互示例</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 2em; }
        input, button { padding: 10px; font-size: 16px; }
        #result { margin-top: 20px; padding: 10px; border: 1px solid #ccc; background-color: #f9f9f9; }
    </style>
</head>
<body>
    <h1>与 HP-Socket 服务器通信</h1>
    <p>点击下面的按钮,向服务器发送一条 "ping" 消息。</p>
    <button id="sendButton">发送消息</button>
    <div id="result">等待响应...</div>
    <script>
        // 当页面加载完成后执行
        document.addEventListener('DOMContentLoaded', function() {
            const sendButton = document.getElementById('sendButton');
            const resultDiv = document.getElementById('result');
            // 为按钮添加点击事件监听器
            sendButton.addEventListener('click', function() {
                // HP-Socket 服务器监听的地址和端口
                const serverUrl = 'http://127.0.0.1:45321'; 
                // 使用 fetch API 发送 POST 请求
                fetch(serverUrl, {
                    method: 'POST', // 使用 POST 方法
                    headers: {
                        'Content-Type': 'application/json', // 告诉服务器我们发送的是 JSON
                    },
                    body: JSON.stringify({ // 将 JavaScript 对象转换为 JSON 字符串
                        action: 'ping',
                        message: 'Hello from Web Page!'
                    })
                })
                .then(response => {
                    // 检查响应是否成功
                    if (!response.ok) {
                        throw new Error('网络响应不正常');
                    }
                    // 解析 JSON 格式的响应体
                    return response.json();
                })
                .then(data => {
                    // 在页面上显示服务器返回的数据
                    resultDiv.textContent = `服务器回复: ${data.message} (时间: ${data.timestamp})`;
                })
                .catch(error => {
                    // 捕获并显示错误
                    resultDiv.textContent = '发生错误: ' + error.message;
                    console.error('Error:', error);
                });
            });
        });
    </script>
</body>
</html>

代码解释:

hpsocket在本地搭建网页
(图片来源网络,侵删)
  • 我们创建了一个按钮和一个用于显示结果的 <div>
  • 当按钮被点击时,JavaScript 代码会执行 fetch 请求。
  • 请求的目标是 http://127.0.0.1:45321这是我们的 HP-Socket 服务器将要运行的地址和端口
  • 我们发送了一个 JSON 对象 { action: 'ping', message: '...' }
  • 我们期望服务器也返回一个 JSON 对象 { message: '...', timestamp: '...' }

第三步:创建后端 HP-Socket 服务器

现在我们来创建 C++ 服务器,我们将使用 HP-Socket 的 HttpClient 组件,因为它能非常方便地处理 HTTP 协议,让我们不用自己解析和构造 HTTP 头。

  1. 创建 Visual Studio 项目:

    • 打开 Visual Studio,创建一个新的 "Windows 桌面应用程序" 项目(C++)。
    • 给项目命名,HP_Socket_Web_Server
  2. 配置项目:

    • 包含目录: 右键项目 -> 属性 -> C/C++ -> 常规 -> 附加包含目录,添加你解压的 HP-Socket 文件夹中的 include 目录路径。
    • 库目录: 右键项目 -> 属性 -> 链接器 -> 常规 -> 附加库目录,添加 HP-Socket 文件夹中的 lib 目录路径。
    • 依赖项: 右键项目 -> 属性 -> 链接器 -> 输入 -> 附加依赖项,添加 HP-Socket.lib
    • 运行库: 确保你的项目运行时库(如 /MD/MDd)与 HP-Socket 库编译时使用的运行时库一致,通常使用 /MD (多线程 DLL) 是最稳妥的。
  3. 编写服务器代码: 将以下代码替换你项目中的 main.cppHP_Socket_Web_Server.cpp 文件。

    hpsocket在本地搭建网页
    (图片来源网络,侵删)
#include <iostream>
#include <string>
#include <sstream>
#include <chrono>
#include <ctime>
#include <json/json.h> // 需要安装 jsoncpp 库
// 引入 HP-Socket 头文件
#include "HP-Socket/HP-Socket.h"
// 定义服务器监听的端口
#define SERVER_PORT 45321
// 自定义的 HTTP 上下文,用于保存客户端连接信息
struct HttpContext {
    TcpAgentPtr agent; // HP-Socket 的客户端连接句柄
};
// 自定义的 HTTP 服务类,继承自 HP-Socket 的 HttpClientService
class MyHttpService : public HttpClientService {
public:
    // 当一个新的 HTTP 请求到达时被调用
    virtual EnHandleResult OnHttpClientAccept(EnContextEvents event, TcpAgentPtr agent, const SOCKET soClient) {
        if (event == HE_ACCEPT) {
            // 为新连接创建一个上下文
            HttpContext* pContext = new HttpContext();
            pContext->agent = agent;
            // 将上下文与连接关联起来
            HP_SetContext(agent, pContext);
            std::cout << "客户端连接: " << soClient << std::endl;
        }
        return HR_OK;
    }
    // 当 HTTP 请求体接收完成时被调用
    virtual EnHandleResult OnHttpClientReceive(EnContextEvents event, TcpAgentPtr agent) {
        if (event == HE_RECEIVE) {
            // 获取与该连接关联的上下文
            HttpContext* pContext = (HttpContext*)HP_GetContext(agent);
            if (!pContext) return HR_ERROR;
            // 获取接收到的请求数据
            const BYTE* pBuffer = HP_GetBuffer(agent);
            int iLength = HP_GetLength(agent);
            // 将请求数据转换为字符串
            std::string requestStr((char*)pBuffer, iLength);
            std::cout << "收到请求:\n" << requestStr << std::endl;
            // 解析 JSON 请求
            Json::CharReaderBuilder builder;
            Json::CharReader* reader = builder.newCharReader();
            Json::Value root;
            std::string errs;
            bool parsingSuccessful = reader->parse(requestStr.c_str(), requestStr.c_str() + requestStr.size(), &root, &errs);
            delete reader;
            std::string responseJson;
            if (parsingSuccessful) {
                std::string action = root["action"].asString();
                if (action == "ping") {
                    // 构造 JSON 响应
                    Json::Value response;
                    response["message"] = "Hello from HP-Socket!";
                    response["timestamp"] = GetCurrentTimestamp();
                    Json::StreamWriterBuilder wbuilder;
                    responseJson = Json::writeString(wbuilder, response);
                } else {
                    responseJson = R"({"error": "Unknown action"})";
                }
            } else {
                responseJson = R"({"error": "Invalid JSON format"})";
            }
            // 构造 HTTP 响应
            std::string httpResponse = "HTTP/1.1 200 OK\r\n";
            httpResponse += "Content-Type: application/json\r\n";
            httpResponse += "Connection: close\r\n";
            httpResponse += "Content-Length: " + std::to_string(responseJson.length()) + "\r\n";
            httpResponse += "\r\n"; // 空行,分隔头部和 body
            httpResponse += responseJson;
            // 发送响应
            HP_Send(pContext->agent, (BYTE*)httpResponse.c_str(), (int)httpResponse.length());
            // 关闭连接
            HP_CloseAgent(agent);
            // 清理上下文
            delete pContext;
        }
        return HR_OK;
    }
    // 当连接关闭时被调用 (可选,但推荐用于清理)
    virtual EnHandleResult OnHttpClientClose(EnContextEvents event, TcpAgentPtr agent, EnSocketOperation enOperation, int iErrorCode) {
        if (event == HE_CLOSE) {
            HttpContext* pContext = (HttpContext*)HP_GetContext(agent);
            if (pContext) {
                std::cout << "客户端断开连接" << std::endl;
                delete pContext; // 确保内存被释放
                HP_SetContext(agent, NULL);
            }
        }
        return HR_OK;
    }
private:
    // 获取当前时间戳的辅助函数
    std::string GetCurrentTimestamp() {
        auto now = std::chrono::system_clock::now();
        std::time_t now_time = std::chrono::system_clock::to_time_t(now);
        std::tm now_tm;
        localtime_s(&now_tm, &now_time); // 使用 localtime_s 是更安全的做法
        char buffer[80];
        std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &now_tm);
        return std::string(buffer);
    }
};
int main() {
    // 创建 HTTP 服务实例
    MyHttpService service;
    // 创建 HttpClient 组件
    HttpClientPtr pClient = HttpClient::Create();
    if (!pClient) {
        std::cerr << "创建 HttpClient 失败!" << std::endl;
        return 1;
    }
    // 设置组件属性
    pClient->EnableAddressCheck(FALSE); // 允许任意 IP 连接
    pClient->SetMaxConnectionCount(1024); // 最大连接数
    pClient->SetListenPort(SERVER_PORT);
    pClient->SetService(&service);
    // 启动组件
    if (!pClient->Start()) {
        std::cerr << "启动 HttpClient 失败! 错误码: " << pClient->GetLastError() << std::endl;
        return 1;
    }
    std::cout << "HP-Socket HTTP 服务器已启动,监听端口: " << SERVER_PORT << std::endl;
    std::cout << "请在浏览器中打开网页并与之交互..." << std::endl;
    // 主循环
    while (true) {
        Sleep(100); // 避免 CPU 占用过高
    }
    // 停止并释放组件 (在正常情况下,上面的 while 循环不会退出)
    pClient->Stop();
    HttpClient::Destroy(pClient);
    return 0;
}

代码解释:

  • 依赖: 这段代码需要 JsonCpp 库来解析和生成 JSON 字符串,你需要自行下载并配置 JsonCpp 到你的项目中。
  • MyHttpService: 我们继承 HttpClientService 并重写了几个关键方法:
    • OnHttpClientAccept: 当有新客户端连接时触发,我们在这里创建并关联了一个 HttpContext 结构体来保存连接信息。
    • OnHttpClientReceive: 这是核心方法,当 HP-Socket 收到一个完整的 HTTP 请求后,它会调用这个函数,我们在这里:
      1. 获取请求的原始数据。
      2. 使用 JsonCpp 解析 JSON 请求体。
      3. 根据请求的 action 构造一个 JSON 响应。
      4. 将 JSON 响应包装成标准的 HTTP 响应格式(包含状态码、Content-Type 等)。
      5. 使用 HP_Send 将响应发送回客户端。
      6. 调用 HP_CloseAgent 关闭连接。
  • main 函数: 初始化 HP-Socket 环境,创建并启动 HttpClient 组件,然后进入一个无限循环保持服务器运行。

第四步:编译、运行与测试

  1. 编译: 在 Visual Studio 中编译你的 C++ 项目,确保没有链接错误。
  2. 运行 HP-Socket 服务器:
    • 找到你编译生成的 .exe 文件(通常在 x64/Debugx64/Release 目录下)。
    • 双击运行它,你应该会在控制台看到 "HP-Socket HTTP 服务器已启动..." 的消息。
  3. 运行前端网页:
    • 打开你之前创建的 index.html 文件所在的文件夹。
    • 如果你使用的是 VS Code 的 Live Server,右键文件选择 "Open with Live Server",浏览器会自动打开 http://127.0.0.1:5500 (或类似地址)。
    • 重要: 网页通过 fetch 请求的是 http://127.0.0.1:45321,这是 HP-Socket 服务器的地址,不是 Live Server 的地址。
  4. 测试交互:
    • 在浏览器中,点击 "发送消息" 按钮。
    • 观察网页上的 "等待响应..." 区域是否变成了 "服务器回复: Hello from HP-Socket! (时间: ...)"。
    • 观察 HP-Socket 服务器的控制台窗口,你应该能看到打印出的原始 HTTP 请求内容。

如果一切顺利,恭喜你!你已经成功地在本地搭建了一个通过 HP-Socket 与网页交互的应用程序。

总结与扩展

  • 核心要点: 前端通过 Web 服务器加载,通过 fetch 等 API 与后端 HP-Socket 服务进行 HTTP 通信,HP-Socket 使用 HttpClient 组件来简化 HTTP 协议的处理。
  • 扩展方向:
    • WebSocket: 如果需要实现服务器主动推送数据(如聊天室、实时通知),可以考虑将 HP-Socket 的 WebSocket 组件与网页的 WebSocket API 结合使用,这比 HTTP 轮询高效得多。
    • 更复杂的协议: 你也可以定义自己的二进制或文本协议,使用 HP-Socket 的 TcpAgentUdpAgent 组件进行通信,但这需要前端和后端都实现协议的解析和构造逻辑。
    • 数据库集成: 在 HP-Socket 服务器的 OnHttpClientReceive 方法中,你可以连接数据库(如 SQLite, MySQL)进行增删改查操作,然后将结果返回给网页。