概述
TCP 是面向连接的可靠传输协议,广泛用于设备上报数据、接收服务器指令。Ai-WB2 通过 lwIP socket(lwIP 是专为嵌入式设备写的轻量级网络库,socket 是它提供的网络编程接口)实现 TCP,官方封装了 tcp_client_init/connect/send/receive 等易用 API。本教程演示:连接远程 TCP 服务器(官方演示服务器 tt.ai-thinker.com:7878),发送数据并循环接收回包。
用大白话讲:TCP 就像打电话——先拨号(connect),等对方接通(三次握手),再说话(收发数据),最后挂断(断开连接)。它可靠:话说没说清、对方听没听清,两边都会确认,丢字了会自动重说,所以设备上报、指令下发这类重要数据都用它。代价是要先「拨通」才能传,且一条电话线只能给一个人打。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/protocols/socket/tcp_client编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 tcp_client 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/protocols/socket/tcp_client
说明:
cd是「进入目录」的命令,进入官方示例工程;后面所有make命令都要在这个目录下执行。
打开 tcp_client/main.c,修改开头的 SSID/密码(同 连接 Wi-Fi),并按需修改服务器地址:
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
//This is Ai-Thinker Remote TCP Server: http://tt.ai-thinker.com:8000/ttcloud
#define TCP_SERVER_IP "122.114.122.174"
#define TCP_SERVER_PORT 7878
| 宏 | 默认值 | 说明 |
|---|---|---|
TCP_SERVER_IP |
122.114.122.174 |
安信可远程 TCP 服务器;连它不需要自己搭服务器 |
TCP_SERVER_PORT |
7878 |
服务器端口(服务器上的「房门号」),找对门才能连上 |
💡 无公网服务器时可用电脑局域网调试:电脑运行网络调试助手监听(如
192.168.x.x:8888),把TCP_SERVER_IP/PORT改成电脑 IP 与端口即可。
打开 tcp_client/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/protocols/socket/tcp_client/tcp_client/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
tcp_client_init(ip, port) |
创建 TCP socket 并初始化(tcp_example.h 封装);不创建就没法打电话 |
tcp_client_connect(socketfd) |
连接服务器(返回 0 成功);相当于拨号,占线就失败 |
tcp_client_send(socketfd, "hell tcp server") |
发送数据到服务器;打电话时的「说话」 |
tcp_client_receive(socketfd, buf) |
接收服务器数据(阻塞等待,返回字节数);阻塞 = 没消息时就一直等 |
strstr(tcp_buff, "close") |
收到 close 命令时断开连接;服务器说挂断就挂断 |
tcp_client_deinit(socketfd) |
关闭 socket 并释放资源;不释放会一直占着内存 |
CODE_WIFI_ON_GOT_IP 回调 |
联网成功后创建 TCP 客户端任务;没拿到 IP 就打电话也打不通 |
💡 封装层 API 实现在工程内
src/tcp_example.c,底层即标准 lwIP socket:socket()/connect()/send()/recv()/close()。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/tcp_client.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件下载进开发板;p=后面是串口设备号(Linux 下常为/dev/ttyUSB0,Windows 下是COM3之类,以你电脑实际为准),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,串口传数据的「语速」,两边必须一致)观察日志:
[APP] [EVT] GOT IP 5
tcp client task run
tcp_client_task:tcp client connect OK
tcp client send OK
连接官方服务器后,服务器会回发数据,串口打印接收内容:
tcp_client_task:tcp receive data:hello, WB2 !
出现 tcp client connect OK、tcp client send OK 并能收到服务器回发的数据(tcp receive data:...)即成功。如果只看到 GOT IP 而没有 connect OK,先确认开发板已联网、目标服务器 IP/端口可达(可在电脑上先试试能不能连上这个 IP 和端口),见文末 FAQ。
💡 局域网自测:电脑开网络调试助手监听端口,手机热点或路由器同一局域网;修改
TCP_SERVER_IP/TCP_SERVER_PORT为电脑 IP 与端口,烧录后助手收到hell tcp server,向开发板发任意字符串,串口打印收到的内容;发送close可断开连接(串口停止收发)。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
tcp_client_init(server_ip, port)
创建 TCP socket 并初始化(工程封装)。
参数:
server_ip:服务器 IP 字符串port:服务器端口
返回值:成功返回 socket 描述符;失败返回 -1
tcp_client_connect(socket_fd)
连接 TCP 服务器(工程封装)。
参数:
socket_fd:tcp_client_init返回的 socket 描述符
返回值:成功返回 0;失败返回负值错误码
tcp_client_send(socket_fd, data)
发送数据(工程封装)。
参数:
socket_fd:socket 描述符data:待发送数据字符串
返回值:成功返回发送字节数;失败返回 <0
tcp_client_receive(socket_fd, data)
接收数据(阻塞,工程封装)。
参数:
socket_fd:socket 描述符data:接收缓冲区
返回值:成功返回接收字节数;失败返回负值
socket(domain, type, proto)
创建 socket(封装层底层)。
参数:
domain:AF_INET(IPv4)type:SOCK_STREAM(流式 TCP)proto:传0
返回值:成功返回 socket 描述符;失败返回 -1
connect(fd, addr, len)
连接服务器(封装层底层)。
参数:
fd:socket 描述符addr:服务器地址(struct sockaddr*)len:地址长度
返回值:成功返回 0;失败返回负值错误码
send / recv(fd, buf, len, flags)
发送 / 接收数据(封装层底层)。
参数:
fd:socket 描述符buf:数据缓冲区len:缓冲区长度flags:标志,传0
返回值:成功返回收发字节数;失败返回负值
pvPortMalloc / vPortFree(size / ptr)
FreeRTOS 动态内存分配 / 释放。
参数:
size:分配字节数ptr:待释放指针(pvPortMalloc返回值)
返回值:pvPortMalloc:成功返回指针,失败返回 NULL;vPortFree:无
📌 服务端地址为 IP 时直接传字符串;域名服务器需先用 DNS 解析为 IP(见官方 dns 示例)。
完整代码
以下为 tcp_client/main.c 完整源码,与官方示例(applications/protocols/socket/tcp_client/tcp_client/main.c)完全一致:
📜 点击展开 tcp_client/main.c 完整代码
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include <blog.h>
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/sockets.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <cli.h>
#include <hal_wifi.h>
#include <lwip/init.h>
#include "tcp_example.h"
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
//This is Ai-Thinker Remote TCP Server: http://tt.ai-thinker.com:8000/ttcloud
#define TCP_SERVER_IP "122.114.122.174"
#define TCP_SERVER_PORT 7878
static wifi_conf_t conf = {
.country_code = "CN",
};
/**
* @brief wifi_sta_connect
* wifi station mode connect start
* @param ssid
* @param password
*/
static void wifi_sta_connect(char* ssid, char* password)
{
wifi_interface_t wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}
/**
* @brief tcp_client_task
*
* @param arg
*/
static void tcp_client_task(void* arg)
{
blog_info("tcp client task run\r\n");
int socketfd;
int ret = 0;
char* tcp_buff = pvPortMalloc(512);
memset(tcp_buff, 0, 512);
socketfd = tcp_client_init(TCP_SERVER_IP, TCP_SERVER_PORT);
if (!tcp_client_connect(socketfd)) {
blog_info("%s:tcp client connect OK\r\n", __func__);
}
else goto __exit;
if (tcp_client_send(socketfd, "hell tcp server")<0) {
printf("tcp client send fail\r\n");
goto __exit;
}
else
blog_info("tcp client send OK\r\n");
while (1) {
ret = tcp_client_receive(socketfd, tcp_buff);
if (ret>0) {
blog_info("%s:tcp receive data:%s \r\n", __func__, tcp_buff);
if (strstr(tcp_buff, "close")) goto __exit;
memset(tcp_buff, 0, 512);
}
vTaskDelay(100/portTICK_PERIOD_MS);
}
__exit:
vPortFree(tcp_buff);
tcp_client_deinit(socketfd);
vTaskDelete(NULL);
}
/**
* @brief event_cb_wifi_event
* wifi connet ap event Callback function
* @param event
* @param private_data
*/
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
static char* ssid;
static char* password;
switch (event->code)
{
case CODE_WIFI_ON_INIT_DONE:
{
printf("[APP] [EVT] INIT DONE %lld\r\n", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
printf("[APP] [EVT] MGMR DONE %lld\r\n", aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
printf("[APP] [EVT] SCAN Done %lld\r\n", aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
printf("[APP] [EVT] disconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
printf("[APP] [EVT] Connecting %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
printf("[APP] [EVT] Reconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
printf("[APP] [EVT] GOT IP %lld\r\n", aos_now_ms());
printf("[SYS] Memory left is %d Bytes\r\n", xPortGetFreeHeapSize());
//WiFi connection succeeded, create TCP client task
xTaskCreate(tcp_client_task, (char*)"tcp_client_task", 1024*2, NULL, 16, NULL);
}
break;
case CODE_WIFI_ON_PROV_SSID:
{
printf("[APP] [EVT] [PROV] [SSID] %lld: %s\r\n",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (ssid)
{
vPortFree(ssid);
ssid = NULL;
}
ssid = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_BSSID:
{
printf("[APP] [EVT] [PROV] [BSSID] %lld: %s\r\n",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (event->value)
{
vPortFree((void*)event->value);
}
}
break;
case CODE_WIFI_ON_PROV_PASSWD:
{
printf("[APP] [EVT] [PROV] [PASSWD] %lld: %s\r\n", aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (password)
{
vPortFree(password);
password = NULL;
}
password = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
printf("[APP] [EVT] [PROV] [CONNECT] %lld\r\n", aos_now_ms());
printf("connecting to %s:%s...\r\n", ssid, password);
wifi_sta_connect(ssid, password);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
printf("[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n", aos_now_ms());
}
break;
default:
{
printf("[APP] [EVT] Unknown code %u, %lld\r\n", event->code, aos_now_ms());
/*nothing*/
}
}
}
static void proc_main_entry(void* pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
void main()
{
puts("[OS] Starting TCP/IP Stack...\r\n");
tcpip_init(NULL, NULL);
puts("[OS] proc_main_entry task...\r\n");
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}常见问题与踩坑提示
⚠️ connect OK 但 send 失败
原因:服务器主动断开、网络中断,或发送数据超长(TCP 缓冲有限)
解决:发送前检查 socket 状态;单次发送控制在 1KB 内;失败后 tcp_client_deinit 重连
⚠️ receive 一直收不到数据
原因:服务器未回发、或对端防火墙拦截
解决:先确认服务器确实会回发数据;局域网测试时关闭电脑防火墙,确认端口放行
⚠️ 局域网 IP 写在代码里,路由器重启后失效
原因:电脑 IP 由 DHCP 动态分配
解决:电脑设置静态 IP;或服务端用域名(先 DNS 解析,见官方 dns 示例)
⚠️ 内存不足(malloc 失败)
原因:任务栈与缓冲区占用过大
解决:任务栈用官方 1024*2;接收缓冲 512B 够用则不要加大;用 xPortGetFreeHeapSize() 监控
⚠️ 一直打印 Connecting,始终没有 GOT IP(连不上路由器)
原因:SSID/密码填错、路由器是 5GHz、或信号太弱
解决:核对 tcp_client/main.c 里 ROUTER_SSID/ROUTER_PWD 与路由器完全一致;确认路由器是 2.4GHz(开发板不支持 5GHz);把开发板靠近路由器再试
⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或数据线只能充电不能传数据
解决:Linux 用 lsusb/dmesg 查看设备,权限不足可 sudo chmod 666 /dev/ttyUSB0;Windows 装驱动后到设备管理器查 COM 口;换一根能传数据的线
⚠️ 烧录一直等待 / 失败
原因:未进入下载模式、波特率不对、或串口号填错
解决:烧录时按提示长按 EN 键进入下载模式;确认 p=/dev/ttyUSB0 换成你实际的串口;换 USB 口或数据线重试
运行自检
串口打印 tcp client connect OK 与 tcp client send OK,并能收到服务器回发数据,即 TCP 客户端验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

