概念先知道
- WebSocket:在 TCP 之上建立的双向长连接协议。HTTP 是“一问一答”,WebSocket 则是连接建立后双方随时互发消息,适合聊天、实时数据推送、远程控制。
- ws / wss:
ws://是明文,wss://是 TLS 加密版(依赖 mbedTLS)。 - 握手:WebSocket 连接通过一次 HTTP Upgrade 请求建立,之后数据走帧(frame)格式。
- librws:例程使用的 WebSocket 客户端库,提供
rws_socket_create/connect/set_*等接口。
例程功能简介
本页对应博流官方 SDK 的 websocket 例程(examples/wifi/sta/websocket):
websocket_test <host> <port> <path>命令连接 WebSocket 服务器;rws_socket_create创建客户端,set_host/set_port/set_path配置目标;rws_socket_set_scheme选择ws(或wss,编译WEBSOCKET_SSL_ENABLE时),set_server_cert设置 TLS 证书;rws_socket_connect发起连接,rws_socket_is_connected轮询等待成功;rws_on_socket_callback处理断开事件并打印错误码。- 同族参考:HTTP 请求见 HTTP GET/POST / HTTPS;WebSocket 常用于替代“轮询”实现实时推送。
操作步骤
在终端进入 SDK 的 WebSocket 例程目录(前提:环境已按快速开始(Linux)或Windows搭好):
cd examples/wifi/sta/websocket统一填写 bl616(例程默认编译 ws:// 版本;打开 WEBSOCKET_SSL_ENABLE 可编译 wss:// 加密版,见例程 Makefile):
make CHIP=bl616 BOARD=bl616dk按住 BOOT 键短按 EN/RST 进入下载模式后烧录:
make flash CHIP=bl616 COMX=/dev/ttyUSB0在电脑上启动一个 WebSocket 服务器(例如 Node 的 ws 库或 websocat -s 8765),记住服务器 IP 与端口:
websocat -s 8765串口助手波特率 2000000,连接路由器后执行(path 可省略,默认 /):
wifi_sta_connect Your_SSID 12345678
websocket_test 192.168.1.2 8765 /日志打印 websocket connecting to ws://192.168.1.2:8765/ ...,随后 websocket connect successfully;断开时打印 websocket disconnected 及错误码。
代码执行流程
WebSocket 从启动到连接的完整流程如下:
例程调用的 API 介绍
rws_socket_set_host / set_port / set_path(ws, ...)
配置目标服务器:主机名/IP、端口、请求路径(默认 /)。
参数:
ws:客户端句柄host/port/path:目标地址信息
返回值:无
rws_socket_set_scheme / rws_socket_set_server_cert(ws, "ws"/"wss") / (ws, cert, len)
设置协议(明文 ws 或加密 wss);加密模式下设置服务器 CA 证书(例程内置测试证书 test_ca_crt)。
参数:
ws:客户端句柄scheme:"ws"或"wss"cert/len:PEM 证书内容与长度
返回值:无
rws_socket_connect / rws_socket_is_connected(ws)
发起连接(内部创建连接线程);is_connected 轮询查询是否已连接成功。
参数:
ws:客户端句柄
返回值:connect 返回真/假;is_connected 返回布尔连接状态
rws_socket_set_on_disconnected(ws, callback)
注册断开回调,断开时打印错误码与描述(rws_socket_get_error)。
参数:
ws:客户端句柄callback:断开回调函数
返回值:无
完整代码
以下为 websocket.c 完整源码,与官方示例(examples/wifi/sta/websocket)一致,默认折叠,点击展开:
📜 点击展开 websocket/main.c 完整代码
/****************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include "wifi_mgmr_ext.h"
#include "bflb_irq.h"
#include "bflb_uart.h"
#include "rfparam_adapter.h"
#include "board.h"
#include "shell.h"
#define DBG_TAG "MAIN"
#include "log.h"
#include "async_event.h"
struct bflb_device_s *gpio;
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
static struct bflb_device_s *uart0;
static wifi_conf_t conf = {
.country_code = "CN",
};
extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Functions
****************************************************************************/
void wifi_start_firmware_task(void *param)
{
LOG_I("Starting wifi ...\r\n");
async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);
wifi_task_create();
LOG_I("Starting fhost ...\r\n");
fhost_init();
vTaskDelete(NULL);
}
void wifi_event_handler(async_input_event_t ev, void *priv)
{
uint32_t code = ev->code;
switch (code) {
case CODE_WIFI_ON_INIT_DONE: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
wifi_mgmr_init(&conf);
} break;
case CODE_WIFI_ON_MGMR_DONE: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
} break;
case CODE_WIFI_ON_SCAN_DONE: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
wifi_mgmr_sta_scanlist();
} break;
case CODE_WIFI_ON_CONNECTED: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
void mm_sec_keydump();
mm_sec_keydump();
} break;
#ifdef CODE_WIFI_ON_GOT_IP_ABORT
case CODE_WIFI_ON_GOT_IP_ABORT: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_ABORT\r\n", __func__);
} break;
#endif
#ifdef CODE_WIFI_ON_GOT_IP_TIMEOUT
case CODE_WIFI_ON_GOT_IP_TIMEOUT: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_TIMEOUT\r\n", __func__);
} break;
#endif
case CODE_WIFI_ON_GOT_IP: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
LOG_I("[SYS] Memory left is %d Bytes\r\n", kfree_size(0));
} break;
case CODE_WIFI_ON_DISCONNECT: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
} break;
case CODE_WIFI_ON_AP_STARTED: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
} break;
case CODE_WIFI_ON_AP_STOPPED: {
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
} break;
case CODE_WIFI_ON_AP_STA_ADD: {
LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
} break;
case CODE_WIFI_ON_AP_STA_DEL: {
LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
} break;
default: {
LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
}
}
}
int main(void)
{
board_init();
uart0 = bflb_device_get_by_name("uart0");
shell_init_with_task(uart0);
if (0 != rfparam_init(0, NULL, 0)) {
LOG_I("PHY RF init failed!\r\n");
return 0;
}
LOG_I("PHY RF init success!\r\n");
tcpip_init(NULL, NULL);
xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);
vTaskStartScheduler();
while (1) {
}
}📜 点击展开 websocket.c 完整代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <lwip/api.h>
#include <lwip/arch.h>
#include <lwip/opt.h>
#include <lwip/inet.h>
#include <lwip/errno.h>
#include <netdb.h>
#include "shell.h"
#include "utils_getopt.h"
#include "bflb_mtimer.h"
#include "librws.h"
#include "rws_error.h"
#include <mbedtls/error.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#include <mbedtls/debug.h>
#include <mbedtls/x509.h>
static const char *test_ca_crt = {
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDgjCCAmqgAwIBAgIUDXuOiFDUpfyctDliikuaRtsOA/UwDQYJKoZIhvcNAQEL\r\n"
"BQAwWTELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0Jl\r\n"
"aWppbmcxEzARBgNVBAoMCk15IENvbXBhbnkxETAPBgNVBAMMCE15IFdTIENBMB4X\r\n"
"DTI1MTEwNzA2NTkzNloXDTM1MTEwNTA2NTkzNlowWTELMAkGA1UEBhMCQ04xEDAO\r\n"
"BgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0JlaWppbmcxEzARBgNVBAoMCk15IENv\r\n"
"bXBhbnkxETAPBgNVBAMMCE15IFdTIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\r\n"
"MIIBCgKCAQEArKRxwazPB1SZAmbibmqqKpj7uFVq/eV7swY2V1Fvy0pEgvk/EYsC\r\n"
"2Vw0TM29oKTvX3TinRg6Kodf9R+kTw9EB8fYeb782ImU6ZSiaVdPg7Dz2QCpiGY7\r\n"
"b4g92H6MUongLR7DWvFg3YIS9AXMwnDsKCD9WqoI6QmEPWGCnpknKGOCkSPmgInR\r\n"
"ruD7c+bxIN8/lefWkjwQOzb4XeqU+DOs74D+75iztaPvtHAQSI6amthfFQtODFDB\r\n"
"qP26pIQ2r6PSZqv7NwUIjXT2e4jqWmjY/OXJLj/TnrBlyjx2M6Vuh1T5hC3FiFUX\r\n"
"dpJMMdRt7mnDP09Dw1th+Kzm4tjVqrch1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\r\n"
"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU37YNYVlsbUYi/mQu+OZQ5FJ/\r\n"
"0BowDQYJKoZIhvcNAQELBQADggEBAKYBU7IxJCauvlJLMoXW7lQIbItQhDgOp+3a\r\n"
"I+xdoTm2kwDtgwgQc2h1Fl7yVqy/TAF4RrZ/m5yuiz/8oAZqieEydWlaX9Akyim8\r\n"
"701G+GP88uAWa4wHMs7laaTWlGC2OHIdL5bg1hx69juA2NigxD3rMd1cMUnl2orc\r\n"
"2qUk/rWKtN0qO5yw9hRd0XYli1PLdQ53w5YYSlme/ABsJKQ/v2FuwrYUfM0haM1i\r\n"
"4eZxNxR69ohgloz4T6C/srCt1QXzdRrCNxk/wSBJM+EPrXCdiYbVgbuMP4oHKwLZ\r\n"
"OaWRtEoSKcObt4JRRPAo/LyS+a+/Q/Ixh13R7SnpoMwpLJaYNt0=\r\n"
"-----END CERTIFICATE-----\r\n"
};
struct arg_param {
int argc;
char **argv;
};
void rws_on_socket_callback(struct rws_socket_struct * wsocket){
rws_error err = rws_socket_get_error(wsocket);
printf("websocket disconnected\r\n");
if(err){
printf("error code: %d, description: %s\r\n", err->code, err->description);
}
}
void websocket_init(char * host, int port, char *path){
rws_socket ws_client = rws_socket_create();
if(!ws_client){
printf("create websocket socket failed\r\n");
return;
}
rws_socket_set_host(ws_client, host);
rws_socket_set_port(ws_client, port);
rws_socket_set_path(ws_client, path);
#ifdef WEBSOCKET_SSL_ENABLE
const char * scheme = "wss";
rws_socket_set_server_cert(ws_client, test_ca_crt, strlen(test_ca_crt)+1);
#else
const char * scheme = "ws";
#endif
rws_socket_set_on_disconnected(ws_client, rws_on_socket_callback);
rws_socket_set_scheme(ws_client, scheme);
if(!rws_socket_connect(ws_client)){
printf("websocet connect thread create failed\r\n");
rws_error err = rws_socket_get_error(ws_client);
if(err)
printf("error code: %d, description: %s\r\n", err->code, err->description);
rws_socket_disconnect_and_release(ws_client);
return;
}
printf("websocket connecting to ws://%s:%d%s ...\r\n", host, port, path);
while (!rws_socket_is_connected(ws_client));
printf("websocket connect successfully\r\n");
}
void cmd_websocket(int argc, char **argv){
if (argc < 3) {
printf("Usage: websocket_test <host> <port> <path>\r\n");
return;
}
char *host = argv[1];
char *port = argv[2];
char *path = "/";
if(argc >=4){
path = argv[3];
}
// struct arg_param arg = {argc, argv};
if (strlen(host) > 255) {
printf("Error: hostname too long (max 255 chars)\r\n");
return;
}
if (atoi(port) <= 0 || atoi(port) > 65535) {
printf("Error: invalid port number (1-65535)\r\n");
return;
}
printf("Set host: %s port: %d path: %s\r\n", host, atoi(port), path);
websocket_init(host, atoi(port), path);
}
SHELL_CMD_EXPORT_ALIAS(cmd_websocket, websocket_test, websocket test);FAQ
websocket connect successfully 之后没数据
例程只建立连接、不自动收发消息;要收发需要在 websocket_init 里用 rws_socket_send_text 发送、注册 rws_socket_set_on_message 接收,或改用服务端主动推送来观察。
wss:// 连接失败
编译时打开 WEBSOCKET_SSL_ENABLE(例程默认关闭),并确认服务器证书由 test_ca_crt 这个内置 CA 签发;连公网 wss 服务需要把对应 CA 换成公共根证书。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

