zh -> en
parent
5e9ff48b07
commit
7f9862f91c
162
README.md
162
README.md
|
@ -1,77 +1,77 @@
|
||||||
# MCP Protocol Framework
|
# MCP Protocol Framework
|
||||||
|
|
||||||
[Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/architecture/) 是一个开放协议,为AI模型和代理提供与各种资源、工具和服务交互的标准化方式。本框架实现了MCP协议的核心功能,符合2024-11-05基本协议规范。
|
[Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/architecture/) is an open protocol that provides a standardized way for AI models and agents to interact with various resources, tools, and services. This framework implements the core functionality of the MCP protocol, conforming to the 2024-11-05 basic protocol specification.
|
||||||
|
|
||||||
## 核心特性
|
## Core Features
|
||||||
|
|
||||||
- **JSON-RPC 2.0通信**: 基于JSON-RPC 2.0标准的请求/响应通信
|
- **JSON-RPC 2.0 Communication**: Request/response communication based on JSON-RPC 2.0 standard
|
||||||
- **资源抽象**: 文件、API等资源的标准接口
|
- **Resource Abstraction**: Standard interfaces for resources such as files, APIs, etc.
|
||||||
- **工具注册**: 注册和调用带有结构化参数的工具
|
- **Tool Registration**: Register and call tools with structured parameters
|
||||||
- **可扩展架构**: 易于扩展新的资源类型和工具
|
- **Extensible Architecture**: Easy to extend with new resource types and tools
|
||||||
- **多传输支持**: 支持HTTP和标准输入/输出(stdio)通信方式
|
- **Multi-Transport Support**: Supports HTTP and standard input/output (stdio) communication methods
|
||||||
|
|
||||||
## 组件
|
## Components
|
||||||
|
|
||||||
MCP C++库包含以下主要组件:
|
The MCP C++ library includes the following main components:
|
||||||
|
|
||||||
### 核心组件
|
### Core Components
|
||||||
|
|
||||||
#### 客户端接口 (`mcp_client.h`)
|
#### Client Interface (`mcp_client.h`)
|
||||||
定义了MCP客户端的抽象接口,所有具体的客户端实现都继承自这个接口。
|
Defines the abstract interface for MCP clients, which all concrete client implementations inherit from.
|
||||||
|
|
||||||
#### SSE客户端 (`mcp_sse_client.h`, `mcp_sse_client.cpp`)
|
#### SSE Client (`mcp_sse_client.h`, `mcp_sse_client.cpp`)
|
||||||
使用HTTP和Server-Sent Events (SSE)与MCP服务器通信的客户端实现。
|
Client implementation that communicates with MCP servers using HTTP and Server-Sent Events (SSE).
|
||||||
|
|
||||||
#### Stdio客户端 (`mcp_stdio_client.h`, `mcp_stdio_client.cpp`)
|
#### Stdio Client (`mcp_stdio_client.h`, `mcp_stdio_client.cpp`)
|
||||||
使用标准输入/输出与MCP服务器通信的客户端实现,可以启动子进程并与之通信。
|
Client implementation that communicates with MCP servers using standard input/output, capable of launching subprocesses and communicating with them.
|
||||||
|
|
||||||
#### 消息处理 (`mcp_message.h`, `mcp_message.cpp`)
|
#### Message Processing (`mcp_message.h`, `mcp_message.cpp`)
|
||||||
处理JSON-RPC消息的序列化和反序列化。
|
Handles serialization and deserialization of JSON-RPC messages.
|
||||||
|
|
||||||
#### 工具管理 (`mcp_tool.h`, `mcp_tool.cpp`)
|
#### Tool Management (`mcp_tool.h`, `mcp_tool.cpp`)
|
||||||
管理和调用MCP工具。
|
Manages and invokes MCP tools.
|
||||||
|
|
||||||
#### 资源管理 (`mcp_resource.h`, `mcp_resource.cpp`)
|
#### Resource Management (`mcp_resource.h`, `mcp_resource.cpp`)
|
||||||
管理MCP资源。
|
Manages MCP resources.
|
||||||
|
|
||||||
#### 服务器 (`mcp_server.h`, `mcp_server.cpp`)
|
#### Server (`mcp_server.h`, `mcp_server.cpp`)
|
||||||
实现MCP服务器功能。
|
Implements MCP server functionality.
|
||||||
|
|
||||||
## 示例
|
## Examples
|
||||||
|
|
||||||
### HTTP服务器示例 (`examples/server_example.cpp`)
|
### HTTP Server Example (`examples/server_example.cpp`)
|
||||||
|
|
||||||
MCP服务器实现示例,带有自定义工具:
|
Example MCP server implementation with custom tools:
|
||||||
- 时间工具: 获取当前时间
|
- Time tool: Get the current time
|
||||||
- 计算器工具: 执行数学运算
|
- Calculator tool: Perform mathematical operations
|
||||||
- 回显工具: 处理和分析文本
|
- Echo tool: Process and analyze text
|
||||||
- 招呼工具:返回`Hello, `+传入名字+`!`,默认返回`Hello, World!`
|
- Greeting tool: Returns `Hello, `+input name+`!`, defaults to `Hello, World!`
|
||||||
|
|
||||||
### HTTP客户端示例 (`examples/client_example.cpp`)
|
### HTTP Client Example (`examples/client_example.cpp`)
|
||||||
|
|
||||||
连接到服务器的MCP客户端示例:
|
Example MCP client connecting to a server:
|
||||||
- 获取服务器信息
|
- Get server information
|
||||||
- 列出可用工具
|
- List available tools
|
||||||
- 使用参数调用工具
|
- Call tools with parameters
|
||||||
- 访问资源
|
- Access resources
|
||||||
|
|
||||||
### Stdio客户端示例 (`examples/stdio_client_example.cpp`)
|
### Stdio Client Example (`examples/stdio_client_example.cpp`)
|
||||||
|
|
||||||
展示如何使用stdio客户端与本地服务器通信:
|
Demonstrates how to use the stdio client to communicate with a local server:
|
||||||
- 启动本地服务器进程
|
- Launch a local server process
|
||||||
- 访问文件系统资源
|
- Access filesystem resources
|
||||||
- 调用服务器工具
|
- Call server tools
|
||||||
|
|
||||||
## 如何使用
|
## How to Use
|
||||||
|
|
||||||
### 设置HTTP服务器
|
### Setting up an HTTP Server
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 创建并配置服务器
|
// Create and configure the server
|
||||||
mcp::server server("localhost", 8080);
|
mcp::server server("localhost", 8080);
|
||||||
server.set_server_info("MCP Example Server", "2024-11-05");
|
server.set_server_info("MCP Example Server", "2024-11-05");
|
||||||
|
|
||||||
// 注册工具
|
// Register tools
|
||||||
mcp::json hello_handler(const mcp::json& params) {
|
mcp::json hello_handler(const mcp::json& params) {
|
||||||
std::string name = params.contains("name") ? params["name"].get<std::string>() : "World";
|
std::string name = params.contains("name") ? params["name"].get<std::string>() : "World";
|
||||||
return {
|
return {
|
||||||
|
@ -89,24 +89,24 @@ mcp::tool hello_tool = mcp::tool_builder("hello")
|
||||||
|
|
||||||
server.register_tool(hello_tool, hello_handler);
|
server.register_tool(hello_tool, hello_handler);
|
||||||
|
|
||||||
// 注册资源
|
// Register resources
|
||||||
auto file_resource = std::make_shared<mcp::file_resource>("<file_path>");
|
auto file_resource = std::make_shared<mcp::file_resource>("<file_path>");
|
||||||
server.register_resource("file://<file_path>", file_resource);
|
server.register_resource("file://<file_path>", file_resource);
|
||||||
|
|
||||||
// 启动服务器
|
// Start the server
|
||||||
server.start(true); // 阻塞模式
|
server.start(true); // Blocking mode
|
||||||
```
|
```
|
||||||
|
|
||||||
### 创建HTTP客户端
|
### Creating an HTTP Client
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 连接到服务器
|
// Connect to the server
|
||||||
mcp::sse_client client("localhost", 8080);
|
mcp::sse_client client("localhost", 8080);
|
||||||
|
|
||||||
// 初始化连接
|
// Initialize the connection
|
||||||
client.initialize("My Client", "1.0.0");
|
client.initialize("My Client", "1.0.0");
|
||||||
|
|
||||||
// 调用工具
|
// Call a tool
|
||||||
mcp::json params = {
|
mcp::json params = {
|
||||||
{"name", "Client"}
|
{"name", "Client"}
|
||||||
};
|
};
|
||||||
|
@ -114,83 +114,83 @@ mcp::json params = {
|
||||||
mcp::json result = client.call_tool("hello", params);
|
mcp::json result = client.call_tool("hello", params);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用SSE客户端
|
### Using the SSE Client
|
||||||
|
|
||||||
SSE客户端使用HTTP和Server-Sent Events (SSE) 与MCP服务器通信。这是一种基于Web标准的通信方式,适合与支持HTTP/SSE的服务器通信。
|
The SSE client uses HTTP and Server-Sent Events (SSE) to communicate with MCP servers. This is a communication method based on Web standards, suitable for communicating with servers that support HTTP/SSE.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include "mcp_sse_client.h"
|
#include "mcp_sse_client.h"
|
||||||
|
|
||||||
// 创建客户端,指定服务器地址和端口
|
// Create a client, specifying the server address and port
|
||||||
mcp::sse_client client("localhost", 8080);
|
mcp::sse_client client("localhost", 8080);
|
||||||
// 或者使用基础URL
|
// Or use a base URL
|
||||||
// mcp::sse_client client("http://localhost:8080");
|
// mcp::sse_client client("http://localhost:8080");
|
||||||
|
|
||||||
// 设置认证令牌(如果需要)
|
// Set an authentication token (if needed)
|
||||||
client.set_auth_token("your_auth_token");
|
client.set_auth_token("your_auth_token");
|
||||||
|
|
||||||
// 设置自定义请求头(如果需要)
|
// Set custom request headers (if needed)
|
||||||
client.set_header("X-Custom-Header", "value");
|
client.set_header("X-Custom-Header", "value");
|
||||||
|
|
||||||
// 初始化客户端
|
// Initialize the client
|
||||||
if (!client.initialize("My Client", "1.0.0")) {
|
if (!client.initialize("My Client", "1.0.0")) {
|
||||||
// 初始化失败处理
|
// Handle initialization failure
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用工具
|
// Call a tool
|
||||||
json result = client.call_tool("tool_name", {
|
json result = client.call_tool("tool_name", {
|
||||||
{"param1", "value1"},
|
{"param1", "value1"},
|
||||||
{"param2", 42}
|
{"param2", 42}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用Stdio客户端
|
### Using the Stdio Client
|
||||||
|
|
||||||
Stdio客户端可以与任何支持stdio传输的MCP服务器进行通信,例如:
|
The Stdio client can communicate with any MCP server that supports stdio transport, such as:
|
||||||
|
|
||||||
- @modelcontextprotocol/server-everything - 示例服务器
|
- @modelcontextprotocol/server-everything - Example server
|
||||||
- @modelcontextprotocol/server-filesystem - 文件系统服务器
|
- @modelcontextprotocol/server-filesystem - Filesystem server
|
||||||
- 其他支持stdio传输的[MCP服务器](https://www.pulsemcp.com/servers)
|
- Other [MCP servers](https://www.pulsemcp.com/servers) that support stdio transport
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include "mcp_stdio_client.h"
|
#include "mcp_stdio_client.h"
|
||||||
|
|
||||||
// 创建客户端,指定服务器命令
|
// Create a client, specifying the server command
|
||||||
mcp::stdio_client client("npx -y @modelcontextprotocol/server-everything");
|
mcp::stdio_client client("npx -y @modelcontextprotocol/server-everything");
|
||||||
// mcp::stdio_client client("npx -y @modelcontextprotocol/server-filesystem /path/to/directory");
|
// mcp::stdio_client client("npx -y @modelcontextprotocol/server-filesystem /path/to/directory");
|
||||||
|
|
||||||
// 初始化客户端
|
// Initialize the client
|
||||||
if (!client.initialize("My Client", "1.0.0")) {
|
if (!client.initialize("My Client", "1.0.0")) {
|
||||||
// 初始化失败处理
|
// Handle initialization failure
|
||||||
}
|
}
|
||||||
|
|
||||||
// 访问资源
|
// Access resources
|
||||||
json resources = client.list_resources();
|
json resources = client.list_resources();
|
||||||
json content = client.read_resource("resource://uri");
|
json content = client.read_resource("resource://uri");
|
||||||
|
|
||||||
// 调用工具
|
// Call a tool
|
||||||
json result = client.call_tool("tool_name", {
|
json result = client.call_tool("tool_name", {
|
||||||
{"param1", "value1"},
|
{"param1", "value1"},
|
||||||
{"param2", "value2"}
|
{"param2", "value2"}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## 构建框架
|
## Building the Framework
|
||||||
|
|
||||||
框架依赖以下库:
|
The framework depends on the following libraries:
|
||||||
- httplib.h - HTTP服务器和客户端
|
- httplib.h - HTTP server and client
|
||||||
- json.hpp - JSON解析和生成
|
- json.hpp - JSON parsing and generation
|
||||||
- gtest - 测试
|
- gtest - Testing
|
||||||
|
|
||||||
所有依赖项都包含在仓库中。
|
All dependencies are included in the repository.
|
||||||
|
|
||||||
使用CMake构建示例:
|
Example of building with CMake:
|
||||||
```bash
|
```bash
|
||||||
cmake -B build
|
cmake -B build
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 许可证
|
## License
|
||||||
|
|
||||||
本框架根据MIT许可证提供。有关详细信息,请参阅LICENSE文件。
|
This framework is provided under the MIT license. For details, please see the LICENSE file.
|
|
@ -18,7 +18,7 @@ add_library(${TARGET} STATIC
|
||||||
|
|
||||||
target_link_libraries(${TARGET} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
target_link_libraries(${TARGET} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
|
||||||
# 如果找到OpenSSL,链接OpenSSL库
|
# If OpenSSL is found, link the OpenSSL libraries
|
||||||
if(OPENSSL_FOUND)
|
if(OPENSSL_FOUND)
|
||||||
target_link_libraries(${TARGET} PUBLIC ${OPENSSL_LIBRARIES})
|
target_link_libraries(${TARGET} PUBLIC ${OPENSSL_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -26,7 +26,7 @@ bool server::start(bool blocking) {
|
||||||
|
|
||||||
LOG_INFO("Starting MCP server on ", host_, ":", port_);
|
LOG_INFO("Starting MCP server on ", host_, ":", port_);
|
||||||
|
|
||||||
// 设置CORS处理
|
// Setup CORS handling
|
||||||
http_server_->Options(".*", [](const httplib::Request& req, httplib::Response& res) {
|
http_server_->Options(".*", [](const httplib::Request& req, httplib::Response& res) {
|
||||||
res.set_header("Access-Control-Allow-Origin", "*");
|
res.set_header("Access-Control-Allow-Origin", "*");
|
||||||
res.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
res.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||||
|
@ -34,23 +34,23 @@ bool server::start(bool blocking) {
|
||||||
res.status = 204; // No Content
|
res.status = 204; // No Content
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置JSON-RPC端点
|
// Setup JSON-RPC endpoint
|
||||||
http_server_->Post(msg_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) {
|
http_server_->Post(msg_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) {
|
||||||
this->handle_jsonrpc(req, res);
|
this->handle_jsonrpc(req, res);
|
||||||
LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"POST ", req.path, " HTTP/1.1\" ", res.status);
|
LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"POST ", req.path, " HTTP/1.1\" ", res.status);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置SSE端点
|
// Setup SSE endpoint
|
||||||
http_server_->Get(sse_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) {
|
http_server_->Get(sse_endpoint_.c_str(), [this](const httplib::Request& req, httplib::Response& res) {
|
||||||
this->handle_sse(req, res);
|
this->handle_sse(req, res);
|
||||||
LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"GET ", req.path, " HTTP/1.1\" ", res.status);
|
LOG_INFO(req.remote_addr, ":", req.remote_port, " - \"GET ", req.path, " HTTP/1.1\" ", res.status);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 启动资源检查线程(优化:只在非阻塞模式下启动)
|
// Start resource check thread (only start in non-blocking mode)
|
||||||
if (!blocking) {
|
if (!blocking) {
|
||||||
maintenance_thread_ = std::make_unique<std::thread>([this]() {
|
maintenance_thread_ = std::make_unique<std::thread>([this]() {
|
||||||
while (running_) {
|
while (running_) {
|
||||||
// 每60秒检查一次不活跃的会话
|
// Check inactive sessions every 60 seconds
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(60));
|
std::this_thread::sleep_for(std::chrono::seconds(60));
|
||||||
if (running_) {
|
if (running_) {
|
||||||
try {
|
try {
|
||||||
|
@ -65,7 +65,7 @@ bool server::start(bool blocking) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动服务器
|
// Start server
|
||||||
if (blocking) {
|
if (blocking) {
|
||||||
running_ = true;
|
running_ = true;
|
||||||
LOG_INFO("Starting server in blocking mode");
|
LOG_INFO("Starting server in blocking mode");
|
||||||
|
@ -76,7 +76,7 @@ bool server::start(bool blocking) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// 在单独的线程中启动
|
// Start server in a separate thread
|
||||||
server_thread_ = std::make_unique<std::thread>([this]() {
|
server_thread_ = std::make_unique<std::thread>([this]() {
|
||||||
LOG_INFO("Starting server in separate thread");
|
LOG_INFO("Starting server in separate thread");
|
||||||
if (!http_server_->listen(host_.c_str(), port_)) {
|
if (!http_server_->listen(host_.c_str(), port_)) {
|
||||||
|
@ -98,7 +98,7 @@ void server::stop() {
|
||||||
LOG_INFO("Stopping MCP server on ", host_, ":", port_);
|
LOG_INFO("Stopping MCP server on ", host_, ":", port_);
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
// 关闭维护线程
|
// Close maintenance thread
|
||||||
if (maintenance_thread_ && maintenance_thread_->joinable()) {
|
if (maintenance_thread_ && maintenance_thread_->joinable()) {
|
||||||
try {
|
try {
|
||||||
maintenance_thread_->join();
|
maintenance_thread_->join();
|
||||||
|
@ -107,20 +107,20 @@ void server::stop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制所有分发器和线程,避免长时间持有锁
|
// Copy all dispatchers and threads to avoid holding the lock for too long
|
||||||
std::vector<std::shared_ptr<event_dispatcher>> dispatchers_to_close;
|
std::vector<std::shared_ptr<event_dispatcher>> dispatchers_to_close;
|
||||||
std::vector<std::unique_ptr<std::thread>> threads_to_join;
|
std::vector<std::unique_ptr<std::thread>> threads_to_join;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
// 复制所有分发器
|
// Copy all dispatchers
|
||||||
dispatchers_to_close.reserve(session_dispatchers_.size());
|
dispatchers_to_close.reserve(session_dispatchers_.size());
|
||||||
for (const auto& [_, dispatcher] : session_dispatchers_) {
|
for (const auto& [_, dispatcher] : session_dispatchers_) {
|
||||||
dispatchers_to_close.push_back(dispatcher);
|
dispatchers_to_close.push_back(dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制所有线程
|
// Copy all threads
|
||||||
threads_to_join.reserve(sse_threads_.size());
|
threads_to_join.reserve(sse_threads_.size());
|
||||||
for (auto& [_, thread] : sse_threads_) {
|
for (auto& [_, thread] : sse_threads_) {
|
||||||
if (thread && thread->joinable()) {
|
if (thread && thread->joinable()) {
|
||||||
|
@ -128,27 +128,27 @@ void server::stop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空映射表
|
// Clear the maps
|
||||||
session_dispatchers_.clear();
|
session_dispatchers_.clear();
|
||||||
sse_threads_.clear();
|
sse_threads_.clear();
|
||||||
session_initialized_.clear();
|
session_initialized_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在锁外关闭所有分发器
|
// Close all dispatchers outside the lock
|
||||||
for (auto& dispatcher : dispatchers_to_close) {
|
for (auto& dispatcher : dispatchers_to_close) {
|
||||||
if (dispatcher && !dispatcher->is_closed()) {
|
if (dispatcher && !dispatcher->is_closed()) {
|
||||||
try {
|
try {
|
||||||
dispatcher->close();
|
dispatcher->close();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// 忽略异常
|
// Ignore exceptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 给线程一些时间处理关闭事件
|
// Give threads some time to handle close events
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||||
|
|
||||||
// 在锁外等待线程结束(有超时限制)
|
// Wait for threads to finish outside the lock (with timeout limit)
|
||||||
const auto timeout_point = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
const auto timeout_point = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||||
|
|
||||||
for (auto& thread : threads_to_join) {
|
for (auto& thread : threads_to_join) {
|
||||||
|
@ -157,20 +157,20 @@ void server::stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::chrono::steady_clock::now() >= timeout_point) {
|
if (std::chrono::steady_clock::now() >= timeout_point) {
|
||||||
// 如果已经超时,detach剩余线程
|
// If timeout reached, detach remaining threads
|
||||||
LOG_WARNING("Thread join timeout reached, detaching remaining threads");
|
LOG_WARNING("Thread join timeout reached, detaching remaining threads");
|
||||||
thread->detach();
|
thread->detach();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试使用超时的join
|
// Try using timeout join
|
||||||
bool joined = false;
|
bool joined = false;
|
||||||
try {
|
try {
|
||||||
// 创建future和promise,用于实现thread join的超时处理
|
// Create future and promise for timeout join
|
||||||
std::promise<void> thread_done;
|
std::promise<void> thread_done;
|
||||||
auto future = thread_done.get_future();
|
auto future = thread_done.get_future();
|
||||||
|
|
||||||
// 在另一个线程中尝试join
|
// Try join in another thread
|
||||||
std::thread join_helper([&thread, &thread_done]() {
|
std::thread join_helper([&thread, &thread_done]() {
|
||||||
try {
|
try {
|
||||||
thread->join();
|
thread->join();
|
||||||
|
@ -182,13 +182,13 @@ void server::stop() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 等待join完成或超时
|
// Wait for join to complete or timeout
|
||||||
if (future.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) {
|
if (future.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) {
|
||||||
future.get(); // 获取可能的异常
|
future.get(); // Get possible exception
|
||||||
joined = true;
|
joined = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理join_helper线程
|
// Process join_helper thread
|
||||||
if (join_helper.joinable()) {
|
if (join_helper.joinable()) {
|
||||||
if (joined) {
|
if (joined) {
|
||||||
join_helper.join();
|
join_helper.join();
|
||||||
|
@ -200,12 +200,12 @@ void server::stop() {
|
||||||
joined = false;
|
joined = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果join失败,则detach
|
// If join fails, then detach
|
||||||
if (!joined) {
|
if (!joined) {
|
||||||
try {
|
try {
|
||||||
thread->detach();
|
thread->detach();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// 忽略异常
|
// Ignore exceptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,37 +382,37 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) {
|
||||||
std::string session_id = generate_session_id();
|
std::string session_id = generate_session_id();
|
||||||
std::string session_uri = msg_endpoint_ + "?session_id=" + session_id;
|
std::string session_uri = msg_endpoint_ + "?session_id=" + session_id;
|
||||||
|
|
||||||
// 设置SSE响应头
|
// Setup SSE response headers
|
||||||
res.set_header("Content-Type", "text/event-stream");
|
res.set_header("Content-Type", "text/event-stream");
|
||||||
res.set_header("Cache-Control", "no-cache");
|
res.set_header("Cache-Control", "no-cache");
|
||||||
res.set_header("Connection", "keep-alive");
|
res.set_header("Connection", "keep-alive");
|
||||||
res.set_header("Access-Control-Allow-Origin", "*");
|
res.set_header("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
// 创建会话特定的事件分发器
|
// Create session-specific event dispatcher
|
||||||
auto session_dispatcher = std::make_shared<event_dispatcher>();
|
auto session_dispatcher = std::make_shared<event_dispatcher>();
|
||||||
|
|
||||||
// 初始化活动时间
|
// Initialize activity time
|
||||||
session_dispatcher->update_activity();
|
session_dispatcher->update_activity();
|
||||||
|
|
||||||
// 添加会话分发器到映射表
|
// Add session dispatcher to mapping table
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
session_dispatchers_[session_id] = session_dispatcher;
|
session_dispatchers_[session_id] = session_dispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建会话线程
|
// Create session thread
|
||||||
auto thread = std::make_unique<std::thread>([this, res, session_id, session_uri, session_dispatcher]() {
|
auto thread = std::make_unique<std::thread>([this, res, session_id, session_uri, session_dispatcher]() {
|
||||||
try {
|
try {
|
||||||
// 发送初始会话URI
|
// Send initial session URI
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "event: endpoint\r\ndata: " << session_uri << "\r\n\r\n";
|
ss << "event: endpoint\r\ndata: " << session_uri << "\r\n\r\n";
|
||||||
session_dispatcher->send_event(ss.str());
|
session_dispatcher->send_event(ss.str());
|
||||||
|
|
||||||
// 更新活动时间(发送消息后)
|
// Update activity time (after sending message)
|
||||||
session_dispatcher->update_activity();
|
session_dispatcher->update_activity();
|
||||||
|
|
||||||
// 定期发送心跳,检测连接状态
|
// Send periodic heartbeats to detect connection status
|
||||||
int heartbeat_count = 0;
|
int heartbeat_count = 0;
|
||||||
while (running_ && !session_dispatcher->is_closed()) {
|
while (running_ && !session_dispatcher->is_closed()) {
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5) + std::chrono::milliseconds(rand() % 500)); // NOTE: DO NOT set it the same as the timeout of wait_event
|
std::this_thread::sleep_for(std::chrono::seconds(5) + std::chrono::milliseconds(rand() % 500)); // NOTE: DO NOT set it the same as the timeout of wait_event
|
||||||
|
@ -431,7 +431,7 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新活动时间(心跳成功)
|
// Update activity time (heartbeat successful)
|
||||||
session_dispatcher->update_activity();
|
session_dispatcher->update_activity();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR("Failed to send heartbeat: ", e.what());
|
LOG_ERROR("Failed to send heartbeat: ", e.what());
|
||||||
|
@ -442,39 +442,39 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) {
|
||||||
LOG_ERROR("SSE session thread exception: ", session_id, ", ", e.what());
|
LOG_ERROR("SSE session thread exception: ", session_id, ", ", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 安全地清理资源
|
// Clean up resources safely
|
||||||
try {
|
try {
|
||||||
// 先复制需要处理的资源指针
|
// Copy resources to be processed
|
||||||
std::shared_ptr<event_dispatcher> dispatcher_to_close;
|
std::shared_ptr<event_dispatcher> dispatcher_to_close;
|
||||||
std::unique_ptr<std::thread> thread_to_release;
|
std::unique_ptr<std::thread> thread_to_release;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
// 获取dispatcher指针
|
// Get dispatcher pointer
|
||||||
auto dispatcher_it = session_dispatchers_.find(session_id);
|
auto dispatcher_it = session_dispatchers_.find(session_id);
|
||||||
if (dispatcher_it != session_dispatchers_.end()) {
|
if (dispatcher_it != session_dispatchers_.end()) {
|
||||||
dispatcher_to_close = dispatcher_it->second;
|
dispatcher_to_close = dispatcher_it->second;
|
||||||
session_dispatchers_.erase(dispatcher_it);
|
session_dispatchers_.erase(dispatcher_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取线程指针
|
// Get thread pointer
|
||||||
auto thread_it = sse_threads_.find(session_id);
|
auto thread_it = sse_threads_.find(session_id);
|
||||||
if (thread_it != sse_threads_.end()) {
|
if (thread_it != sse_threads_.end()) {
|
||||||
thread_to_release = std::move(thread_it->second);
|
thread_to_release = std::move(thread_it->second);
|
||||||
sse_threads_.erase(thread_it);
|
sse_threads_.erase(thread_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理初始化状态
|
// Clean up initialization status
|
||||||
session_initialized_.erase(session_id);
|
session_initialized_.erase(session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在锁外关闭dispatcher
|
// Close dispatcher outside the lock
|
||||||
if (dispatcher_to_close && !dispatcher_to_close->is_closed()) {
|
if (dispatcher_to_close && !dispatcher_to_close->is_closed()) {
|
||||||
dispatcher_to_close->close();
|
dispatcher_to_close->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 释放线程资源
|
// Release thread resources
|
||||||
if (thread_to_release) {
|
if (thread_to_release) {
|
||||||
thread_to_release.release();
|
thread_to_release.release();
|
||||||
}
|
}
|
||||||
|
@ -485,42 +485,42 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 存储线程
|
// Store thread
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
sse_threads_[session_id] = std::move(thread);
|
sse_threads_[session_id] = std::move(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置分块内容提供者
|
// Setup chunked content provider
|
||||||
res.set_chunked_content_provider("text/event-stream", [this, session_id, session_dispatcher](size_t /* offset */, httplib::DataSink& sink) {
|
res.set_chunked_content_provider("text/event-stream", [this, session_id, session_dispatcher](size_t /* offset */, httplib::DataSink& sink) {
|
||||||
try {
|
try {
|
||||||
// 检查会话是否已关闭 - 直接从分发器获取状态,减少锁冲突
|
// Check if session is closed - directly get status from dispatcher, reduce lock contention
|
||||||
if (session_dispatcher->is_closed()) {
|
if (session_dispatcher->is_closed()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新活动时间(接收到请求)
|
// Update activity time (received request)
|
||||||
session_dispatcher->update_activity();
|
session_dispatcher->update_activity();
|
||||||
|
|
||||||
// 等待事件
|
// Wait for event
|
||||||
bool result = session_dispatcher->wait_event(&sink);
|
bool result = session_dispatcher->wait_event(&sink);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
LOG_WARNING("Failed to wait for event, closing connection: ", session_id);
|
LOG_WARNING("Failed to wait for event, closing connection: ", session_id);
|
||||||
|
|
||||||
// 直接关闭分发器,无需加锁
|
// Close dispatcher directly, no need to lock
|
||||||
session_dispatcher->close();
|
session_dispatcher->close();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新活动时间(成功接收消息)
|
// Update activity time (successfully received message)
|
||||||
session_dispatcher->update_activity();
|
session_dispatcher->update_activity();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR("SSE content provider exception: ", e.what());
|
LOG_ERROR("SSE content provider exception: ", e.what());
|
||||||
|
|
||||||
// 直接关闭分发器,无需加锁
|
// Close dispatcher directly, no need to lock
|
||||||
session_dispatcher->close();
|
session_dispatcher->close();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -529,23 +529,23 @@ void server::handle_sse(const httplib::Request& req, httplib::Response& res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) {
|
void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res) {
|
||||||
// 设置响应头
|
// Setup response headers
|
||||||
res.set_header("Content-Type", "application/json");
|
res.set_header("Content-Type", "application/json");
|
||||||
res.set_header("Access-Control-Allow-Origin", "*");
|
res.set_header("Access-Control-Allow-Origin", "*");
|
||||||
res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS");
|
res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||||
res.set_header("Access-Control-Allow-Headers", "Content-Type");
|
res.set_header("Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
|
||||||
// 处理OPTIONS请求(CORS预检)
|
// Handle OPTIONS request (CORS pre-flight)
|
||||||
if (req.method == "OPTIONS") {
|
if (req.method == "OPTIONS") {
|
||||||
res.status = 204; // No Content
|
res.status = 204; // No Content
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取会话ID
|
// Get session ID
|
||||||
auto it = req.params.find("session_id");
|
auto it = req.params.find("session_id");
|
||||||
std::string session_id = it != req.params.end() ? it->second : "";
|
std::string session_id = it != req.params.end() ? it->second : "";
|
||||||
|
|
||||||
// 更新会话活动时间
|
// Update session activity time
|
||||||
if (!session_id.empty()) {
|
if (!session_id.empty()) {
|
||||||
std::shared_ptr<event_dispatcher> dispatcher;
|
std::shared_ptr<event_dispatcher> dispatcher;
|
||||||
{
|
{
|
||||||
|
@ -561,7 +561,7 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析请求
|
// Parse request
|
||||||
json req_json;
|
json req_json;
|
||||||
try {
|
try {
|
||||||
req_json = json::parse(req.body);
|
req_json = json::parse(req.body);
|
||||||
|
@ -572,13 +572,13 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查会话是否存在
|
// Check if session exists
|
||||||
std::shared_ptr<event_dispatcher> dispatcher;
|
std::shared_ptr<event_dispatcher> dispatcher;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto disp_it = session_dispatchers_.find(session_id);
|
auto disp_it = session_dispatchers_.find(session_id);
|
||||||
if (disp_it == session_dispatchers_.end()) {
|
if (disp_it == session_dispatchers_.end()) {
|
||||||
// 处理ping请求
|
// Handle ping request
|
||||||
if (req_json["method"] == "ping") {
|
if (req_json["method"] == "ping") {
|
||||||
res.status = 202;
|
res.status = 202;
|
||||||
res.set_content("Accepted", "text/plain");
|
res.set_content("Accepted", "text/plain");
|
||||||
|
@ -592,7 +592,7 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
dispatcher = disp_it->second;
|
dispatcher = disp_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建请求对象
|
// Create request object
|
||||||
request mcp_req;
|
request mcp_req;
|
||||||
try {
|
try {
|
||||||
mcp_req.jsonrpc = req_json["jsonrpc"].get<std::string>();
|
mcp_req.jsonrpc = req_json["jsonrpc"].get<std::string>();
|
||||||
|
@ -610,25 +610,25 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是通知(没有ID),直接处理并返回202状态码
|
// If it is a notification (no ID), process it directly and return 202 status code
|
||||||
if (mcp_req.is_notification()) {
|
if (mcp_req.is_notification()) {
|
||||||
// 在线程池中异步处理通知
|
// Process it asynchronously in the thread pool
|
||||||
thread_pool_.enqueue([this, mcp_req, session_id]() {
|
thread_pool_.enqueue([this, mcp_req, session_id]() {
|
||||||
process_request(mcp_req, session_id);
|
process_request(mcp_req, session_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 返回202 Accepted
|
// Return 202 Accepted
|
||||||
res.status = 202;
|
res.status = 202;
|
||||||
res.set_content("Accepted", "text/plain");
|
res.set_content("Accepted", "text/plain");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于有ID的请求,在线程池中处理并通过SSE返回结果
|
// For requests with ID, process it asynchronously in the thread pool and return the result via SSE
|
||||||
thread_pool_.enqueue([this, mcp_req, session_id, dispatcher]() {
|
thread_pool_.enqueue([this, mcp_req, session_id, dispatcher]() {
|
||||||
// 处理请求
|
// Process the request
|
||||||
json response_json = process_request(mcp_req, session_id);
|
json response_json = process_request(mcp_req, session_id);
|
||||||
|
|
||||||
// 通过SSE发送响应
|
// Send response via SSE
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "event: message\r\ndata: " << response_json.dump() << "\r\n\r\n";
|
ss << "event: message\r\ndata: " << response_json.dump() << "\r\n\r\n";
|
||||||
bool result = dispatcher->send_event(ss.str());
|
bool result = dispatcher->send_event(ss.str());
|
||||||
|
@ -638,13 +638,13 @@ void server::handle_jsonrpc(const httplib::Request& req, httplib::Response& res)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 返回202 Accepted
|
// Return 202 Accepted
|
||||||
res.status = 202;
|
res.status = 202;
|
||||||
res.set_content("Accepted", "text/plain");
|
res.set_content("Accepted", "text/plain");
|
||||||
}
|
}
|
||||||
|
|
||||||
json server::process_request(const request& req, const std::string& session_id) {
|
json server::process_request(const request& req, const std::string& session_id) {
|
||||||
// 检查是否是通知
|
// Check if it is a notification
|
||||||
if (req.is_notification()) {
|
if (req.is_notification()) {
|
||||||
if (req.method == "notifications/initialized") {
|
if (req.method == "notifications/initialized") {
|
||||||
set_session_initialized(session_id, true);
|
set_session_initialized(session_id, true);
|
||||||
|
@ -652,11 +652,11 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
return json::object();
|
return json::object();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理方法调用
|
// Process method call
|
||||||
try {
|
try {
|
||||||
LOG_INFO("Processing method call: ", req.method);
|
LOG_INFO("Processing method call: ", req.method);
|
||||||
|
|
||||||
// 特殊情况:初始化
|
// Special case: initialization
|
||||||
if (req.method == "initialize") {
|
if (req.method == "initialize") {
|
||||||
return handle_initialize(req, session_id);
|
return handle_initialize(req, session_id);
|
||||||
} else if (req.method == "ping") {
|
} else if (req.method == "ping") {
|
||||||
|
@ -672,7 +672,7 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
).to_json();
|
).to_json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找注册的方法处理器
|
// Find registered method handler
|
||||||
std::function<json(const json&)> handler;
|
std::function<json(const json&)> handler;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
@ -683,19 +683,19 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler) {
|
if (handler) {
|
||||||
// 调用处理器
|
// Call handler
|
||||||
LOG_INFO("Calling method handler: ", req.method);
|
LOG_INFO("Calling method handler: ", req.method);
|
||||||
auto future = thread_pool_.enqueue([handler, params = req.params]() -> json {
|
auto future = thread_pool_.enqueue([handler, params = req.params]() -> json {
|
||||||
return handler(params);
|
return handler(params);
|
||||||
});
|
});
|
||||||
json result = future.get();
|
json result = future.get();
|
||||||
|
|
||||||
// 创建成功响应
|
// Create success response
|
||||||
LOG_INFO("Method call successful: ", req.method);
|
LOG_INFO("Method call successful: ", req.method);
|
||||||
return response::create_success(req.id, result).to_json();
|
return response::create_success(req.id, result).to_json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方法未找到
|
// Method not found
|
||||||
LOG_WARNING("Method not found: ", req.method);
|
LOG_WARNING("Method not found: ", req.method);
|
||||||
return response::create_error(
|
return response::create_error(
|
||||||
req.id,
|
req.id,
|
||||||
|
@ -703,7 +703,7 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
"Method not found: " + req.method
|
"Method not found: " + req.method
|
||||||
).to_json();
|
).to_json();
|
||||||
} catch (const mcp_exception& e) {
|
} catch (const mcp_exception& e) {
|
||||||
// MCP异常
|
// MCP exception
|
||||||
LOG_ERROR("MCP exception: ", e.what(), ", code: ", static_cast<int>(e.code()));
|
LOG_ERROR("MCP exception: ", e.what(), ", code: ", static_cast<int>(e.code()));
|
||||||
return response::create_error(
|
return response::create_error(
|
||||||
req.id,
|
req.id,
|
||||||
|
@ -711,7 +711,7 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
e.what()
|
e.what()
|
||||||
).to_json();
|
).to_json();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
// 其他异常
|
// Other exceptions
|
||||||
LOG_ERROR("Exception while processing request: ", e.what());
|
LOG_ERROR("Exception while processing request: ", e.what());
|
||||||
return response::create_error(
|
return response::create_error(
|
||||||
req.id,
|
req.id,
|
||||||
|
@ -719,7 +719,7 @@ json server::process_request(const request& req, const std::string& session_id)
|
||||||
"Internal error: " + std::string(e.what())
|
"Internal error: " + std::string(e.what())
|
||||||
).to_json();
|
).to_json();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// 未知异常
|
// Unknown exception
|
||||||
LOG_ERROR("Unknown exception while processing request");
|
LOG_ERROR("Unknown exception while processing request");
|
||||||
return response::create_error(
|
return response::create_error(
|
||||||
req.id,
|
req.id,
|
||||||
|
@ -792,7 +792,7 @@ json server::handle_initialize(const request& req, const std::string& session_id
|
||||||
}
|
}
|
||||||
|
|
||||||
void server::send_request(const std::string& session_id, const std::string& method, const json& params) {
|
void server::send_request(const std::string& session_id, const std::string& method, const json& params) {
|
||||||
// 检查会话ID是否有效
|
// Check if session ID is valid
|
||||||
if (session_id.empty()) {
|
if (session_id.empty()) {
|
||||||
LOG_WARNING("Cannot send request to empty session_id");
|
LOG_WARNING("Cannot send request to empty session_id");
|
||||||
return;
|
return;
|
||||||
|
@ -810,7 +810,7 @@ void server::send_request(const std::string& session_id, const std::string& meth
|
||||||
// Create request
|
// Create request
|
||||||
request req = request::create(method, params);
|
request req = request::create(method, params);
|
||||||
|
|
||||||
// 获取会话分发器
|
// Get session dispatcher
|
||||||
std::shared_ptr<event_dispatcher> dispatcher;
|
std::shared_ptr<event_dispatcher> dispatcher;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
@ -822,13 +822,13 @@ void server::send_request(const std::string& session_id, const std::string& meth
|
||||||
dispatcher = it->second;
|
dispatcher = it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认dispatcher仍然有效
|
// Confirm dispatcher is still valid
|
||||||
if (!dispatcher || dispatcher->is_closed()) {
|
if (!dispatcher || dispatcher->is_closed()) {
|
||||||
LOG_WARNING("Cannot send to closed session: ", session_id);
|
LOG_WARNING("Cannot send to closed session: ", session_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送请求
|
// Send request
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "event: message\r\ndata: " << req.to_json().dump() << "\r\n\r\n";
|
ss << "event: message\r\ndata: " << req.to_json().dump() << "\r\n\r\n";
|
||||||
bool result = dispatcher->send_event(ss.str());
|
bool result = dispatcher->send_event(ss.str());
|
||||||
|
@ -839,7 +839,7 @@ void server::send_request(const std::string& session_id, const std::string& meth
|
||||||
}
|
}
|
||||||
|
|
||||||
bool server::is_session_initialized(const std::string& session_id) const {
|
bool server::is_session_initialized(const std::string& session_id) const {
|
||||||
// 检查会话ID是否有效
|
// Check if session ID is valid
|
||||||
if (session_id.empty()) {
|
if (session_id.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -855,7 +855,7 @@ bool server::is_session_initialized(const std::string& session_id) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void server::set_session_initialized(const std::string& session_id, bool initialized) {
|
void server::set_session_initialized(const std::string& session_id, bool initialized) {
|
||||||
// 检查会话ID是否有效
|
// Check if session ID is valid
|
||||||
if (session_id.empty()) {
|
if (session_id.empty()) {
|
||||||
LOG_WARNING("Cannot set initialization state for empty session_id");
|
LOG_WARNING("Cannot set initialization state for empty session_id");
|
||||||
return;
|
return;
|
||||||
|
@ -863,7 +863,7 @@ void server::set_session_initialized(const std::string& session_id, bool initial
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
// 检查会话是否仍然存在
|
// Check if session still exists
|
||||||
auto it = session_dispatchers_.find(session_id);
|
auto it = session_dispatchers_.find(session_id);
|
||||||
if (it == session_dispatchers_.end()) {
|
if (it == session_dispatchers_.end()) {
|
||||||
LOG_WARNING("Cannot set initialization state for non-existent session: ", session_id);
|
LOG_WARNING("Cannot set initialization state for non-existent session: ", session_id);
|
||||||
|
@ -915,7 +915,7 @@ void server::check_inactive_sessions() {
|
||||||
if (!running_) return;
|
if (!running_) return;
|
||||||
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
const auto timeout = std::chrono::minutes(3); // 3分钟不活跃则关闭
|
const auto timeout = std::chrono::minutes(60); // 1 hour inactive then close
|
||||||
|
|
||||||
std::vector<std::string> sessions_to_close;
|
std::vector<std::string> sessions_to_close;
|
||||||
|
|
||||||
|
@ -923,13 +923,13 @@ void server::check_inactive_sessions() {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
for (const auto& [session_id, dispatcher] : session_dispatchers_) {
|
for (const auto& [session_id, dispatcher] : session_dispatchers_) {
|
||||||
if (now - dispatcher->last_activity() > timeout) {
|
if (now - dispatcher->last_activity() > timeout) {
|
||||||
// 超过闲置时间限制
|
// Exceeded idle time limit
|
||||||
sessions_to_close.push_back(session_id);
|
sessions_to_close.push_back(session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭不活跃的会话
|
// Close inactive sessions
|
||||||
for (const auto& session_id : sessions_to_close) {
|
for (const auto& session_id : sessions_to_close) {
|
||||||
LOG_INFO("Closing inactive session: ", session_id);
|
LOG_INFO("Closing inactive session: ", session_id);
|
||||||
|
|
||||||
|
|
|
@ -206,13 +206,13 @@ bool stdio_client::start_server_process() {
|
||||||
LOG_INFO("Starting server process: ", command_);
|
LOG_INFO("Starting server process: ", command_);
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
// Windows实现
|
// Windows implementation
|
||||||
SECURITY_ATTRIBUTES sa;
|
SECURITY_ATTRIBUTES sa;
|
||||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
sa.bInheritHandle = TRUE;
|
sa.bInheritHandle = TRUE;
|
||||||
sa.lpSecurityDescriptor = NULL;
|
sa.lpSecurityDescriptor = NULL;
|
||||||
|
|
||||||
// 创建管道
|
// Create pipes
|
||||||
HANDLE child_stdin_read = NULL;
|
HANDLE child_stdin_read = NULL;
|
||||||
HANDLE child_stdin_write = NULL;
|
HANDLE child_stdin_write = NULL;
|
||||||
HANDLE child_stdout_read = NULL;
|
HANDLE child_stdout_read = NULL;
|
||||||
|
@ -246,7 +246,7 @@ bool stdio_client::start_server_process() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备进程启动信息
|
// Prepare process startup info
|
||||||
STARTUPINFOA si;
|
STARTUPINFOA si;
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
@ -259,12 +259,12 @@ bool stdio_client::start_server_process() {
|
||||||
|
|
||||||
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
|
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
|
||||||
|
|
||||||
// 准备环境变量
|
// Prepare environment variables
|
||||||
std::string env_block;
|
std::string env_block;
|
||||||
if (!env_vars_.empty()) {
|
if (!env_vars_.empty()) {
|
||||||
char* system_env = GetEnvironmentStringsA();
|
char* system_env = GetEnvironmentStringsA();
|
||||||
if (system_env) {
|
if (system_env) {
|
||||||
// 复制系统环境变量
|
// Copy system environment variables
|
||||||
const char* env_ptr = system_env;
|
const char* env_ptr = system_env;
|
||||||
while (*env_ptr) {
|
while (*env_ptr) {
|
||||||
std::string env_var(env_ptr);
|
std::string env_var(env_ptr);
|
||||||
|
@ -273,32 +273,32 @@ bool stdio_client::start_server_process() {
|
||||||
}
|
}
|
||||||
FreeEnvironmentStringsA(system_env);
|
FreeEnvironmentStringsA(system_env);
|
||||||
|
|
||||||
// 添加自定义环境变量
|
// Add custom environment variables
|
||||||
for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) {
|
for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) {
|
||||||
std::string env_var = it.key() + "=" + it.value().get<std::string>();
|
std::string env_var = it.key() + "=" + it.value().get<std::string>();
|
||||||
env_block += env_var + '\0';
|
env_block += env_var + '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加结束符
|
// Add terminator
|
||||||
env_block += '\0';
|
env_block += '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建子进程
|
// Create child process
|
||||||
std::string cmd_line = command_;
|
std::string cmd_line = command_;
|
||||||
char* cmd_str = const_cast<char*>(cmd_line.c_str());
|
char* cmd_str = const_cast<char*>(cmd_line.c_str());
|
||||||
|
|
||||||
BOOL success = CreateProcessA(
|
BOOL success = CreateProcessA(
|
||||||
NULL, // 应用程序名称
|
NULL, // Application name
|
||||||
cmd_str, // 命令行
|
cmd_str, // Command line
|
||||||
NULL, // 进程安全属性
|
NULL, // Process security attributes
|
||||||
NULL, // 线程安全属性
|
NULL, // Thread security attributes
|
||||||
TRUE, // 继承句柄
|
TRUE, // Inherit handles
|
||||||
CREATE_NO_WINDOW, // 创建标志
|
CREATE_NO_WINDOW, // Creation flags
|
||||||
env_vars_.empty() ? NULL : (LPVOID)env_block.c_str(), // 环境变量
|
env_vars_.empty() ? NULL : (LPVOID)env_block.c_str(), // Environment variables
|
||||||
NULL, // 当前目录
|
NULL, // Current directory
|
||||||
&si, // 启动信息
|
&si, // Startup info
|
||||||
&pi // 进程信息
|
&pi // Process info
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -310,12 +310,12 @@ bool stdio_client::start_server_process() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭不需要的句柄
|
// Close unnecessary handles
|
||||||
CloseHandle(child_stdin_read);
|
CloseHandle(child_stdin_read);
|
||||||
CloseHandle(child_stdout_write);
|
CloseHandle(child_stdout_write);
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
// 保存进程信息
|
// Save process info
|
||||||
process_id_ = pi.dwProcessId;
|
process_id_ = pi.dwProcessId;
|
||||||
process_handle_ = pi.hProcess;
|
process_handle_ = pi.hProcess;
|
||||||
stdin_pipe_[0] = NULL;
|
stdin_pipe_[0] = NULL;
|
||||||
|
@ -323,13 +323,13 @@ bool stdio_client::start_server_process() {
|
||||||
stdout_pipe_[0] = child_stdout_read;
|
stdout_pipe_[0] = child_stdout_read;
|
||||||
stdout_pipe_[1] = NULL;
|
stdout_pipe_[1] = NULL;
|
||||||
|
|
||||||
// 设置非阻塞模式
|
// Set non-blocking mode
|
||||||
DWORD mode = PIPE_NOWAIT;
|
DWORD mode = PIPE_NOWAIT;
|
||||||
SetNamedPipeHandleState(stdout_pipe_[0], &mode, NULL, NULL);
|
SetNamedPipeHandleState(stdout_pipe_[0], &mode, NULL, NULL);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// POSIX实现
|
// POSIX implementation
|
||||||
// 创建管道
|
// Create pipes
|
||||||
if (pipe(stdin_pipe_) == -1) {
|
if (pipe(stdin_pipe_) == -1) {
|
||||||
LOG_ERROR("Failed to create stdin pipe: ", strerror(errno));
|
LOG_ERROR("Failed to create stdin pipe: ", strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
|
@ -342,7 +342,7 @@ bool stdio_client::start_server_process() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建子进程
|
// Create child process
|
||||||
process_id_ = fork();
|
process_id_ = fork();
|
||||||
|
|
||||||
if (process_id_ == -1) {
|
if (process_id_ == -1) {
|
||||||
|
@ -355,9 +355,9 @@ bool stdio_client::start_server_process() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process_id_ == 0) {
|
if (process_id_ == 0) {
|
||||||
// 子进程
|
// Child process
|
||||||
|
|
||||||
// 设置环境变量
|
// Set environment variables
|
||||||
if (!env_vars_.empty()) {
|
if (!env_vars_.empty()) {
|
||||||
for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) {
|
for (auto it = env_vars_.begin(); it != env_vars_.end(); ++it) {
|
||||||
std::string env_var = it.key() + "=" + it.value().get<std::string>();
|
std::string env_var = it.key() + "=" + it.value().get<std::string>();
|
||||||
|
@ -367,11 +367,11 @@ bool stdio_client::start_server_process() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭不需要的管道端
|
// Close unnecessary pipe ends
|
||||||
close(stdin_pipe_[1]); // 关闭写入端
|
close(stdin_pipe_[1]); // Close write end
|
||||||
close(stdout_pipe_[0]); // 关闭读取端
|
close(stdout_pipe_[0]); // Close read end
|
||||||
|
|
||||||
// 重定向标准输入/输出
|
// Redirect standard input/output
|
||||||
if (dup2(stdin_pipe_[0], STDIN_FILENO) == -1) {
|
if (dup2(stdin_pipe_[0], STDIN_FILENO) == -1) {
|
||||||
LOG_ERROR("Failed to redirect stdin: ", strerror(errno));
|
LOG_ERROR("Failed to redirect stdin: ", strerror(errno));
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
@ -382,15 +382,15 @@ bool stdio_client::start_server_process() {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭已重定向的文件描述符
|
// Close already redirected file descriptors
|
||||||
close(stdin_pipe_[0]);
|
close(stdin_pipe_[0]);
|
||||||
close(stdout_pipe_[1]);
|
close(stdout_pipe_[1]);
|
||||||
|
|
||||||
// 设置非阻塞模式
|
// Set non-blocking mode
|
||||||
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||||
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
|
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
// 执行命令
|
// Execute command
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
std::istringstream iss(command_);
|
std::istringstream iss(command_);
|
||||||
std::string arg;
|
std::string arg;
|
||||||
|
@ -407,22 +407,22 @@ bool stdio_client::start_server_process() {
|
||||||
|
|
||||||
execvp(c_args[0], c_args.data());
|
execvp(c_args[0], c_args.data());
|
||||||
|
|
||||||
// 如果execvp返回,则表示出错
|
// If execvp returns, it means an error occurred
|
||||||
LOG_ERROR("Failed to execute command: ", strerror(errno));
|
LOG_ERROR("Failed to execute command: ", strerror(errno));
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 父进程
|
// Parent process
|
||||||
|
|
||||||
// 关闭不需要的管道端
|
// Close unnecessary pipe ends
|
||||||
close(stdin_pipe_[0]); // 关闭读取端
|
close(stdin_pipe_[0]); // Close read end
|
||||||
close(stdout_pipe_[1]); // 关闭写入端
|
close(stdout_pipe_[1]); // Close write end
|
||||||
|
|
||||||
// 设置非阻塞模式
|
// Set non-blocking mode
|
||||||
int flags = fcntl(stdout_pipe_[0], F_GETFL, 0);
|
int flags = fcntl(stdout_pipe_[0], F_GETFL, 0);
|
||||||
fcntl(stdout_pipe_[0], F_SETFL, flags | O_NONBLOCK);
|
fcntl(stdout_pipe_[0], F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
// 检查进程是否仍在运行
|
// Check if process is still running
|
||||||
int status;
|
int status;
|
||||||
pid_t result = waitpid(process_id_, &status, WNOHANG);
|
pid_t result = waitpid(process_id_, &status, WNOHANG);
|
||||||
|
|
||||||
|
@ -455,14 +455,14 @@ bool stdio_client::start_server_process() {
|
||||||
|
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
|
||||||
// 启动读取线程
|
// Start read thread
|
||||||
read_thread_ = std::make_unique<std::thread>(&stdio_client::read_thread_func, this);
|
read_thread_ = std::make_unique<std::thread>(&stdio_client::read_thread_func, this);
|
||||||
|
|
||||||
// 等待一段时间,确保进程启动
|
// Wait for a while to ensure process starts
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
// 检查进程是否仍在运行
|
// Check if process is still running
|
||||||
DWORD exit_code;
|
DWORD exit_code;
|
||||||
if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code != STILL_ACTIVE) {
|
if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code != STILL_ACTIVE) {
|
||||||
LOG_ERROR("Server process exited immediately with status: ", exit_code);
|
LOG_ERROR("Server process exited immediately with status: ", exit_code);
|
||||||
|
@ -494,8 +494,8 @@ void stdio_client::stop_server_process() {
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
// Windows实现
|
// Windows implementation
|
||||||
// 关闭管道
|
// Close pipes
|
||||||
if (stdin_pipe_[1] != NULL) {
|
if (stdin_pipe_[1] != NULL) {
|
||||||
CloseHandle(stdin_pipe_[1]);
|
CloseHandle(stdin_pipe_[1]);
|
||||||
stdin_pipe_[1] = NULL;
|
stdin_pipe_[1] = NULL;
|
||||||
|
@ -506,22 +506,22 @@ void stdio_client::stop_server_process() {
|
||||||
stdout_pipe_[0] = NULL;
|
stdout_pipe_[0] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待读取线程结束
|
// Wait for read thread to finish
|
||||||
if (read_thread_ && read_thread_->joinable()) {
|
if (read_thread_ && read_thread_->joinable()) {
|
||||||
read_thread_->join();
|
read_thread_->join();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 终止进程
|
// Terminate process
|
||||||
if (process_handle_ != NULL) {
|
if (process_handle_ != NULL) {
|
||||||
LOG_INFO("Terminating process: ", process_id_);
|
LOG_INFO("Terminating process: ", process_id_);
|
||||||
TerminateProcess(process_handle_, 0);
|
TerminateProcess(process_handle_, 0);
|
||||||
|
|
||||||
// 等待进程结束
|
// Wait for process to finish
|
||||||
WaitForSingleObject(process_handle_, 2000);
|
WaitForSingleObject(process_handle_, 2000);
|
||||||
|
|
||||||
DWORD exit_code;
|
DWORD exit_code;
|
||||||
if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code == STILL_ACTIVE) {
|
if (GetExitCodeProcess(process_handle_, &exit_code) && exit_code == STILL_ACTIVE) {
|
||||||
// 进程仍在运行,强制终止
|
// Process is still running, force termination
|
||||||
LOG_WARNING("Process did not terminate, forcing termination");
|
LOG_WARNING("Process did not terminate, forcing termination");
|
||||||
TerminateProcess(process_handle_, 1);
|
TerminateProcess(process_handle_, 1);
|
||||||
WaitForSingleObject(process_handle_, 1000);
|
WaitForSingleObject(process_handle_, 1000);
|
||||||
|
@ -532,8 +532,8 @@ void stdio_client::stop_server_process() {
|
||||||
process_id_ = -1;
|
process_id_ = -1;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// POSIX实现
|
// POSIX implementation
|
||||||
// 关闭管道
|
// Close pipes
|
||||||
if (stdin_pipe_[1] != -1) {
|
if (stdin_pipe_[1] != -1) {
|
||||||
close(stdin_pipe_[1]);
|
close(stdin_pipe_[1]);
|
||||||
stdin_pipe_[1] = -1;
|
stdin_pipe_[1] = -1;
|
||||||
|
@ -544,28 +544,28 @@ void stdio_client::stop_server_process() {
|
||||||
stdout_pipe_[0] = -1;
|
stdout_pipe_[0] = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待读取线程结束
|
// Wait for read thread to finish
|
||||||
if (read_thread_ && read_thread_->joinable()) {
|
if (read_thread_ && read_thread_->joinable()) {
|
||||||
read_thread_->join();
|
read_thread_->join();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 终止进程
|
// Terminate process
|
||||||
if (process_id_ > 0) {
|
if (process_id_ > 0) {
|
||||||
LOG_INFO("Sending SIGTERM to process: ", process_id_);
|
LOG_INFO("Sending SIGTERM to process: ", process_id_);
|
||||||
kill(process_id_, SIGTERM);
|
kill(process_id_, SIGTERM);
|
||||||
|
|
||||||
// 等待进程结束
|
// Wait for process to finish
|
||||||
int status;
|
int status;
|
||||||
pid_t result = waitpid(process_id_, &status, WNOHANG);
|
pid_t result = waitpid(process_id_, &status, WNOHANG);
|
||||||
|
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
// 进程仍在运行,等待一段时间
|
// Process is still running, wait for a while
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
|
||||||
result = waitpid(process_id_, &status, WNOHANG);
|
result = waitpid(process_id_, &status, WNOHANG);
|
||||||
|
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
// 进程仍在运行,强制终止
|
// Process is still running, force termination
|
||||||
LOG_WARNING("Process did not terminate, sending SIGKILL");
|
LOG_WARNING("Process did not terminate, sending SIGKILL");
|
||||||
kill(process_id_, SIGKILL);
|
kill(process_id_, SIGKILL);
|
||||||
waitpid(process_id_, &status, 0);
|
waitpid(process_id_, &status, 0);
|
||||||
|
@ -587,18 +587,18 @@ void stdio_client::read_thread_func() {
|
||||||
std::string data_buffer;
|
std::string data_buffer;
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
// Windows实现
|
// Windows implementation
|
||||||
DWORD bytes_read;
|
DWORD bytes_read;
|
||||||
|
|
||||||
while (running_) {
|
while (running_) {
|
||||||
// 读取数据
|
// Read data
|
||||||
BOOL success = ReadFile(stdout_pipe_[0], buffer, buffer_size - 1, &bytes_read, NULL);
|
BOOL success = ReadFile(stdout_pipe_[0], buffer, buffer_size - 1, &bytes_read, NULL);
|
||||||
|
|
||||||
if (success && bytes_read > 0) {
|
if (success && bytes_read > 0) {
|
||||||
buffer[bytes_read] = '\0';
|
buffer[bytes_read] = '\0';
|
||||||
data_buffer.append(buffer, bytes_read);
|
data_buffer.append(buffer, bytes_read);
|
||||||
|
|
||||||
// 处理完整的JSON-RPC消息
|
// Process complete JSON-RPC message
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while ((pos = data_buffer.find('\n')) != std::string::npos) {
|
while ((pos = data_buffer.find('\n')) != std::string::npos) {
|
||||||
std::string line = data_buffer.substr(0, pos);
|
std::string line = data_buffer.substr(0, pos);
|
||||||
|
@ -610,7 +610,7 @@ void stdio_client::read_thread_func() {
|
||||||
|
|
||||||
if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") {
|
if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") {
|
||||||
if (message.contains("id") && !message["id"].is_null()) {
|
if (message.contains("id") && !message["id"].is_null()) {
|
||||||
// 这是一个响应
|
// This is a response
|
||||||
json id = message["id"];
|
json id = message["id"];
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(response_mutex_);
|
std::lock_guard<std::mutex> lock(response_mutex_);
|
||||||
|
@ -634,9 +634,9 @@ void stdio_client::read_thread_func() {
|
||||||
LOG_WARNING("Received response for unknown request ID: ", id);
|
LOG_WARNING("Received response for unknown request ID: ", id);
|
||||||
}
|
}
|
||||||
} else if (message.contains("method")) {
|
} else if (message.contains("method")) {
|
||||||
// 这是一个请求或通知
|
// This is a request or notification
|
||||||
LOG_INFO("Received request/notification: ", message["method"]);
|
LOG_INFO("Received request/notification: ", message["method"]);
|
||||||
// 目前不处理服务器发来的请求
|
// Currently not handling requests from the server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const json::exception& e) {
|
} catch (const json::exception& e) {
|
||||||
|
@ -647,7 +647,7 @@ void stdio_client::read_thread_func() {
|
||||||
} else if (!success) {
|
} else if (!success) {
|
||||||
DWORD error = GetLastError();
|
DWORD error = GetLastError();
|
||||||
if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) {
|
if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) {
|
||||||
// 管道已关闭或没有数据
|
// Pipe is closed or no data available
|
||||||
LOG_WARNING("Pipe closed by server or no data available");
|
LOG_WARNING("Pipe closed by server or no data available");
|
||||||
break;
|
break;
|
||||||
} else if (error != ERROR_IO_PENDING) {
|
} else if (error != ERROR_IO_PENDING) {
|
||||||
|
@ -655,24 +655,24 @@ void stdio_client::read_thread_func() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非阻塞模式下没有数据可读
|
// No data to read in non-blocking mode
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
} else {
|
} else {
|
||||||
// 非阻塞模式下没有数据可读
|
// No data to read in non-blocking mode
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// POSIX实现
|
// POSIX implementation
|
||||||
while (running_) {
|
while (running_) {
|
||||||
// 读取数据
|
// Read data
|
||||||
ssize_t bytes_read = read(stdout_pipe_[0], buffer, buffer_size - 1);
|
ssize_t bytes_read = read(stdout_pipe_[0], buffer, buffer_size - 1);
|
||||||
|
|
||||||
if (bytes_read > 0) {
|
if (bytes_read > 0) {
|
||||||
buffer[bytes_read] = '\0';
|
buffer[bytes_read] = '\0';
|
||||||
data_buffer.append(buffer, bytes_read);
|
data_buffer.append(buffer, bytes_read);
|
||||||
|
|
||||||
// 处理完整的JSON-RPC消息
|
// Process complete JSON-RPC message
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while ((pos = data_buffer.find('\n')) != std::string::npos) {
|
while ((pos = data_buffer.find('\n')) != std::string::npos) {
|
||||||
std::string line = data_buffer.substr(0, pos);
|
std::string line = data_buffer.substr(0, pos);
|
||||||
|
@ -684,7 +684,7 @@ void stdio_client::read_thread_func() {
|
||||||
|
|
||||||
if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") {
|
if (message.contains("jsonrpc") && message["jsonrpc"] == "2.0") {
|
||||||
if (message.contains("id") && !message["id"].is_null()) {
|
if (message.contains("id") && !message["id"].is_null()) {
|
||||||
// 这是一个响应
|
// This is a response
|
||||||
json id = message["id"];
|
json id = message["id"];
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(response_mutex_);
|
std::lock_guard<std::mutex> lock(response_mutex_);
|
||||||
|
@ -708,9 +708,9 @@ void stdio_client::read_thread_func() {
|
||||||
LOG_WARNING("Received response for unknown request ID: ", id);
|
LOG_WARNING("Received response for unknown request ID: ", id);
|
||||||
}
|
}
|
||||||
} else if (message.contains("method")) {
|
} else if (message.contains("method")) {
|
||||||
// 这是一个请求或通知
|
// This is a request or notification
|
||||||
LOG_INFO("Received request/notification: ", message["method"]);
|
LOG_INFO("Received request/notification: ", message["method"]);
|
||||||
// 目前不处理服务器发来的请求
|
// Currently not handling requests from the server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const json::exception& e) {
|
} catch (const json::exception& e) {
|
||||||
|
@ -719,12 +719,12 @@ void stdio_client::read_thread_func() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (bytes_read == 0) {
|
} else if (bytes_read == 0) {
|
||||||
// 管道已关闭
|
// Pipe is closed
|
||||||
LOG_WARNING("Pipe closed by server");
|
LOG_WARNING("Pipe closed by server");
|
||||||
break;
|
break;
|
||||||
} else if (bytes_read == -1) {
|
} else if (bytes_read == -1) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
// 非阻塞模式下没有数据可读
|
// No data to read in non-blocking mode
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("Error reading from pipe: ", strerror(errno));
|
LOG_ERROR("Error reading from pipe: ", strerror(errno));
|
||||||
|
@ -746,7 +746,7 @@ json stdio_client::send_jsonrpc(const request& req) {
|
||||||
std::string req_str = req_json.dump() + "\n";
|
std::string req_str = req_json.dump() + "\n";
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
// Windows实现
|
// Windows implementation
|
||||||
DWORD bytes_written;
|
DWORD bytes_written;
|
||||||
BOOL success = WriteFile(stdin_pipe_[1], req_str.c_str(), static_cast<DWORD>(req_str.size()), &bytes_written, NULL);
|
BOOL success = WriteFile(stdin_pipe_[1], req_str.c_str(), static_cast<DWORD>(req_str.size()), &bytes_written, NULL);
|
||||||
|
|
||||||
|
@ -755,7 +755,7 @@ json stdio_client::send_jsonrpc(const request& req) {
|
||||||
throw mcp_exception(error_code::internal_error, "Failed to write to pipe");
|
throw mcp_exception(error_code::internal_error, "Failed to write to pipe");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// POSIX实现
|
// POSIX implementation
|
||||||
ssize_t bytes_written = write(stdin_pipe_[1], req_str.c_str(), req_str.size());
|
ssize_t bytes_written = write(stdin_pipe_[1], req_str.c_str(), req_str.size());
|
||||||
|
|
||||||
if (bytes_written != static_cast<ssize_t>(req_str.size())) {
|
if (bytes_written != static_cast<ssize_t>(req_str.size())) {
|
||||||
|
@ -764,12 +764,12 @@ json stdio_client::send_jsonrpc(const request& req) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 如果是通知,不需要等待响应
|
// If this is a notification, no need to wait for a response
|
||||||
if (req.is_notification()) {
|
if (req.is_notification()) {
|
||||||
return json::object();
|
return json::object();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Promise和Future
|
// Create Promise and Future
|
||||||
std::promise<json> response_promise;
|
std::promise<json> response_promise;
|
||||||
std::future<json> response_future = response_promise.get_future();
|
std::future<json> response_future = response_promise.get_future();
|
||||||
|
|
||||||
|
@ -778,7 +778,7 @@ json stdio_client::send_jsonrpc(const request& req) {
|
||||||
pending_requests_[req.id] = std::move(response_promise);
|
pending_requests_[req.id] = std::move(response_promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待响应,设置超时
|
// Wait for response, set timeout
|
||||||
const auto timeout = std::chrono::seconds(30);
|
const auto timeout = std::chrono::seconds(30);
|
||||||
auto status = response_future.wait_for(timeout);
|
auto status = response_future.wait_for(timeout);
|
||||||
|
|
||||||
|
|
|
@ -1,69 +1,59 @@
|
||||||
# MCP 单元测试
|
# MCP Unit Tests
|
||||||
|
|
||||||
本目录包含 Model Context Protocol (MCP) 实现的单元测试,基于规范 2024-11-05。
|
This directory contains unit tests for the Model Context Protocol (MCP) implementation, based on the 2024-11-05 specification.
|
||||||
|
|
||||||
## 测试内容
|
## Building and Running Tests
|
||||||
|
|
||||||
测试文件包括:
|
### Building Tests
|
||||||
|
|
||||||
- `test_mcp_message.cpp`: 测试消息相关功能
|
|
||||||
- `test_mcp_tool.cpp`: 测试工具相关功能
|
|
||||||
- `test_mcp_resource.cpp`: 测试资源相关功能
|
|
||||||
- `test_mcp_client.cpp`: 测试客户端相关功能
|
|
||||||
- `test_mcp_server.cpp`: 测试服务器相关功能
|
|
||||||
|
|
||||||
## 构建和运行测试
|
|
||||||
|
|
||||||
### 构建测试
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 在项目根目录创建构建目录
|
# Create a build directory in the project root
|
||||||
mkdir -p build && cd build
|
mkdir -p build && cd build
|
||||||
|
|
||||||
# 配置项目
|
# Configure the project
|
||||||
cmake ..
|
cmake ..
|
||||||
|
|
||||||
# 构建项目和测试
|
# Build the project and tests
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行测试
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 运行所有测试
|
# Run all tests
|
||||||
make run_tests
|
make run_tests
|
||||||
|
|
||||||
# 或者直接运行测试可执行文件
|
# Or directly run the test executable
|
||||||
./test/mcp_tests
|
./test/mcp_tests
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行特定测试
|
### Running Specific Tests
|
||||||
|
|
||||||
要运行特定的测试,可以使用 Google Test 的过滤功能:
|
To run specific tests, you can use Google Test's filtering capability:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 运行所有消息相关测试
|
# Run all message-related tests
|
||||||
./test/mcp_tests --gtest_filter=McpMessageTest.*
|
./test/mcp_tests --gtest_filter=McpMessageTest.*
|
||||||
|
|
||||||
# 运行所有工具相关测试
|
# Run all tool-related tests
|
||||||
./test/mcp_tests --gtest_filter=McpToolTest.*
|
./test/mcp_tests --gtest_filter=McpToolTest.*
|
||||||
|
|
||||||
# 运行所有资源相关测试
|
# Run all resource-related tests
|
||||||
./test/mcp_tests --gtest_filter=McpResourceTest.*
|
./test/mcp_tests --gtest_filter=McpResourceTest.*
|
||||||
|
|
||||||
# 运行所有客户端相关测试
|
# Run all client-related tests
|
||||||
./test/mcp_tests --gtest_filter=ClientTest.*
|
./test/mcp_tests --gtest_filter=ClientTest.*
|
||||||
|
|
||||||
# 运行所有服务器相关测试
|
# Run all server-related tests
|
||||||
./test/mcp_tests --gtest_filter=ServerTest.*
|
./test/mcp_tests --gtest_filter=ServerTest.*
|
||||||
```
|
```
|
||||||
|
|
||||||
## 测试依赖
|
## Test Dependencies
|
||||||
|
|
||||||
测试使用 Google Test 框架,该框架会在构建时自动下载和配置。
|
Tests use the Google Test framework, which is automatically downloaded and configured during the build process.
|
||||||
|
|
||||||
## 注意事项
|
## Notes
|
||||||
|
|
||||||
- 部分测试需要网络功能,确保本地端口(如 8090、8095)未被占用
|
- Some tests require network functionality, ensure that local ports (such as 8090, 8095) are not in use
|
||||||
- 客户端和服务器测试会启动实际的服务器和客户端进行交互测试
|
- Client and server tests will start actual servers and clients for interaction testing
|
||||||
- 资源测试会在临时目录创建文件,测试完成后会自动清理
|
- Resource tests will create files in a temporary directory, which will be automatically cleaned up after testing
|
Loading…
Reference in New Issue