概述
开发板作为 TCP 服务器时,会监听固定端口等待客户端(手机/电脑/其他设备)连接,常用于局域网直连控制(如配网后的设备控制、工具直连调试)。本教程演示:开启 TCP 服务器监听 7878 端口,客户端连接后回显收到的数据,收到 close 时断开该客户端。
用大白话讲:这课角色换过来了——开发板自己当客服接线员:先占好一个工位(监听 7878 端口,端口就是服务器上的「房门号」),然后坐等别人打进来(客户端连接),接通后把对方说的话原样复读回去(回显),对方说「挂断」(close)就挂掉这条线。TCP 先拨通再说话、丢字自动重说的可靠特性,在服务器这一端同样适用。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/protocols/socket/tcp_server编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 tcp_server 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/protocols/socket/tcp_server
说明:
cd是「进入目录」的命令,进入官方示例工程;后面所有make命令都要在这个目录下执行。
打开 tcp_server/main.c,修改开头的 SSID/密码(同 连接 Wi-Fi):
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
#define TCP_SERVER_PORT 7878
💡
TCP_SERVER_PORT 7878为监听端口(服务器上的「房门号」,客户端必须连同一个端口),可任意修改(避开常用端口)。服务器与电脑客户端需在同一局域网,且路由器未开启 AP 隔离。
打开 tcp_server/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/protocols/socket/tcp_server/tcp_server/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
tcp_server_init(NULL, 7878) |
创建 socket 并绑定监听 7878 端口;占好工位才有人打得进来 |
tcp_server_accept(fd, cb) |
阻塞等待客户端连接,每接入一个自动创建回调任务处理该连接;一个客户一条线,互不干扰 |
tcp_client_msg_t |
连接信息结构体:socket_id(连接 ID)、ip_addr(客户端地址);回调靠它知道「是谁打来的」 |
tcp_server_receive(socket_id, data) |
接收该客户端的数据;听对方说了什么 |
tcp_server_send(socket_id, data) |
回显数据给该客户端;原样复读回去,对方才知道你收到了 |
inet_ntoa(socket_addr->sin_addr.s_addr) |
打印客户端 IP;把二进制的地址翻译成人能看的数字 |
tcp_server_close(socket_id) |
收到 close 时断开该客户端连接;对方说挂断就挂线 |
💡 官方封装支持多客户端:
tcp_server_accept每接受一个连接就创建一个独立回调任务(在src/tcp_server.c中实现),各连接互不干扰。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/tcp_server.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件下载进开发板;p=后面是串口设备号(Linux 下常为/dev/ttyUSB0,Windows 下是COM3之类,以你电脑实际为准),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,串口传数据的「语速」,两边必须一致),先看到 GOT IP,记下开发板 IP 地址(可在路由器后台查看,或由 UART 日志 显示)。
电脑打开网络调试助手(TCP Client 模式)连接:
| 配置项 | 值 |
|---|---|
| 服务器 IP | 开发板 IP(如 192.168.1.100) |
| 端口 | 7878 |
连接成功后向开发板发送 hello wb2,串口打印:
tcp_server task run
192.168.1.50:hello wb2
电脑助手同步收到回显 hello wb2(服务器把数据原样返回)。发送 close 后连接被开发板断开,助手提示连接断开。
串口打印 tcp_server task run 和「客户端 IP:内容」(如 192.168.1.50:hello wb2),且电脑助手收到回显 hello wb2 即成功。如果电脑提示连接失败,先确认开发板已联网(先看到 GOT IP)、电脑与开发板在同一局域网、端口填的是 7878,见文末 FAQ。
💡 手机也可测试:手机连接同一 Wi-Fi,用 TCP 调试 App 连接开发板 IP:7878,效果相同。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
tcp_server_init(s_ip, s_port)
创建 socket 并绑定监听端口(工程封装,s_ip 传 NULL 监听本机所有地址)。
参数:
s_ip:监听地址,传NULL表示监听所有地址s_port:监听端口(官方示例7878)
返回值:成功返回 socket 描述符;失败返回 -1
tcp_server_accept(socketfd, cb)
接受客户端连接,每接入一个连接调用一次回调(工程封装)。
参数:
socketfd:监听 socket 描述符cb:连接回调函数(参数含连接 ID、客户端地址)
返回值:成功返回 0;失败返回负值错误码
tcp_server_receive(socket_id, recv_data)
接收指定连接的数据(工程封装)。
参数:
socket_id:连接 IDrecv_data:接收缓冲区
返回值:成功返回接收字节数;失败返回负值
bind / listen(fd, addr, len / fd, backlog)
绑定地址 / 开始监听(封装层底层)。
参数:
fd:socket 描述符addr:本地地址(struct sockaddr*)len:地址长度backlog:最大等待连接数
返回值:成功返回 0;失败返回负值错误码
accept(fd, addr, len)
接受连接(封装层底层,阻塞)。
参数:
fd:监听 socket 描述符addr:输出参数,客户端地址len:地址长度
返回值:成功返回客户端 socket 描述符;失败返回 -1
📌
tcp_client_msg_t结构:socket_id(连接 ID)、socket_fd(监听 socket)、ip_addr(客户端struct sockaddr_in*)。
完整代码
以下为 tcp_server/main.c 完整源码,与官方示例(applications/protocols/socket/tcp_server/tcp_server/main.c)完全一致:
📜 点击展开 tcp_server/main.c 完整代码
/**
* @file main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2022-10-13
*
* @copyright Copyright (c) 2022
*
*/
#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_server.h"
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
#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_accpet_handle_cb
*
* @param arg
*/
static void tcp_accpet_handle_cb(void* arg)
{
tcp_client_msg_t* tcp_client_msg = (tcp_client_msg_t*)arg;
struct sockaddr_in* socket_addr = (struct sockaddr_in*)tcp_client_msg->ip_addr;
int ret = 0;
char data[1024] = { 0 };
while (1) {
ret = tcp_server_receive(tcp_client_msg->socket_id, data);
if (ret>0) {
printf("%s:%s\r\n", inet_ntoa(socket_addr->sin_addr.s_addr), data);
tcp_server_send(tcp_client_msg->socket_id, data);
if (strstr(data, "close")!=NULL) tcp_server_close(tcp_client_msg->socket_id);
}
vTaskDelay(500/portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
/**
* @brief tcp_server_task
*
* @param arg
*/
static void tcp_server_task(void* arg)
{
int socket_fd;
socket_fd = tcp_server_init(NULL, 7878);
tcp_server_accept(socket_fd, tcp_accpet_handle_cb);
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 server task
xTaskCreate(tcp_server_task, "tcp_server_task", 2048, 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);
}常见问题与踩坑提示
⚠️ 电脑连不上开发板服务器
原因:不同网段、路由器开 AP 隔离、或开发板 IP 输入错误
解决:确认电脑与开发板同一路由器同一网段;关闭路由器 AP 隔离(访客网络隔离);核对开发板 IP
⚠️ 端口被占用(bind 失败)
原因:其他应用占用了 7878,或上次连接未释放
解决:更换端口;重启开发板释放残留连接
⚠️ 回显内容乱码/截断
原因:客户端发送非文本数据(含 \0),或超过 1024 字节
解决:data[1024] 缓冲区够用则单次发送控制在 1KB 内;文本传输确认编码一致(UTF-8)
⚠️ 多个客户端同时连接时数据错乱
原因:回调任务间共享了全局变量
解决:每连接的数据都通过 tcp_client_msg_t 参数传递,不要在回调外共享 buffer;需要互斥时用信号量
⚠️ 一直打印 Connecting,始终没有 GOT IP(连不上路由器)
原因:SSID/密码填错、路由器是 5GHz、或信号太弱
解决:核对 tcp_server/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 口或数据线重试
运行自检
电脑客户端连接后发送任意内容,开发板回显且串口打印客户端 IP 与内容,即 TCP 服务器验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

