Skip to content

概念先知道

  • TCP 客户端:主动发起连接的一方。本例开发板是客户端,电脑是服务器。
  • 三次握手:TCP 建立连接前双方互换确认包,connect 返回成功即握手完成。
  • socket / connect / send / recv:客户端编程四件套——建套接字、连服务器、发数据、收数据。
  • 同一局域网:开发板与电脑必须连同一个路由器,且防火墙要放行对应端口。

例程功能简介

  • 本页对应博流官方 SDK 的 wifi_tcp 例程(examples/wifi/sta/wifi_tcp),重点讲解其中的 TCP 客户端部分(wifi_tcp_client.c,shell 命令 wifi_tcp_test <ip> <port>):
  • 连接成功后创建两个任务:tcp_client_tx 每 200ms 发送固定字符串,tcp_client_rx 同时接收服务器数据,统计收发字节数,Ctrl-C 退出时打印统计。
  • 同族功能:同一例程还包含 TCP 服务器(见 TCP 服务器,命令 wifi_tcp_echo_test)。

操作步骤

1
进入例程目录

进入 SDK 的 wifi_tcp 例程(前提:已搭建 SDK 环境):

cd examples/wifi/sta/wifi_tcp
2
编译并烧录

统一编译并烧录(按住 BOOT 短按 EN/RST 进入下载模式):

make CHIP=bl616 BOARD=bl616dk && make flash CHIP=bl616 COMX=/dev/ttyUSB0
3
PC 端启动监听

在电脑上打开终端,用 netcat 监听 3365 端口(Windows 可用 nc -l -p 3365 或安装 Nmap 版 nc):

nc -lp 3365
4
开发板连接 Wi-Fi

串口波特率 2000000,等提示符后连接路由器,记下电脑的 IP(同一局域网):

wifi_sta_connect SSID <密码>
5
发起 TCP 连接

在开发板 shell 输入(IP 换成电脑 IP,端口与监听一致):

wifi_tcp_test <电脑IP> 3365
6
验证通信

串口打印 TCP client connect server success!,随后每 200ms 向服务器发送 wifi tcp client test, helloworld!;电脑的 nc 窗口会持续收到该字符串。按 Ctrl-C 退出。

netcat 收到: wifi tcp client test, helloworld!

代码执行流程

例程调用的 API 介绍

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

创建 TCP 套接字,返回文件描述符。

参数

  • AF_INET:IPv4 地址族
  • SOCK_STREAM:流式套接字(TCP)
  • IPPROTO_TCP:TCP 协议

返回值:成功返回 fd(≥0);失败返回 -1

connect(sock, addr, len)

向服务器发起 TCP 连接(内部完成三次握手)。

参数

  • sock:套接字 fd
  • addr:服务器 sockaddr_in 地址结构
  • len:地址结构长度

返回值:成功返回 0;失败返回 -1

write / read(sock, buf, len)

通过已连接套接字发送 / 接收数据(lwIP 提供的 POSIX 风格接口)。

参数

  • sock:套接字 fd
  • buf:数据缓冲区
  • len:长度

返回值:返回实际收发字节数;失败返回负值

xTaskCreate(task, name, stack, arg, prio, handle)

创建 FreeRTOS 任务。例程为收发各建一个任务,保证一边发一边收互不阻塞。

参数

  • task:任务函数
  • name:任务名
  • stack:栈大小(512 字)
  • arg:参数(套接字指针)
  • prio:优先级 20
  • handle:任务句柄

返回值pdPASS 成功 / errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 失败

closesocket(sock)

关闭套接字并释放资源(lwIP 的 close 别名)。

参数

  • sock:套接字 fd

返回值:成功返回 0

完整代码

以下为 wifi_tcp 例程完整源码,与官方示例一致,默认折叠,点击展开:

📜 点击展开 wifi_tcp/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"
#ifndef BL602
#include "fhost_api.h"
#include "wifi_mgmr.h"
#endif

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "rfparam_adapter.h"
#include "async_event.h"
#include "mm.h"
#include "board.h"
#include "shell.h"

#define DBG_TAG "MAIN"
#include "log.h"

