Skip to content

Overview

TCP is a connection-oriented reliable transport protocol, widely used for devices reporting data and receiving server commands. The Ai-WB2 implements TCP via lwIP socket (lwIP is a lightweight network library written for embedded devices; socket is the network programming interface it provides), and the official SDK wraps easy-to-use APIs like tcp_client_init/connect/send/receive. This tutorial demonstrates: connecting to a remote TCP server (the official demo server tt.ai-thinker.com:7878), sending data and looping to receive replies.

In plain words: TCP is like making a phone call — first dial (connect), wait for the other side to answer (three-way handshake), then talk (send and receive data), and finally hang up (disconnect). It's reliable: whether the words got through and the other side heard clearly are confirmed by both sides, and lost words are automatically re-sent — so important data like device reports and command pushes all use it. The price is that you must "dial through" before sending, and one phone line can only call one person.

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/protocols/socket/tcp_client; the code can be found directly in your local SDK.

🎯Page GoalConnect to a remote server as a TCP client and send/receive data, mastering the full flow of the wrapped APIs: initialization, connect, send, receive and resource release.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② A 2.4GHz router ③ A computer (can run a TCP test tool or network debugging assistant) ④ Environment set up per [SDK Installation](../../sdk/sdk_intro) and completed [Connect Wi-Fi](./wifi_connect).
🔗RelatedServer role: [TCP Server](./tcp_server); unreliable UDP transport: [UDP Client](./udp_client).

Enter the Example Project

Open a terminal and enter the official tcp_client example project directory:

cd ~/Ai-Thinker-WB2/applications/protocols/socket/tcp_client

Note: cd is the “change directory” command, entering the official example project; all subsequent make commands must run in this directory.

Modify the Server Parameters

Open tcp_client/main.c and change the SSID/password at the top (same as Connect Wi-Fi), and modify the server address as needed:

#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
Macro Default Description
TCP_SERVER_IP 122.114.122.174 Ai-Thinker remote TCP server; no need to build your own server
TCP_SERVER_PORT 7878 Server port (the “door number” on the server); find the right door to connect

💡 Without a public server you can debug over the LAN: run a network debugging assistant on your computer to listen (e.g. 192.168.x.x:8888), then change TCP_SERVER_IP/PORT to your computer’s IP and port.

Write the Code

Open tcp_client/main.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/protocols/socket/tcp_client/tcp_client/main.c).

Code highlights:

Code Purpose
tcp_client_init(ip, port) Creates the TCP socket and initializes (wrapped in tcp_example.h); no creation, no phone call
tcp_client_connect(socketfd) Connects to the server (returns 0 on success); the dialing — busy line fails
tcp_client_send(socketfd, "hell tcp server") Sends data to the server; the “talking” in the call
tcp_client_receive(socketfd, buf) Receives server data (blocking wait, returns byte count); blocking = keeps waiting when there’s no message
strstr(tcp_buff, "close") Disconnects on receiving the close command; when the server says hang up, hang up
tcp_client_deinit(socketfd) Closes the socket and frees resources; not freeing keeps occupying memory
CODE_WIFI_ON_GOT_IP callback After networking succeeds, creates the TCP client task; no IP means the call won’t go through either

💡 The wrapper APIs are implemented in the project’s src/tcp_example.c; underneath they’re the standard lwIP socket: socket() / connect() / send() / recv() / close().

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program flashed into the board) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/tcp_client.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board. After p= comes the serial device (often /dev/ttyUSB0 on Linux, COM3-like on Windows — use your computer’s actual one), b=921600 is the flash baud rate (serial transfer speed), keep the default.

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.

Run and Verify

After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600, the “speech speed” of serial data — both ends must match) and observe the logs:

[APP] [EVT] GOT IP 5
tcp client task run
tcp_client_task:tcp client connect OK
tcp client send OK

After connecting to the official server, the server sends data back and the serial prints the received content:

tcp_client_task:tcp receive data:hello, WB2 !

tcp client connect OK and tcp client send OK appearing, plus receiving the server’s reply (tcp receive data:...) means success. If you only see GOT IP without connect OK, first confirm the board is online and the target server IP/port is reachable (try on your computer whether you can connect to this IP and port), see the FAQ at the end.

