From: 孙春阳 Date: Wed, 6 Aug 2025 11:57:54 +0000 (+0800) Subject: 添加websocket测试指令 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=a20e0afd74f43066f1b48811d4e68b334645f21d;p=mirrors%2FFD230.git 添加websocket测试指令 lwipopts.h 调整参数 --- diff --git a/code/sdk/drivers/peripheral/slip/CMakeLists.txt b/code/sdk/drivers/peripheral/slip/CMakeLists.txt index 7aad39318..f5d97a53a 100644 --- a/code/sdk/drivers/peripheral/slip/CMakeLists.txt +++ b/code/sdk/drivers/peripheral/slip/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/asio.c ${CMAKE_CURRENT_SOURCE_DIR}/slipif.c ${CMAKE_CURRENT_SOURCE_DIR}/uart_slip.c + ${CMAKE_CURRENT_SOURCE_DIR}/websocket_test.c # ${CMAKE_CURRENT_SOURCE_DIR}/udp_server.c # ${LWIP_PORT_SOURCES} ) @@ -20,6 +21,7 @@ set(PUBLIC_HEADER ${ROOT_DIR}/open_source/openharmony/third_party/lwip/port ) set(PUBLIC_DEFINES +SUPPORT_WEBSOCKET_TEST ) set(PRIVATE_HEADER ) diff --git a/code/sdk/drivers/peripheral/slip/cat1Service/cat1_at_cmd.cpp b/code/sdk/drivers/peripheral/slip/cat1Service/cat1_at_cmd.cpp index f4f399e2b..745dce651 100644 --- a/code/sdk/drivers/peripheral/slip/cat1Service/cat1_at_cmd.cpp +++ b/code/sdk/drivers/peripheral/slip/cat1Service/cat1_at_cmd.cpp @@ -27,6 +27,8 @@ extern "C" { #include "app_at_process.h" #include "cat1_ws.h" +#include "../websocket_test.h" +#include "cmsis_os2.h" static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, @@ -685,6 +687,95 @@ static uint32_t sendfotaurl(uint8_t *para, uint32_t paraLen, char *strCommonCmd) return 0; } +/** + * WebSocket测试AT命令处理函数 + * 格式: AT^TESTWEBSOCKET=, + */ +static uint32_t test_websocket(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + // 将参数转换为字符串 + char params[256] = {0}; + if (paraLen > 0 && paraLen < sizeof(params)) { + memcpy(params, para, paraLen); + params[paraLen] = '\0'; + } + + printf("WebSocket测试命令: AT^TESTWEBSOCKET=%s\n", params); + + // 检查当前测试状态 + int current_state = get_websocket_test_state(); + printf("当前测试状态: %d\n", current_state); + + if (current_state != 1) //TEST_STATE_RUNNING + { + int result = at_test_websocket_handler("AT^TESTWEBSOCKET", params); + if (result == 0) { + printf("WebSocket测试启动成功\n"); + } else { + printf("WebSocket测试启动失败,错误码: %d\n", result); + } + }else{ + printf("WebSocket测试已经运行\n"); + } + + return ERRCODE_SUCC; +} + + + +/** + * WebSocket停止测试AT命令处理函数 + * 格式: AT^STOPWEBSOCKET + */ +static uint32_t stop_websocket_test_cmd(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + UNUSED(para); + UNUSED(paraLen); + + printf("WebSocket停止测试命令: AT^STOPWEBSOCKET\n"); + + // 检查当前测试状态 + int current_state = get_websocket_test_state(); + printf("停止前测试状态: %d\n", current_state); + + // 直接调用停止函数,减少中间变量 + stop_websocket_test(); + + // 简化状态检查 + printf("停止后测试状态: %d\n", get_websocket_test_state()); + printf("WebSocket测试已停止\n"); + return ERRCODE_SUCC; +} + +/** + * WebSocket测试状态查询AT命令处理函数 + * 格式: AT^WEBSOCKETSTATUS + */ +static uint32_t websocket_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + UNUSED(para); + UNUSED(paraLen); + + printf("WebSocket状态查询命令: AT^WEBSOCKETSTATUS\n"); + + // 获取测试状态 + int state = get_websocket_test_state(); + uint32_t send_count, recv_count, lost_count; + get_websocket_test_stats(&send_count, &recv_count, &lost_count); + + const char *state_str[] = {"空闲", "运行中", "已完成", "错误"}; + printf("测试状态: %s\n", state_str[state]); + printf("发送数据包: %u\n", send_count); + printf("接收数据包: %u\n", recv_count); + printf("丢失数据包: %u\n", lost_count); + + return ERRCODE_SUCC; +} + static const at_cmd_table_t g_cat1_cmd[] = { @@ -722,6 +813,9 @@ static const at_cmd_table_t g_cat1_cmd[] = { { (char *)"AT^SENDTTTS", sendttstxt, (char *)"1" }, { (char *)"AT^STOPTEST", stopwstest, (char *)"1" }, { (char *)"AT^OTAHTTP", sendfotaurl, (char *)"1" }, + { (char *)"AT^TESTWEBSOCKET", test_websocket, (char *)"1" }, + { (char *)"AT^STOPWEBSOCKET", stop_websocket_test_cmd, (char *)"1" }, + { (char *)"AT^WEBSOCKETSTATUS", websocket_status, (char *)"1" }, }; diff --git a/code/sdk/drivers/peripheral/slip/websocket_test.c b/code/sdk/drivers/peripheral/slip/websocket_test.c new file mode 100644 index 000000000..d73e7cea9 --- /dev/null +++ b/code/sdk/drivers/peripheral/slip/websocket_test.c @@ -0,0 +1,548 @@ +/* + * WebSocket测试程序 + * 基于LiteOS-M系统 + * 通过AT^TESTWEBSOCKET命令执行测试 + */ + +#ifdef SUPPORT_WEBSOCKET_TEST + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include +#include +#include +#include "cmsis_os2.h" +#include "bk_websocket.h" +#include "cat1_ctl.h" +#include "custom_runtime.h" +#include "wearable_log.h" + +// 测试配置 +#define TEST_DATA_SIZE 228 +#define MAX_TEST_COUNT 1000 +#define THREAD_STACK_SIZE 4096 +#define THREAD_PRIORITY 10 + +// 全局变量 +static osThreadId_t send_thread_id = NULL; +static osThreadId_t recv_thread_id = NULL; +static osMutexId_t test_mutex = NULL; +static osSemaphoreId_t test_semaphore = NULL; + +static WebSocketClient *test_client = NULL; +static bool test_running = false; +static uint32_t send_count = 0; +static uint32_t recv_count = 0; +static uint32_t lost_count = 0; +static uint8_t expected_data[TEST_DATA_SIZE]; +static uint8_t received_data[TEST_DATA_SIZE]; + +// 测试状态 +typedef enum { + TEST_STATE_IDLE, + TEST_STATE_RUNNING, + TEST_STATE_COMPLETED, + TEST_STATE_ERROR +} test_state_t; + +static test_state_t test_state = TEST_STATE_IDLE; + +// 函数声明 +static void websocket_send_thread(void *argument); +static void websocket_recv_thread(void *argument); +static bool init_websocket_test(const char *server_addr, uint16_t port); +static void cleanup_websocket_test(void); +static bool send_test_data(uint8_t data_value); +static bool receive_test_data(uint8_t *data_value); +static void print_test_results(void); +static void check_memory_status(void); + +/** + * WebSocket发送线程 + */ +static void websocket_send_thread(void *argument) +{ + uint8_t data_value = 0; + uint32_t send_interval = 100; // 100ms间隔 + uint32_t error_count = 0; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket发送线程启动"); + + while (test_running) { + // 检查客户端是否有效 + if (!test_client || test_client->sockfd < 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送线程: 客户端无效"); + break; + } + + if (send_test_data(data_value)) { + send_count++; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "发送数据包 %u, 值: %u", send_count, data_value); + data_value = (data_value + 1) % 256; // 0-255循环 + error_count = 0; // 重置错误计数 + } else { + error_count++; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送测试数据失败, 错误计数: %u", error_count); + + // 如果连续失败超过5次,退出循环 + if (error_count > 5) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送线程: 连续失败次数过多,退出"); + break; + } + } + + // 检查是否需要停止 + if (!test_running) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "发送线程: 收到停止信号"); + break; + } + + osDelay(send_interval); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket发送线程结束"); + return; +} + +/** + * WebSocket接收线程 + */ +static void websocket_recv_thread(void *argument) +{ + uint8_t data_value = 0; + uint32_t timeout_count = 0; + uint32_t error_count = 0; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket接收线程启动"); + + while (test_running) { + // 检查客户端是否有效 + if (!test_client || test_client->sockfd < 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收线程: 客户端无效"); + break; + } + + if (receive_test_data(&data_value)) { + recv_count++; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "接收数据包 %u, 值: %u", recv_count, data_value); + + // 检查数据是否正确 + bool data_correct = true; + for (int i = 0; i < TEST_DATA_SIZE; i++) { + if (received_data[i] != data_value) { + data_correct = false; + break; + } + } + + if (!data_correct) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据验证失败"); + lost_count++; + } + + timeout_count = 0; + error_count = 0; // 重置错误计数 + } else { + timeout_count++; + error_count++; + if (timeout_count > 10) { // 1秒超时 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据超时"); + break; + } + + // 如果连续错误超过20次,退出循环 + if (error_count > 20) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收线程: 连续错误次数过多,退出"); + break; + } + } + + // 检查是否需要停止 + if (!test_running) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "接收线程: 收到停止信号"); + break; + } + + osDelay(100); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket接收线程结束"); + return; +} + +/** + * 初始化WebSocket测试 + */ +static bool init_websocket_test(const char *server_addr, uint16_t port) +{ + char url[256]; + snprintf(url, sizeof(url), "ws://%s:%d/", server_addr, port); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "连接WebSocket服务器: %s", url); + + // 创建WebSocket客户端 + test_client = init_websocket_client(url, NULL, 1); // 启用DNS解析 + if (test_client == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket客户端创建失败"); + return false; + } + + // 创建互斥锁 + test_mutex = osMutexNew(NULL); + if (test_mutex == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "互斥锁创建失败"); + lwip_close_websocket_client(test_client); + test_client = NULL; + return false; + } + + // 创建信号量 + test_semaphore = osSemaphoreNew(1, 1, NULL); + if (test_semaphore == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "信号量创建失败"); + osMutexDelete(test_mutex); + lwip_close_websocket_client(test_client); + test_client = NULL; + return false; + } + + // 设置运行标志(在创建线程之前) + test_running = true; + test_state = TEST_STATE_RUNNING; + + // 创建发送线程 + osThreadAttr_t send_attr = { + .name = "websocket_send", + .stack_size = THREAD_STACK_SIZE, + .priority = osPriorityNormal, + }; + send_thread_id = osThreadNew(websocket_send_thread, NULL, &send_attr); + if (send_thread_id == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送线程创建失败"); + cleanup_websocket_test(); + return false; + } + + // 创建接收线程 + osThreadAttr_t recv_attr = { + .name = "websocket_recv", + .stack_size = THREAD_STACK_SIZE, + .priority = osPriorityNormal, + }; + recv_thread_id = osThreadNew(websocket_recv_thread, NULL, &recv_attr); + if (recv_thread_id == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收线程创建失败"); + cleanup_websocket_test(); + return false; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试初始化成功"); + return true; +} + +/** + * 清理WebSocket测试 + */ +static void cleanup_websocket_test(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "开始清理WebSocket测试"); + + // 首先停止运行标志 + test_running = false; + + // 等待一段时间让线程自然结束 + osDelay(100); + + // 安全地等待线程结束 + if (send_thread_id) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "等待发送线程结束"); + osThreadJoin(send_thread_id); + send_thread_id = NULL; + } + + if (recv_thread_id) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "等待接收线程结束"); + osThreadJoin(recv_thread_id); + recv_thread_id = NULL; + } + + // 关闭WebSocket连接 - 在删除线程相关资源之前 + if (test_client) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "关闭WebSocket连接"); + lwip_close_websocket_client(test_client); + test_client = NULL; + } + + // 删除信号量 + if (test_semaphore) { + osSemaphoreDelete(test_semaphore); + test_semaphore = NULL; + } + + // 删除互斥锁 - 最后删除 + if (test_mutex) { + osMutexDelete(test_mutex); + test_mutex = NULL; + } + + test_state = TEST_STATE_IDLE; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试清理完成"); +} + +/** + * 发送测试数据 + */ +static bool send_test_data(uint8_t data_value) +{ + if (!test_client || !test_running) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送数据失败: 客户端或运行状态无效"); + return false; + } + + // 添加额外的安全检查 + if (test_client->sockfd < 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送数据失败: 无效的socket"); + return false; + } + + // 验证测试数据缓冲区 - 数组地址总是为true,不需要检查 + // if (!expected_data) { + // WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送数据失败: 测试数据缓冲区无效"); + // return false; + // } + + // 准备测试数据 + memset(expected_data, data_value, TEST_DATA_SIZE); + + // 添加内存检查 + if (TEST_DATA_SIZE > 65536) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送数据失败: 数据大小过大"); + return false; + } + + // 检查内存状态 + check_memory_status(); + + // 发送数据前增加延迟,避免过于频繁的发送 + osDelay(5); + + // 发送数据 + int result = send_websocket_message(test_client, expected_data, TEST_DATA_SIZE, WS_BINARY); + if (result != 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "发送数据失败: %d", result); + return false; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "发送数据成功: 值=%u, 大小=%d", data_value, TEST_DATA_SIZE); + return true; +} + +/** + * 接收测试数据 + */ +static bool receive_test_data(uint8_t *data_value) +{ + if (!test_client || !test_running) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据失败: 客户端或运行状态无效"); + return false; + } + + WebSocketMessageType type; + int result = receive_websocket_message(test_client, received_data, TEST_DATA_SIZE, &type); + + if (result <= 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据失败: result=%d", result); + return false; + } + + if (type != WS_BINARY) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据类型错误: type=%d", type); + return false; + } + + // 检查数据大小 + if (result != TEST_DATA_SIZE) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "接收数据大小错误: %d", result); + return false; + } + + // 提取数据值 + *data_value = received_data[0]; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "接收数据成功: 值=%u, 大小=%d", *data_value, result); + return true; +} + +/** + * 打印测试结果 + */ +/** + * 检查内存状态 + */ +static void check_memory_status(void) +{ + // 检查内存状态 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket: 检查内存状态"); + + // 检查内存池状态 - 暂时注释掉,避免编译错误 + // extern void ws_mem_pool_status(void); + // ws_mem_pool_status(); + + // 检查系统内存状态 - 暂时注释掉,避免编译错误 + // extern size_t xPortGetFreeHeapSize(void); + // size_t free_heap = xPortGetFreeHeapSize(); + // WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket: 系统空闲堆内存: %u 字节", (uint32_t)free_heap); + + // 如果内存不足,增加延迟 + // if (free_heap < 1024) { + // WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "WebSocket: 系统内存不足,增加延迟"); + // osDelay(50); + // } else { + osDelay(10); + // } +} + +/** + * 打印测试结果 + */ +static void print_test_results(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "=== WebSocket测试结果 ==="); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "发送数据包数: %u", send_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "接收数据包数: %u", recv_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "丢失数据包数: %u", lost_count); + + if (send_count > 0) { + float loss_rate = (float)lost_count / send_count * 100.0f; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "丢包率: %.2f%%", loss_rate); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "========================"); +} + +/** + * AT命令处理函数 + */ +int at_test_websocket_handler(const char *cmd, const char *params) +{ + if (strncmp(cmd, "AT^TESTWEBSOCKET", 16) != 0) { + return -1; + } + + // 解析参数: AT^TESTWEBSOCKET=, + char server_addr[64] = {0}; + uint16_t port1 = 0, port2 = 0; + + if (sscanf(params, "%63[^,],%hu", server_addr, &port1) != 2) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "参数格式错误,正确格式: AT^TESTWEBSOCKET=,"); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "开始WebSocket测试"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "服务器地址: %s", server_addr); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "端口1: %u", port1); + + // 检查当前状态 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "当前测试状态: %d", test_state); + if (test_state != TEST_STATE_IDLE) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "测试正在进行中,强制清理并重新开始"); + // 强制清理 + test_running = false; + if (test_client) { + lwip_close_websocket_client(test_client); + test_client = NULL; + } + test_state = TEST_STATE_IDLE; + send_count = 0; + recv_count = 0; + lost_count = 0; + } + + // 初始化测试 + if (!init_websocket_test(server_addr, port1)) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket测试初始化失败"); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试已启动,使用AT^STOPWEBSOCKET停止测试"); + return 0; +} + +/** + * 手动启动WebSocket测试 + */ +int start_websocket_test(const char *server_addr, uint16_t port, uint32_t duration_ms) +{ + if (test_state != TEST_STATE_IDLE) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "测试正在进行中"); + return -1; + } + + if (!init_websocket_test(server_addr, port)) { + return -1; + } + + // 如果指定了持续时间,则运行指定时间后自动停止 + if (duration_ms > 0) { + osDelay(duration_ms); + cleanup_websocket_test(); + print_test_results(); + } + + return 0; +} + +/** + * 停止WebSocket测试 + */ +void stop_websocket_test(void) +{ + if (test_state == TEST_STATE_RUNNING) { + cleanup_websocket_test(); + print_test_results(); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试已停止"); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试当前未运行"); + } +} + +/** + * 获取测试状态 + */ +int get_websocket_test_state(void) +{ + return (int)test_state; +} + +/** + * 获取测试统计信息 + */ +void get_websocket_test_stats(uint32_t *send_count_out, uint32_t *recv_count_out, uint32_t *lost_count_out) +{ + if (send_count_out) *send_count_out = send_count; + if (recv_count_out) *recv_count_out = recv_count; + if (lost_count_out) *lost_count_out = lost_count; +} + +/** + * 模块初始化 + */ +void websocket_test_init(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket测试模块初始化"); + + // 注册AT命令处理器 + // 注意:这里需要根据实际的AT命令系统进行适配 + // extern int register_at_command_handler(const char *cmd, at_command_handler_t handler); + // register_at_command_handler("TESTWEBSOCKET", at_test_websocket_handler); +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SUPPORT_WEBSOCKET_TEST */ \ No newline at end of file diff --git a/code/sdk/drivers/peripheral/slip/websocket_test.h b/code/sdk/drivers/peripheral/slip/websocket_test.h new file mode 100644 index 000000000..20a89cf57 --- /dev/null +++ b/code/sdk/drivers/peripheral/slip/websocket_test.h @@ -0,0 +1,61 @@ +/* + * WebSocket测试模块头文件 + * 基于LiteOS-M系统 + */ + +#ifndef __WEBSOCKET_TEST_H +#define __WEBSOCKET_TEST_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * WebSocket测试模块初始化 + */ +void websocket_test_init(void); + +/** + * AT命令处理函数 + * @param cmd AT命令字符串 + * @param params 命令参数 + * @return 0表示成功,-1表示失败 + */ +int at_test_websocket_handler(const char *cmd, const char *params); + +/** + * 手动启动WebSocket测试 + * @param server_addr 服务器地址 + * @param port 服务器端口 + * @param duration_ms 测试持续时间(毫秒) + * @return 0表示成功,-1表示失败 + */ +int start_websocket_test(const char *server_addr, uint16_t port, uint32_t duration_ms); + +/** + * 停止WebSocket测试 + */ +void stop_websocket_test(void); + +/** + * 获取测试状态 + * @return 0-空闲, 1-运行中, 2-已完成, 3-错误 + */ +int get_websocket_test_state(void); + +/** + * 获取测试统计信息 + * @param send_count 发送数据包数 + * @param recv_count 接收数据包数 + * @param lost_count 丢失数据包数 + */ +void get_websocket_test_stats(uint32_t *send_count, uint32_t *recv_count, uint32_t *lost_count); + +#ifdef __cplusplus +} +#endif + +#endif /* __WEBSOCKET_TEST_H */ \ No newline at end of file diff --git a/code/sdk/open_source/openharmony/third_party/lwip/port/lwip-2.1/porting/include/lwip/lwipopts.h b/code/sdk/open_source/openharmony/third_party/lwip/port/lwip-2.1/porting/include/lwip/lwipopts.h index 7d39869de..09a23d6d2 100644 --- a/code/sdk/open_source/openharmony/third_party/lwip/port/lwip-2.1/porting/include/lwip/lwipopts.h +++ b/code/sdk/open_source/openharmony/third_party/lwip/port/lwip-2.1/porting/include/lwip/lwipopts.h @@ -35,6 +35,9 @@ // lwIP debug options, comment the ones you don't want #define LWIP_DEBUG 1 #if LWIP_DEBUG + +#define DEBUG_TCP 0 + #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF #define PBUF_DEBUG LWIP_DBG_OFF @@ -56,12 +59,13 @@ #define TCP_ERR_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF -#define TCP_RTO_DEBUG LWIP_DBG_OFF -#define TCP_CWND_DEBUG LWIP_DBG_OFF +#if DEBUG_TCP +#define TCP_RTO_DEBUG LWIP_DBG_ON +#define TCP_CWND_DEBUG LWIP_DBG_ON #define TCP_WND_DEBUG LWIP_DBG_OFF -#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_ON #define TCP_RST_DEBUG LWIP_DBG_OFF -#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_ON #define TCP_SACK_DEBUG LWIP_DBG_OFF #define TCP_TLP_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF @@ -81,6 +85,33 @@ #define ALTCP_MBEDTLS_MEM_DEBUG LWIP_DBG_ON #define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON #define ALTCP_MBEDTLS_LIB_DEBUG LWIP_DBG_ON +#else +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_SACK_DEBUG LWIP_DBG_OFF +#define TCP_TLP_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF +#define AUTOIP_DEBUG LWIP_DBG_OFF +#define DNS_DEBUG LWIP_DBG_OFF +#define TFTP_DEBUG LWIP_DBG_OFF +#define SYS_ARCH_DEBUG LWIP_DBG_OFF +#define SNTP_DEBUG LWIP_DBG_OFF +#define IP6_DEBUG LWIP_DBG_OFF +#define DHCP6_DEBUG LWIP_DBG_OFF +#define DRV_STS_DEBUG LWIP_DBG_OFF +#define HTTPC_DEBUG LWIP_DBG_OFF + +#define ALTCP_MBEDTLS_MEM_DEBUG LWIP_DBG_OFF +#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_OFF +#define ALTCP_MBEDTLS_LIB_DEBUG LWIP_DBG_OFF +#endif #endif @@ -94,6 +125,23 @@ //多线程安全支持 #define LWIP_NETCONN_SEM_PER_THREAD 1 + + +// Options in old lwipopts.h but kept in Defaults with new opt.h +#define IP_FORWARD 0 +#define LWIP_DBG_TYPES_ON LWIP_DBG_ON +#define LWIP_ICMP 1 +#define LWIP_NETCONN 1 +#define LWIP_SOCKET 1 +#define LWIP_STATS 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define NO_SYS 0 + +#define LWIP_ALTCP 1 +#define LWIP_ALTCP_TLS 1 +#define LWIP_ALTCP_TLS_MBEDTLS 1 + // Options for old lwipopts.h #define MEM_SIZE (512*1024) //(4*1024*1024) // (512*1024) #define IP_FRAG_MAX_MTU 1500 @@ -124,7 +172,7 @@ #define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_TX_SINGLE_PBUF 1 -#define LWIP_NETCONN_FULLDUPLEX 1 // Caution +#define LWIP_NETCONN_FULLDUPLEX 1 // 确保全双工支持 #define LWIP_POSIX_SOCKETS_IO_NAMES 0 #define LWIP_TCP_KEEPALIVE 1 #define RECV_BUFSIZE_DEFAULT 65535 @@ -171,14 +219,14 @@ #define LWIP_SO_SNDTIMEO 1 #define LWIP_STATS_DISPLAY 1 #define MEM_LIBC_MALLOC 1 -#define MEMP_NUM_ARP_QUEUE (65535 * LWIP_CONFIG_NUM_SOCKETS / (IP_FRAG_MAX_MTU - 20 - 8)) -#define MEMP_NUM_NETBUF (65535 * 3 * LWIP_CONFIG_NUM_SOCKETS / (IP_FRAG_MAX_MTU - 20 - 8)) +#define MEMP_NUM_ARP_QUEUE (65535UL * LWIP_CONFIG_NUM_SOCKETS / (IP_FRAG_MAX_MTU - 20 - 8)) +#define MEMP_NUM_NETBUF (65535UL * 3 * LWIP_CONFIG_NUM_SOCKETS / (IP_FRAG_MAX_MTU - 20 - 8)) #define MEMP_NUM_NETCONN LWIP_CONFIG_NUM_SOCKETS #define MEMP_NUM_PBUF LWIP_CONFIG_NUM_SOCKETS*2 #define MEMP_NUM_RAW_PCB LWIP_CONFIG_NUM_SOCKETS -#define MEMP_NUM_REASSDATA (IP_REASS_MAX_MEM_SIZE / 65535) -#define MEMP_NUM_TCPIP_MSG_API 64 -#define MEMP_NUM_TCPIP_MSG_INPKT 512 +#define MEMP_NUM_REASSDATA (IP_REASS_MAX_MEM_SIZE / 65535UL) +#define MEMP_NUM_TCPIP_MSG_API 64 +#define MEMP_NUM_TCPIP_MSG_INPKT 512 #define MEMP_NUM_TCP_PCB LWIP_CONFIG_NUM_SOCKETS #define MEMP_NUM_TCP_PCB_LISTEN LWIP_CONFIG_NUM_SOCKETS #define MEMP_NUM_UDP_PCB LWIP_CONFIG_NUM_SOCKETS @@ -188,33 +236,27 @@ #define TCPIP_MBOX_SIZE 512 #define TCPIP_THREAD_PRIO 25 //osPriorityBelowNormal 16 #define TCPIP_THREAD_STACKSIZE 0x2000 -#define TCP_MAXRTX 12 //max 12 -#define LWIP_TCP_RTO_TIME 1500 +#define TCP_MAXRTX 6 // 从8减少到6,减少重传次数 +#define LWIP_TCP_RTO_TIME 1500 // 从1000减少到800,减少重传超时时间 +/* 优化TCP重传参数,减少AI音频传输延迟 */ +#define TCP_SYNMAXRTX 6 // 从3减少到2,减少SYN重传次数 +#define TCP_QUEUE_OOSEQ LWIP_TCP #define TCP_MSS (IP_FRAG_MAX_MTU - 20 - 20) -#define TCP_SND_BUF (10* TCP_MSS) -#define TCP_SND_QUEUELEN (2*(TCP_SND_BUF/TCP_MSS)) -#define TCP_SNDLOWAT LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -#define TCP_SNDQUEUELOWAT LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5) +/* 针对AI音频传输优化TCP发送缓冲区大小 */ +#define TCP_SND_BUF (10* TCP_MSS) // 增加到16*,约11680字节,提高发送缓冲区 +/* 严格控制TCP发送队列长度,防止积压 */ +#define TCP_SND_QUEUELEN (2*(TCP_SND_BUF/TCP_MSS)) // 增加到8*,提高队列容量 +#define TCP_SNDLOWAT LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) /* 降低低水位阈值 */ +#define TCP_SNDQUEUELOWAT LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5) /* 进一步降低低水位阈值 */ #define TCP_TTL 255 -#define TCP_WND (TCP_SND_BUF/2) +/* 优化TCP窗口大小,匹配发送缓冲区 */ +#define TCP_WND (TCP_SND_BUF/2) // 保持SND_BUF/2,约5840字节,提高接收窗口 #define UDP_TTL 255 -#define MEMP_NUM_TCP_SEG (((TCP_SND_BUF * 3 / 2) + TCP_WND) * LWIP_CONFIG_NUM_SOCKETS / TCP_MSS) +// 增加TCP段内存池大小 +#define MEMP_NUM_TCP_SEG (LWIP_CONFIG_NUM_SOCKETS * 16) // 从8增加到16,提高TCP段处理能力 + -// Options in old lwipopts.h but kept in Defaults with new opt.h -#define IP_FORWARD 0 -#define LWIP_DBG_TYPES_ON LWIP_DBG_ON -#define LWIP_ICMP 1 -#define LWIP_NETCONN 1 -#define LWIP_SOCKET 1 -#define LWIP_STATS 1 -#define LWIP_TCP 1 -#define LWIP_UDP 1 -#define NO_SYS 0 -#define TCP_QUEUE_OOSEQ LWIP_TCP -#define LWIP_ALTCP 1 -#define LWIP_ALTCP_TLS 1 -#define LWIP_ALTCP_TLS_MBEDTLS 1 #if defined LWIP_STATS_DISPLAY && !LWIP_STATS @@ -241,7 +283,12 @@ // Options for enhancement code, same for old lwipopts.h #define LWIP_NETIF_PROMISC 0 +// 修复后 +#ifdef LOSCFG_NET_LWIP_SACK_TFTP #define LWIP_TFTP LOSCFG_NET_LWIP_SACK_TFTP +#else +#define LWIP_TFTP 0 +#endif #define LWIP_DHCPS 1 #define LWIP_ENABLE_NET_CAPABILITY 1 #define LWIP_ENABLE_CAP_NET_BROADCAST 0 @@ -250,11 +297,6 @@ #define MEMP_MEM_MALLOC 1 - -// #undef LWIP_SOCKET_SELECT -// #define LWIP_SOCKET_SELECT 1 - - /* MIB2 stats are required to check IPv4 reassembly results */ #define MIB2_STATS 1 #define SLIP_RX_FROM_ISR 1 diff --git a/ws_lwip_test/test_websocket_at_commands.txt b/ws_lwip_test/test_websocket_at_commands.txt new file mode 100644 index 000000000..0d6545c28 --- /dev/null +++ b/ws_lwip_test/test_websocket_at_commands.txt @@ -0,0 +1,19 @@ +# WebSocket AT命令测试脚本 +# 使用方法:在串口终端中依次输入以下命令 + +# 1. 启动WebSocket测试 +AT^TESTWEBSOCKET=snicker.com.cn,8910 + +# 2. 查询测试状态 +AT^WEBSOCKETSTATUS + +# 3. 停止WebSocket测试 +AT^STOPWEBSOCKET + +# 4. 再次查询状态确认已停止 +AT^WEBSOCKETSTATUS + +# 预期输出: +# 启动命令:WebSocket测试已启动,使用AT^STOPWEBSOCKET停止测试 +# 状态查询:显示当前测试状态和统计数据 +# 停止命令:WebSocket测试已停止,并显示最终测试结果 \ No newline at end of file diff --git a/ws_lwip_test/websocket_test_client_native.py b/ws_lwip_test/websocket_test_client_native.py new file mode 100644 index 000000000..a0156b3fe --- /dev/null +++ b/ws_lwip_test/websocket_test_client_native.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python3 +""" +WebSocket客户端测试程序(原生版本) +使用Python标准库socket实现,不依赖websockets库 +""" + +import socket +import ssl +import base64 +import hashlib +import struct +import logging +import argparse +import time +import threading +import sys +import random + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# 测试配置 +TEST_DATA_SIZE = 228 +SEND_INTERVAL = 0.1 # 100ms间隔 + +class WebSocketClient: + """原生WebSocket客户端实现""" + + def __init__(self, host, port): + self.host = host + self.port = port + self.socket = None + self.connected = False + self.last_pong_time = None + + def connect(self): + """连接到WebSocket服务器""" + try: + # 创建TCP连接 + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((self.host, self.port)) + + # 生成WebSocket握手密钥 + key = base64.b64encode(bytes(random.getrandbits(8) for _ in range(16))).decode() + + # 发送WebSocket握手请求 + handshake = ( + f"GET / HTTP/1.1\r\n" + f"Host: {self.host}:{self.port}\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Key: {key}\r\n" + f"Sec-WebSocket-Version: 13\r\n" + f"\r\n" + ) + + self.socket.send(handshake.encode()) + + # 接收握手响应 + response = self.socket.recv(1024).decode() + + if "101 Switching Protocols" in response: + logger.info("WebSocket握手成功") + self.connected = True + return True + else: + logger.error(f"WebSocket握手失败: {response}") + return False + + except Exception as e: + logger.error(f"连接失败: {e}") + return False + + def disconnect(self): + """断开连接""" + if self.socket: + try: + # 发送关闭帧 + close_frame = struct.pack('!BB', 0x88, 0x00) + self.socket.send(close_frame) + self.socket.close() + except: + pass + self.socket = None + self.connected = False + logger.info("WebSocket连接已断开") + + def send_frame(self, data, opcode=0x02): # 0x02 = binary frame + """发送WebSocket帧""" + if not self.connected: + return False + + try: + length = len(data) + if length < 126: + frame = struct.pack('!BB', 0x80 | opcode, 0x80 | length) + elif length < 65536: + frame = struct.pack('!BBH', 0x80 | opcode, 0x80 | 126, length) + else: + frame = struct.pack('!BBQ', 0x80 | opcode, 0x80 | 127, length) + + # 添加掩码 + mask = bytes(random.getrandbits(8) for _ in range(4)) + frame += mask + + # 掩码数据 + masked_data = bytes(data[i] ^ mask[i % 4] for i in range(len(data))) + frame += masked_data + + self.socket.send(frame) + return True + except Exception as e: + logger.error(f"发送帧失败: {e}") + return False + + def send_ping(self): + """发送ping帧""" + try: + # ping帧: FIN=1, RSV=0, Opcode=9, Mask=1, Payload Length=0 + ping_frame = struct.pack('!BB', 0x89, 0x80) # 0x89 = 10001001 (FIN=1, Opcode=9) + + # 添加掩码 + mask = bytes(random.getrandbits(8) for _ in range(4)) + ping_frame += mask + + self.socket.send(ping_frame) + logger.debug("发送ping帧") + return True + except Exception as e: + logger.error(f"发送ping帧失败: {e}") + return False + + def send_pong(self, payload=b''): + """发送pong帧""" + try: + length = len(payload) + if length == 0: + pong_frame = struct.pack('!BB', 0x8A, 0x80) # 0x8A = 10001010 (FIN=1, Opcode=10) + else: + pong_frame = struct.pack('!BB', 0x8A, 0x80 | length) + + # 添加掩码 + mask = bytes(random.getrandbits(8) for _ in range(4)) + pong_frame += mask + + # 掩码数据 + masked_data = bytes(payload[i] ^ mask[i % 4] for i in range(len(payload))) + pong_frame += masked_data + + self.socket.send(pong_frame) + logger.debug("发送pong帧") + return True + except Exception as e: + logger.error(f"发送pong帧失败: {e}") + return False + + def receive_frame(self): + """接收WebSocket帧""" + if not self.connected: + return None + + try: + # 读取帧头 + header = self.socket.recv(2) + if len(header) < 2: + return None + + fin = (header[0] & 0x80) != 0 + opcode = header[0] & 0x0F + masked = (header[1] & 0x80) != 0 + payload_length = header[1] & 0x7F + + # 读取扩展长度 + if payload_length == 126: + length_bytes = self.socket.recv(2) + payload_length = struct.unpack('!H', length_bytes)[0] + elif payload_length == 127: + length_bytes = self.socket.recv(8) + payload_length = struct.unpack('!Q', length_bytes)[0] + + # 读取掩码 + mask = None + if masked: + mask = self.socket.recv(4) + + # 读取数据 + data = b'' + remaining = payload_length + while remaining > 0: + chunk = self.socket.recv(min(remaining, 4096)) + if not chunk: + break + data += chunk + remaining -= len(chunk) + + # 解掩码 + if masked and mask: + data = bytes(data[i] ^ mask[i % 4] for i in range(len(data))) + + # 处理不同类型的帧 + if opcode == 0x09: # ping帧 + logger.debug("收到ping帧,发送pong响应") + self.send_pong(data) # 将ping的payload作为pong的payload返回 + return None + elif opcode == 0x0A: # pong帧 + logger.debug("收到pong帧") + self.last_pong_time = time.time() + return None + elif opcode == 0x08: # close帧 + logger.info("收到关闭帧") + self.connected = False + return None + elif opcode == 0x02: # binary帧 + return data + else: + logger.debug(f"收到未知帧类型: {opcode}") + return None + + except Exception as e: + logger.error(f"接收帧失败: {e}") + return None + +class WebSocketTestClient: + def __init__(self, server_addr, port): + self.server_addr = server_addr + self.port = port + self.websocket = None + self.test_running = False + self.send_count = 0 + self.recv_count = 0 + self.lost_count = 0 + self.expected_data = bytearray(TEST_DATA_SIZE) + self.received_data = bytearray(TEST_DATA_SIZE) + + # 测试状态 + self.test_state = 'IDLE' # IDLE, RUNNING, COMPLETED, ERROR + + # ping/pong相关 + self.last_ping_time = None + self.ping_interval = 30 # 30秒发送一次ping + + def connect(self): + """连接到WebSocket服务器""" + logger.info(f"连接WebSocket服务器: ws://{self.server_addr}:{self.port}/") + + try: + self.websocket = WebSocketClient(self.server_addr, self.port) + if self.websocket.connect(): + logger.info("WebSocket连接成功") + return True + else: + logger.error("WebSocket连接失败") + return False + except Exception as e: + logger.error(f"WebSocket连接失败: {e}") + return False + + def disconnect(self): + """断开WebSocket连接""" + if self.websocket: + self.websocket.disconnect() + self.websocket = None + logger.info("WebSocket连接已断开") + + def send_test_data(self, data_value): + """发送测试数据""" + if not self.websocket or not self.test_running: + return False + + try: + # 准备测试数据 + test_data = bytes([data_value] * TEST_DATA_SIZE) + + # 发送数据 + if self.websocket.send_frame(test_data): + return True + else: + return False + except Exception as e: + logger.error(f"发送数据失败: {e}") + return False + + def receive_test_data(self): + """接收测试数据""" + if not self.websocket or not self.test_running: + return None + + try: + # 接收数据 + data = self.websocket.receive_frame() + + if data and len(data) == TEST_DATA_SIZE: + self.received_data = data + return data[0] # 返回第一个字节作为数据值 + else: + return None + + except Exception as e: + logger.error(f"接收数据失败: {e}") + return None + + def send_thread_func(self): + """发送线程函数""" + data_value = 0 + logger.info("WebSocket发送线程启动") + + while self.test_running: + # 发送测试数据 + if self.send_test_data(data_value): + self.send_count += 1 + data_value = (data_value + 1) % 256 # 0-255循环 + else: + logger.error("发送测试数据失败") + break + + # 发送ping帧 + current_time = time.time() + if self.last_ping_time is None or (current_time - self.last_ping_time) >= self.ping_interval: + if self.websocket and self.websocket.connected: + self.websocket.send_ping() + self.last_ping_time = current_time + + time.sleep(SEND_INTERVAL) + + logger.info("WebSocket发送线程结束") + + def recv_thread_func(self): + """接收线程函数""" + timeout_count = 0 + logger.info("WebSocket接收线程启动") + + while self.test_running: + data_value = self.receive_test_data() + + if data_value is not None: + self.recv_count += 1 + + # 检查数据是否正确 + data_correct = True + for i in range(TEST_DATA_SIZE): + if self.received_data[i] != data_value: + data_correct = False + break + + if not data_correct: + logger.error("接收数据验证失败") + self.lost_count += 1 + + timeout_count = 0 + else: + timeout_count += 1 + if timeout_count > 10: # 1秒超时 + logger.error("接收数据超时") + break + + time.sleep(0.1) + + logger.info("WebSocket接收线程结束") + + def status_thread_func(self, duration_seconds): + """状态打印线程函数""" + logger.info("状态监控线程启动") + + start_time = time.time() + last_status_time = start_time + + while self.test_running: + current_time = time.time() + elapsed_time = current_time - start_time + + # 每10秒打印一次状态 + if current_time - last_status_time >= 10: + self.print_status(elapsed_time, duration_seconds) + last_status_time = current_time + + time.sleep(1) # 每秒检查一次 + + logger.info("状态监控线程结束") + + def print_status(self, elapsed_time, total_duration): + """打印当前状态""" + progress = (elapsed_time / total_duration) * 100 if total_duration > 0 else 0 + + logger.info("=== 测试状态 ===") + logger.info(f"运行时间: {elapsed_time:.1f}秒 / {total_duration}秒 ({progress:.1f}%)") + logger.info(f"发送数据包数: {self.send_count}") + logger.info(f"接收数据包数: {self.recv_count}") + logger.info(f"丢失数据包数: {self.lost_count}") + + if self.send_count > 0: + loss_rate = (self.lost_count / self.send_count) * 100.0 + logger.info(f"丢包率: {loss_rate:.2f}%") + + if elapsed_time > 0: + send_rate = self.send_count / elapsed_time + recv_rate = self.recv_count / elapsed_time + logger.info(f"发送速率: {send_rate:.2f} 包/秒") + logger.info(f"接收速率: {recv_rate:.2f} 包/秒") + + # ping/pong状态 + if self.websocket and self.websocket.last_pong_time: + pong_delay = time.time() - self.websocket.last_pong_time + logger.info(f"最后pong时间: {pong_delay:.1f}秒前") + + logger.info("================") + + def start_test(self, duration_seconds=30): + """启动测试""" + if self.test_state != 'IDLE': + logger.error("测试正在进行中,请等待完成") + return False + + # 连接服务器 + if not self.connect(): + return False + + self.test_running = True + self.test_state = 'RUNNING' + + logger.info("WebSocket测试初始化成功") + + # 启动发送和接收线程 + send_thread = threading.Thread(target=self.send_thread_func) + recv_thread = threading.Thread(target=self.recv_thread_func) + status_thread = threading.Thread(target=self.status_thread_func, args=(duration_seconds,)) + + send_thread.start() + recv_thread.start() + status_thread.start() + + # 运行指定时间 + time.sleep(duration_seconds) + + # 停止测试 + self.test_running = False + + # 等待线程完成 + send_thread.join() + recv_thread.join() + status_thread.join() + + # 断开连接 + self.disconnect() + + self.test_state = 'COMPLETED' + + # 打印结果 + self.print_test_results() + + logger.info("WebSocket测试完成") + return True + + def stop_test(self): + """停止测试""" + if self.test_state == 'RUNNING': + self.test_running = False + logger.info("WebSocket测试已停止") + + def get_test_state(self): + """获取测试状态""" + return self.test_state + + def get_test_stats(self): + """获取测试统计信息""" + return self.send_count, self.recv_count, self.lost_count + + def print_test_results(self): + """打印测试结果""" + logger.info("=== WebSocket测试结果 ===") + logger.info(f"发送数据包数: {self.send_count}") + logger.info(f"接收数据包数: {self.recv_count}") + logger.info(f"丢失数据包数: {self.lost_count}") + + if self.send_count > 0: + loss_rate = (self.lost_count / self.send_count) * 100.0 + logger.info(f"丢包率: {loss_rate:.2f}%") + + logger.info("========================") + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description='WebSocket客户端测试程序(原生版本)') + parser.add_argument('--server', default='localhost', help='服务器地址 (默认: localhost)') + parser.add_argument('--port', type=int, default=8080, help='服务器端口 (默认: 8080)') + parser.add_argument('--duration', type=int, default=30, help='测试持续时间(秒) (默认: 30)') + parser.add_argument('--verbose', '-v', action='store_true', help='详细日志输出') + + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + logger.info("开始WebSocket客户端测试") + logger.info(f"服务器地址: {args.server}") + logger.info(f"服务器端口: {args.port}") + logger.info(f"测试持续时间: {args.duration}秒") + + # 创建客户端实例 + client = WebSocketTestClient(args.server, args.port) + + try: + # 启动测试 + success = client.start_test(args.duration) + + if success: + logger.info("🎉 测试完成") + else: + logger.error("💥 测试失败") + + except KeyboardInterrupt: + logger.info("收到中断信号,正在停止测试...") + client.stop_test() + except Exception as e: + logger.error(f"测试运行出错: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ws_lwip_test/websocket_test_server.py b/ws_lwip_test/websocket_test_server.py new file mode 100644 index 000000000..2e11cd22a --- /dev/null +++ b/ws_lwip_test/websocket_test_server.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +WebSocket测试服务器 +支持多客户端连接,进行数据回显测试 +""" + +import asyncio +import logging +import argparse +import time +from typing import Dict, Set +import struct + +# 检查 SSL 模块 +try: + import ssl +except ImportError: + print("警告: SSL 模块不可用,某些功能可能受限") + ssl = None + +# 导入 websockets +try: + import websockets + from websockets.asyncio.server import serve + # 避免导入可能导致兼容性问题的异常类 +except ImportError as e: + print(f"错误: 无法导入 websockets 模块: {e}") + print("请确保已安装 websockets: pip install websockets") + exit(1) + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# 全局变量 +TEST_DATA_SIZE = 228 +clients: Set = set() # 移除已弃用的类型注解 +test_stats = { + 'total_received': 0, + 'total_sent': 0, + 'lost_packets': 0, + 'start_time': None +} + +class WebSocketTestServer: + def __init__(self, host: str, port: int): + self.host = host + self.port = port + self.clients = set() + self.test_stats = { + 'total_received': 0, + 'total_sent': 0, + 'lost_packets': 0, + 'start_time': None + } + + async def handle_client(self, websocket, path): + """处理客户端连接""" + client_id = id(websocket) + client_ip, client_port = websocket.remote_address + logger.info(f"客户端 {client_id} 连接: {client_ip}:{client_port}, 路径: {path}") + + self.clients.add(websocket) + + try: + async for message in websocket: + await self.process_message(websocket, message) + except ConnectionClosedOK: + logger.info(f"客户端 {client_id} 连接正常关闭") + except ConnectionClosedError as e: + logger.error(f"客户端 {client_id} 连接异常关闭: {e}") + except Exception as e: + logger.error(f"处理客户端 {client_id} 消息时出错: {e}") + finally: + self.clients.discard(websocket) + logger.info(f"客户端 {client_id} 断开连接") + + async def process_message(self, websocket, message): + """处理接收到的消息""" + if isinstance(message, bytes): + # 二进制消息 + await self.handle_binary_message(websocket, message) + else: + # 文本消息 + await self.handle_text_message(websocket, message) + + async def handle_binary_message(self, websocket, data): + """处理二进制消息""" + if len(data) != TEST_DATA_SIZE: + logger.warning(f"收到错误大小的数据: {len(data)} bytes") + return + + # 提取数据值 + data_value = data[0] + + # 验证数据一致性 + expected_data = bytes([data_value] * TEST_DATA_SIZE) + if data != expected_data: + logger.warning(f"数据验证失败: 期望值={data_value}, 实际数据不一致") + self.test_stats['lost_packets'] += 1 + return + + self.test_stats['total_received'] += 1 + + # 回显数据 + try: + await websocket.send(data) + self.test_stats['total_sent'] += 1 + + if self.test_stats['total_received'] % 100 == 0: + logger.info(f"已处理 {self.test_stats['total_received']} 个数据包") + + except Exception as e: + if "connection closed" in str(e).lower(): + logger.info("发送数据时连接关闭") + else: + logger.warning(f"发送数据时连接异常关闭: {e}") + except Exception as e: + logger.error(f"发送数据失败: {e}") + + async def handle_text_message(self, websocket, message): + """处理文本消息""" + logger.info(f"收到文本消息: {message}") + + # 发送响应 + try: + response = f"Echo: {message}" + await websocket.send(response) + except Exception as e: + if "connection closed" in str(e).lower(): + logger.info("发送文本响应时连接关闭") + else: + logger.warning(f"发送文本响应时连接异常关闭: {e}") + except Exception as e: + logger.error(f"发送文本响应失败: {e}") + + async def start_server(self): + """启动WebSocket服务器""" + logger.info(f"启动WebSocket测试服务器: ws://{self.host}:{self.port}") + + # 启动服务器 + server = await serve( + self.handle_client, + self.host, + self.port, + ping_interval=30, + ping_timeout=10 + ) + + self.test_stats['start_time'] = time.time() + logger.info("WebSocket服务器启动成功") + + # 保持服务器运行 + await server.wait_closed() + + def print_stats(self): + """打印测试统计信息""" + if self.test_stats['start_time'] is None: + return + + duration = time.time() - self.test_stats['start_time'] + + logger.info("=== WebSocket测试服务器统计 ===") + logger.info(f"运行时间: {duration:.2f} 秒") + logger.info(f"当前连接数: {len(self.clients)}") + logger.info(f"接收数据包数: {self.test_stats['total_received']}") + logger.info(f"发送数据包数: {self.test_stats['total_sent']}") + logger.info(f"丢失数据包数: {self.test_stats['lost_packets']}") + + if self.test_stats['total_received'] > 0: + loss_rate = (self.test_stats['lost_packets'] / self.test_stats['total_received']) * 100 + logger.info(f"丢包率: {loss_rate:.2f}%") + + if duration > 0: + receive_rate = self.test_stats['total_received'] / duration + send_rate = self.test_stats['total_sent'] / duration + logger.info(f"接收速率: {receive_rate:.2f} 包/秒") + logger.info(f"发送速率: {send_rate:.2f} 包/秒") + + logger.info("================================") + +async def main(): + """主函数""" + parser = argparse.ArgumentParser(description='WebSocket测试服务器') + parser.add_argument('--host', default='0.0.0.0', help='服务器地址 (默认: 0.0.0.0)') + parser.add_argument('--port', type=int, default=8910, help='服务器端口 (默认: 8910)') + parser.add_argument('--verbose', '-v', action='store_true', help='详细日志输出') + + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + # 创建服务器实例 + server = WebSocketTestServer(args.host, args.port) + + # 启动服务器 + try: + await server.start_server() + except KeyboardInterrupt: + logger.info("收到中断信号,正在关闭服务器...") + # 优雅关闭所有连接 + for conn in list(server.clients): + try: + await conn.close() + except Exception as e: + logger.error(f"关闭连接时出错: {e}") + except Exception as e: + logger.error(f"服务器运行出错: {e}") + finally: + server.print_stats() + +if __name__ == "__main__": + asyncio.run(main())