struct bflb_device_s *gpio;

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;

extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);
#ifdef BL602
extern void wifi_task_create(void);
extern int fhost_init(void);
extern int wifi_mgmr_task_start(void);
#endif

/****************************************************************************
 * 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");
#ifndef BL602
    fhost_init();
#endif

    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_task_start();
        } 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;
        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) {
    }
}
📜 点击展开 wifi_tcp/wifi_tcp_client.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"

#define TCP_CLIENT_BLOCK_TIMEOUT 0
#define TCP_CLIENT_NOT_DEL_SELF  0

struct arg_param {
    int argc;
    char **argv;
};

// clang-format off
static const uint8_t write_buf[128] = "wifi tcp client test, helloworld!\r\n";
static uint8_t read_buf[128];
// clang-format on

static volatile int wifi_tcp_client_exit;
static shell_sig_func_ptr abort_exec;
static uint64_t total_rx_cnt;
static uint64_t total_tx_cnt;

static void sig_close(int sig)
{
    wifi_tcp_client_exit = 1;

    if (abort_exec) {
        abort_exec(sig);
    }
}

#define PING_USAGE                    \
    "wifi_tcp_test [ip] [port]\r\n"   \
    "\t ip: dest ip or server ip\r\n" \
    "\t port: dest server listen port\r\n"

static void wifi_tcp_client_rx(void *sock_client)
{
    printf("tcp client rx task start ...\r\n");

    int sock = *(int*)sock_client;
    int ret;
    int timeout_cnt = 0;

    /* read */
    while (1) {
        ret = read(sock, read_buf, sizeof(read_buf));

        if (ret >= 0) {
            total_rx_cnt += ret;
            timeout_cnt = 0;
        } else if (ret == ERR_TIMEOUT) {
            if (++timeout_cnt > 3) {
                printf("read failed, timeout_cnt: %d\n\r", timeout_cnt);
                break;
            }
        } else {
            printf("read failed, ret: %d, errno: %d\n\r", ret, errno);
            break;
        }

        vTaskDelay(200);
    }

    /* wait to be deleted */
    #if TCP_CLIENT_NOT_DEL_SELF
    printf("tcp client rx task exiting...\r\n");
    while (1) {
        vTaskDelay(200);
    }
    #else
    vTaskDelete(NULL);
    #endif

    printf("tcp client rx task exit!\r\n");
}

static void wifi_tcp_client_tx(void *sock_client)
{
    printf("tcp client tx task start ...\r\n");

    int sock = *(int*)sock_client;
    int ret;
    int timeout_cnt = 0;

    /* write */
    while (1) {
        ret = write(sock, write_buf, sizeof(write_buf));

        if (ret >= 0) {
            total_tx_cnt += sizeof(write_buf);
            timeout_cnt = 0;
        } else if (ret == ERR_TIMEOUT) {
            if (++timeout_cnt > 3) {
                printf("write failed, timeout_cnt: %d\n\r", timeout_cnt);
                break;
            }
        } else {
            printf("write failed, ret: %d, errno: %d\n\r", ret, errno);
            break;
        }

        vTaskDelay(200);
    }

    /* wait to be deleted */
    #if TCP_CLIENT_NOT_DEL_SELF
    printf("tcp client tx task exiting...\r\n");
    while (1) {
        vTaskDelay(200);
    }
    #else
    vTaskDelete(NULL);
    #endif

    printf("tcp client tx task exit!\r\n");
}