💡 LAN self-test: open a network debugging assistant on your computer to listen, same LAN via phone hotspot or router; change TCP_SERVER_IP/TCP_SERVER_PORT to your computer’s IP and port; after flashing, the assistant receives hell tcp server; send any string to the board and the serial prints the received content; sending close disconnects (the serial stops sending/receiving).


API Summary for This Tutorial

tcp_client_init(server_ip, port)

Creates the TCP socket and initializes (project wrapper).

Parameters:

  • server_ip: server IP string
  • port: server port

Return: socket descriptor on success; -1 on failure

tcp_client_connect(socket_fd)

Connects to the TCP server (project wrapper).

Parameters:

  • socket_fd: socket descriptor returned by tcp_client_init

Return: 0 on success; negative error code on failure

tcp_client_send(socket_fd, data)

Sends data (project wrapper).

Parameters:

  • socket_fd: socket descriptor
  • data: the data string to send

Return: bytes sent on success; <0 on failure

tcp_client_receive(socket_fd, data)

Receives data (blocking, project wrapper).

Parameters:

  • socket_fd: socket descriptor
  • data: receive buffer

Return: bytes received on success; negative on failure

tcp_client_deinit(socket_fd)

Closes the socket and frees resources (project wrapper).

Parameters:

  • socket_fd: socket descriptor

Return: none

socket(domain, type, proto)

Creates a socket (the wrapper's underlying layer).

Parameters:

  • domain: AF_INET (IPv4)
  • type: SOCK_STREAM (streaming TCP)
  • proto: pass 0

Return: socket descriptor on success; -1 on failure

connect(fd, addr, len)

Connects to the server (the wrapper's underlying layer).

Parameters:

  • fd: socket descriptor
  • addr: server address (struct sockaddr*)
  • len: address length

Return: 0 on success; negative error code on failure

send / recv(fd, buf, len, flags)

Sends / receives data (the wrapper's underlying layer).

Parameters:

  • fd: socket descriptor
  • buf: data buffer
  • len: buffer length
  • flags: flags, pass 0

Return: bytes sent/received on success; negative on failure

pvPortMalloc / vPortFree(size / ptr)

FreeRTOS dynamic memory allocation / release.

Parameters:

  • size: bytes to allocate
  • ptr: pointer to free (return of pvPortMalloc)

Return: pvPortMalloc: pointer on success, NULL on failure; vPortFree: none

📌 Pass the string directly when the server address is an IP; domain servers must first be resolved to an IP via DNS (see the official dns example).


Full Code

Below is the complete tcp_client/main.c source, identical to the official example (applications/protocols/socket/tcp_client/tcp_client/main.c):

📜 Click to expand the full tcp_client/main.c code
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);
}

FAQ & Troubleshooting

⚠️ connect OK but send fails
Cause: the server disconnected proactively, network interrupted, or the sent data is too long (limited TCP buffer)
Fix: check the socket state before sending; keep single sends under 1KB; after failure tcp_client_deinit then reconnect

⚠️ receive never gets data
Cause: the server doesn't reply, or the peer's firewall blocks it
Fix: first confirm the server actually replies; for LAN tests turn off the computer's firewall and confirm the port is allowed

⚠️ LAN IP hardcoded in code, invalid after router restart
Cause: the computer's IP is dynamically assigned by DHCP
Fix: set a static IP on the computer; or use a domain on the server side (resolve via DNS first, see the official dns example)

⚠️ Insufficient memory (malloc fails)
Cause: task stack and buffers take too much
Fix: use the official 1024*2 task stack; keep the receive buffer at 512B if it suffices; monitor with xPortGetFreeHeapSize()

⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: double-check ROUTER_SSID/ROUTER_PWD in tcp_client/main.c match the router exactly; confirm the router is 2.4GHz (the board doesn't support 5GHz); try moving the board closer to the router

⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable

⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable

Self-Check

The serial prints tcp client connect OK and tcp client send OK, and receives the server's reply — the TCP client is verified.

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