Skip to content

概念先知道

  • WebSocket:在 TCP 之上建立的双向长连接协议。HTTP 是“一问一答”,WebSocket 则是连接建立后双方随时互发消息,适合聊天、实时数据推送、远程控制。
  • ws / wssws:// 是明文,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 常用于替代“轮询”实现实时推送。

操作步骤

1
进入例程目录

在终端进入 SDK 的 WebSocket 例程目录(前提:环境已按快速开始(Linux)Windows搭好):

cd examples/wifi/sta/websocket
2
编译工程

统一填写 bl616(例程默认编译 ws:// 版本;打开 WEBSOCKET_SSL_ENABLE 可编译 wss:// 加密版,见例程 Makefile):

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

按住 BOOT 键短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
准备 WebSocket 服务器

在电脑上启动一个 WebSocket 服务器(例如 Node 的 ws 库或 websocat -s 8765),记住服务器 IP 与端口:

websocat -s 8765
5
连接 Wi-Fi 并连接 WebSocket

串口助手波特率 2000000,连接路由器后执行(path 可省略,默认 /):

wifi_sta_connect Your_SSID 12345678
websocket_test 192.168.1.2 8765 /
6
运行验证

日志打印 websocket connecting to ws://192.168.1.2:8765/ ...,随后 websocket connect successfully;断开时打印 websocket disconnected 及错误码。

代码执行流程

WebSocket 从启动到连接的完整流程如下:

例程调用的 API 介绍

rws_socket_create()

创建 WebSocket 客户端对象。

参数:无

返回值rws_socket 句柄;失败返回 NULL

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 完整代码
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 完整代码
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

Released under the MIT License. Build Time 2026-09-11 14:52:23