static void wifi_tcp_client_init(void *input_arg)
{
    printf("tcp client task start ...\r\n");

    char *addr;
    char *port;
    int sock_client = -1;
    struct sockaddr_in remote_addr;
    TaskHandle_t px_tcpclient_rx_task = NULL;
    TaskHandle_t px_tcpclient_tx_task = NULL;
    struct arg_param* arg = (struct arg_param*)input_arg;

    /* check arg */
    if (arg->argc < 3) {
        printf("%s", PING_USAGE);
        goto __exit;
    }

    /* get address (argv[1] if present) */
    addr = arg->argv[1];
    /* get port number (argv[2] if present) */
    port = arg->argv[2];

    /* create socket */
    if ((sock_client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        printf("TCP Client create socket error\r\n");
        goto __exit;
    }

    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(atoi(port));
    remote_addr.sin_addr.s_addr = inet_addr(addr);
    memset(&(remote_addr.sin_zero), 0, sizeof(remote_addr.sin_zero));
    printf("Server ip Address : %s:%s\r\n", addr, port);

    /* connect socket */
    if (connect(sock_client, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) != 0) {
        printf("TCP client connect server falied!\r\n");
        goto __exit;
    }
    printf("TCP client connect server success!\r\n");

    #if TCP_CLIENT_BLOCK_TIMEOUT
    /* blocking timeout */
    int timeout_ms = 4000;
    #if LWIP_SO_SNDRCVTIMEO_NONSTANDARD
    int opt_on = timeout_ms;
    #else
    struct timeval opt_on = {
        .tv_sec = timeout_ms / 1000,
        .tv_usec = (timeout_ms - (opt_on.tv_sec * 1000)) * 1000,
    };
    #endif
    setsockopt(sock_client, SOL_SOCKET, SO_SNDTIMEO, (void *)&opt_on, sizeof(opt_on));
    #endif

    total_rx_cnt = 0;
    total_tx_cnt = 0;
    wifi_tcp_client_exit = 0;
    abort_exec = shell_signal(SHELL_SIGINT, sig_close);
    printf("Press CTRL-C to exit before next Shell CMD.\r\n");

    /* fork recv and send task, dont care fail */
    xTaskCreate(wifi_tcp_client_rx, "tcp_client_rx", 512, (void *)&sock_client, 20, &px_tcpclient_rx_task);
    xTaskCreate(wifi_tcp_client_tx, "tcp_client_tx", 512, (void *)&sock_client, 20, &px_tcpclient_tx_task);

    while (!wifi_tcp_client_exit) {
        vTaskDelay(500);
    }

__exit:
    /* exit procedure */
    if (sock_client >= 0) {
        printf("closesocket!\r\n");
        closesocket(sock_client);
    }

    if (px_tcpclient_rx_task || px_tcpclient_tx_task) {
        vTaskDelay(500);
    }

    #if TCP_CLIENT_NOT_DEL_SELF
    if (px_tcpclient_rx_task) {
        vTaskDelete(px_tcpclient_rx_task);
    }
    if (px_tcpclient_tx_task) {
        vTaskDelete(px_tcpclient_tx_task);
    }
    #endif

    if (total_rx_cnt || total_tx_cnt) {
        printf("Total recv data=%lld\r\n", total_rx_cnt);
        printf("Total send data=%lld\r\n", total_tx_cnt);
    }

    printf("tcp_client exit!\r\n");
    vTaskDelete(NULL);
}

#ifdef CONFIG_SHELL
#include <shell.h>

int cmd_wifi_tcp_client(int argc, char **argv)
{
    struct arg_param arg = {argc, argv};

    if (pdPASS != xTaskCreate(wifi_tcp_client_init, "tcp_client", 512, (void *)&arg, 15, NULL)) {
        return -1;
    }

    return 0;
}

SHELL_CMD_EXPORT_ALIAS(cmd_wifi_tcp_client, wifi_tcp_test, wifi tcp test);
#endif

FAQ

`TCP client connect server falied!`?

依次检查:电脑 nc 是否在监听、IP/端口是否一致、开发板与电脑是否同一局域网、电脑防火墙是否放行该端口。

连上了但电脑收不到数据?

确认电脑 nc 先启动监听再让开发板连接;部分路由器开启了 AP 隔离,会阻断设备互访,可关闭 AP 隔离或换热点。

按 Ctrl-C 没反应?

例程通过 shell 信号处理退出;在串口助手发送 Ctrl-C(0x03)后需等待任务清理,最终打印收发统计。

可以发中文吗?

例程发送的是固定 ASCII 字符串;发自定义内容需修改 write_buf 或改写收发逻辑(UTF-8 编码的中文亦可直接放入缓冲区)。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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