Skip to content

概念先知道

  • TCP 服务器:在一台设备上监听某个端口,等待其他设备(客户端)主动来连接。连接建立后双方可以互相收发数据。
  • 监听(listen):服务器 socket() 创建套接字后,用 bind() 绑定本地端口,再 listen() 开始监听,之后 accept() 等待并接受客户端连接。
  • 回显(echo):例程把收到的数据原样发回给客户端,用来验证双向收发是否正常。
  • 连接是点对点的:TCP 服务器可以轮流接受多个客户端,但每个连接各自独立,需要记录各自的 socket 描述符。

例程功能简介

本页对应博流官方 SDK 的 wifi_tcp 例程中的 **TCP 服务器(回显)**部分(examples/wifi/sta/wifi_tcp/wifi_tcp_server.c):

  • 用 lwIP socket 完成 socket → bind → listen → accept 标准服务器流程;
  • 收到客户端数据后打印长度并原样回写(echo),每 500ms 处理一轮;
  • setsockopt(TCP_NODELAY) 关闭 Nagle 算法,减少小数据包延迟;
  • shell 命令 wifi_tcp_echo_test <port> 启动服务器,Ctrl+C 退出并打印收发统计。
  • 同族例程:同目录 wifi_tcp_client.c 提供 TCP 客户端命令 wifi_tcp_test <ip> <port>(见TCP 客户端页)。

操作步骤

1
进入例程目录

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

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

Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写 bl616

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

按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
连接 Wi-Fi 并启动服务器

串口助手波特率 2000000,看到 bouffalolab /> 后先连接路由器,再启动 TCP 服务器监听 3365 端口:

wifi_sta_connect Your_SSID 12345678
wifi_tcp_echo_test 3365
5
运行验证

在电脑上用 netcat 连接模组的 IP(端口 3365),输入任意内容,模组会把收到的数据原样回显:

nc -v 192.168.1.3 3365

代码执行流程

服务器从启动到回显数据的完整流程如下:

例程调用的 API 介绍

socket(AF_INET, SOCK_STREAM, 0)

创建一个 TCP 套接字(socket),返回句柄,后续 bind/listen/accept 都基于它。

参数

  • AF_INET:IPv4 地址族
  • SOCK_STREAM:面向连接的流式套接字(TCP)
  • 0:协议自动选择

返回值:大于等于 0 的句柄;< 0 失败

bind(sock, addr, len)

把套接字绑定到本地地址和端口,服务器必须做这一步才能被外部访问。

参数

  • sock:socket 句柄
  • addrsockaddr_in,含端口(htons(port))与 INADDR_ANY
  • len:地址结构长度

返回值0 成功;-1 失败

listen / accept(sock, backlog) / (sock, addr, len)

listen() 让套接字进入监听状态(backlog 为等待队列长度,例程为 5);accept() 阻塞等待客户端连入,返回新连接的套接字。

参数

  • sock:监听套接字
  • backlog:最大等待连接数

返回值accept 返回新连接句柄;失败返回 -1

recv / write(sock, buf, len, 0)

recv() 从连接读取数据(返回实际字节数,<= 0 表示关闭或出错);write() 向连接发送数据。

参数

  • sock:accept 返回的连接句柄
  • buf / len:缓冲区与长度

返回值:实际收/发字节数;<= 0 出错或连接关闭

setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, ...)

关闭 Nagle 算法,让每次 write 立即发出,适合交互式小包场景。

参数

  • sock:连接句柄
  • TCP_NODELAY:选项名,flag = 1 开启

返回值0 成功;-1 失败

完整代码

以下为 wifi_tcp 例程中 TCP 服务器相关源码,与官方示例(examples/wifi/sta/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_server.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"

// clang-format off
// clang-format on

#define RECV_DATA_LEN (1024)
#define PING_USAGE                  \
    "wifi_tcp_echo_test [port]\r\n" \
    "\t port: local server listen port\r\n"

shell_sig_func_ptr abort_exec;
uint64_t recv_data_cnt = 0;
int sock_server = -1, connected;
char *recv_data;

static void test_close(int sig)
{
    if (connected >= 0) {
        closesocket(connected);
        connected = -1;
    }
    if (sock_server >= 0) {
        closesocket(sock_server);
    }
    if (recv_data) {
        free(recv_data);
        recv_data = NULL;
    }
    abort_exec(sig);
    if (recv_data_cnt > 0) {
        printf("recv data=%lld\r\n", recv_data_cnt);
    }
}

static void wifi_test_tcp_server_init(int argc, char **argv)
{
    abort_exec = shell_signal(SHELL_SIGINT, test_close);
    printf("tcp server task start ...\r\n");

    char *port;
    struct sockaddr_in server_addr, client_addr;
    socklen_t sin_size;

    if (argc < 2) {
        printf("%s", PING_USAGE);
        return;
    }

    /* get listen port (argv[1] if present) */
    port = argv[1];

    recv_data = (char *)pvPortMalloc(RECV_DATA_LEN);
    if (recv_data == NULL) {
        printf("not memory\r\n");
        return;
    }
    /* create socket */
    if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("TCP server create socket error\r\n");
        return;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(port));
    server_addr.sin_addr.s_addr = INADDR_ANY;
    memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* bind socket */
    if (bind(sock_server, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
        printf("TCP server socket bind fail!\r\n");
        return;
    }

    /* listen port */
    if (listen(sock_server, 5) == -1) {
        printf("TCP server listen error!\r\n ");
        return;
    }

    printf("TCP Server listen start: localhost:%s\r\n", port);
    printf("please connet ...\r\n");

    while (1) {
        sin_size = sizeof(struct sockaddr_in);
        connected = accept(sock_server, (struct sockaddr *)&client_addr, &sin_size);
        printf("new client connected from (%s, %d)\r\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        int flag = 1;
        setsockopt(connected, IPPROTO_TCP, TCP_NODELAY, (void *)&flag, sizeof(int));

        printf("Press CTRL-C to exit.\r\n");
        recv_data_cnt = 0;
        while (1) {
            recv_data_cnt = recv(connected, recv_data, RECV_DATA_LEN, 0);
            if (recv_data_cnt <= 0) {
                break;
            }
            printf("recv %lld len data\r\n", recv_data_cnt);
            if (write(connected, recv_data, recv_data_cnt) < 0) {
                printf("write falied!\r\n");
                break;
            }
            vTaskDelay(500);
        }
        if (connected >= 0) {
            closesocket(connected);
            connected = -1;
        }
        if (sock_server >= 0) {
            closesocket(sock_server);
        }
        if (recv_data) {
            free(recv_data);
            recv_data = NULL;
        }

        return;
    }
}

#ifdef CONFIG_SHELL
#include <shell.h>

int cmd_wifi_tcp_server(int argc, char **argv)
{
    wifi_test_tcp_server_init(argc, argv);

    return 0;
}

SHELL_CMD_EXPORT_ALIAS(cmd_wifi_tcp_server, wifi_tcp_echo_test, wifi tcp test);
#endif

FAQ

电脑连不上模组的端口

确认模组与电脑在同一个路由器/网段(模组 IP 用 wifi_sta_connect 后的 CODE_WIFI_ON_GOT_IP 日志或 ifconfig 命令查看);确认命令是 wifi_tcp_echo_test 3365 而不是客户端命令;部分路由器开了 AP 隔离,需关闭。

能连接但收不到回显

检查 nc 连接地址是否就是模组的 IP;例程只支持同时处理一个客户端,处理完一个连接后服务器会退出,需要重新执行命令。

遇到问题?

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

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