概念先知道
- 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 客户端页)。
操作步骤
在终端进入 SDK 的 TCP 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/wifi/sta/wifi_tcpAi-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写 bl616:
make CHIP=bl616 BOARD=bl616dk按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)短按 EN/RST 进入下载模式后烧录:
make flash CHIP=bl616 COMX=/dev/ttyUSB0串口助手波特率 2000000,看到 bouffalolab /> 后先连接路由器,再启动 TCP 服务器监听 3365 端口:
wifi_sta_connect Your_SSID 12345678
wifi_tcp_echo_test 3365在电脑上用 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 句柄addr:sockaddr_in,含端口(htons(port))与INADDR_ANYlen:地址结构长度
返回值: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 完整代码
/****************************************************************************
*
* 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 完整代码
#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);
#endifFAQ
电脑连不上模组的端口
确认模组与电脑在同一个路由器/网段(模组 IP 用 wifi_sta_connect 后的 CODE_WIFI_ON_GOT_IP 日志或 ifconfig 命令查看);确认命令是 wifi_tcp_echo_test 3365 而不是客户端命令;部分路由器开了 AP 隔离,需关闭。
能连接但收不到回显
检查 nc 连接地址是否就是模组的 IP;例程只支持同时处理一个客户端,处理完一个连接后服务器会退出,需要重新执行命令。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

