Skip to content

Overview

When the board acts as a TCP server, it listens on a fixed port waiting for clients (phone/computer/other devices) to connect, commonly used for LAN direct control (e.g. device control after provisioning, tool direct-connect debugging). This tutorial demonstrates: starting a TCP server listening on port 7878, echoing received data back to the connected client, and disconnecting that client on receiving close.

In plain words: the roles are swapped in this lesson — the board plays customer service: first it claims a seat (listens on port 7878; a port is the "door number" on a server), then sits waiting for others to call in (client connections). Once the call connects, it reads back what the other side says verbatim (echo), and when the other side says "hang up" (close) it drops that line. TCP's reliable traits — dial first then talk, auto re-send on lost words — apply on the server side just the same.

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_server; the code can be found directly in your local SDK.

🎯Page GoalStart a TCP server on the board and verify it with a computer client, mastering listening, accepting connections, sending/receiving data and disconnect handling.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② A 2.4GHz router ③ A computer (TCP debug tool, e.g. a network debugging assistant) ④ Environment set up per [SDK Installation](../../sdk/sdk_intro) and completed [Connect Wi-Fi](./wifi_connect).
🔗RelatedClient role: [TCP Client](./tcp_client); comparison with the UDP server: [UDP Server](./udp_server).

Enter the Example Project

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

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

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

Modify the Router Parameters

Open tcp_server/main.c and change the SSID/password at the top (same as Connect Wi-Fi):

#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"

#define TCP_SERVER_PORT 7878

💡 TCP_SERVER_PORT 7878 is the listening port (the “door number” on the server; clients must connect to the same port), changeable as you wish (avoid common ports). The server and the computer client must be on the same LAN, and the router must not have AP isolation enabled.

Write the Code

Open tcp_server/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_server/tcp_server/main.c).

Code highlights:

Code Purpose
tcp_server_init(NULL, 7878) Creates the socket and binds and listens on port 7878; only after claiming the seat can anyone call in
tcp_server_accept(fd, cb) Blocks waiting for client connections; each join automatically creates a callback task handling that connection; one client one line, no interference
tcp_client_msg_t Connection info struct: socket_id (connection ID), ip_addr (client address); the callback uses it to know “who called”
tcp_server_receive(socket_id, data) Receives that client’s data; listening to what the other side said
tcp_server_send(socket_id, data) Echoes data back to that client; reading it back verbatim so the other side knows you received it
inet_ntoa(socket_addr->sin_addr.s_addr) Prints the client IP; translating the binary address into human-readable numbers
tcp_server_close(socket_id) Disconnects that client on receiving close; when the other side says hang up, drop the line

💡 The official wrapper supports multiple clients: tcp_server_accept creates an independent callback task for every accepted connection (implemented in src/tcp_server.c), each connection isolated from the others.

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_server.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). First you see GOT IP; note the board’s IP address (check it in the router admin page, or it’s shown by the UART logs).

Open a network debugging assistant on your computer (TCP Client mode) and connect:

Setting Value
Server IP The board’s IP (e.g. 192.168.1.100)
Port 7878

After connecting, send hello wb2 to the board; the serial prints:

tcp_server task run
192.168.1.50:hello wb2

The computer assistant receives the echo hello wb2 at the same time (the server returns the data verbatim). After sending close the board disconnects the connection and the assistant reports the connection closed.

The serial printing tcp_server task run and “client IP:content” (e.g. 192.168.1.50:hello wb2), plus the computer assistant receiving the echo hello wb2 means success. If the computer reports a connection failure, first confirm the board is online (you saw GOT IP), the computer and board are on the same LAN, and the port filled in is 7878, see the FAQ at the end.

💡 A phone works too: connect the phone to the same Wi-Fi, use a TCP debug App to connect to the board’s IP:7878, same result.


API Summary for This Tutorial

tcp_server_init(s_ip, s_port)

Creates the socket and binds the listening port (project wrapper; pass NULL as s_ip to listen on all local addresses).

Parameters:

  • s_ip: listen address; pass NULL to listen on all addresses
  • s_port: listening port (official example 7878)

Return: socket descriptor on success; -1 on failure

tcp_server_accept(socketfd, cb)

Accepts client connections, calling the callback once per accepted connection (project wrapper).

Parameters:

  • socketfd: listening socket descriptor
  • cb: connection callback function (arguments include the connection ID and client address)

Return: 0 on success; negative error code on failure

tcp_server_receive(socket_id, recv_data)

Receives data from the specified connection (project wrapper).

Parameters:

  • socket_id: connection ID
  • recv_data: receive buffer

Return: bytes received on success; negative on failure

tcp_server_send(socket_id, data)

Sends data to the specified connection (project wrapper).

Parameters:

  • socket_id: connection ID
  • data: data to send

Return: bytes sent on success; negative on failure

tcp_server_close(socket_id)

Closes the specified client connection (project wrapper).

Parameters:

  • socket_id: connection ID

Return: none

tcp_server_deinit

Releases server resources (project wrapper).

Return: none

bind / listen(fd, addr, len / fd, backlog)

Binds the address / starts listening (the wrapper's underlying layer).

Parameters:

  • fd: socket descriptor
  • addr: local address (struct sockaddr*)
  • len: address length
  • backlog: maximum pending connections

Return: 0 on success; negative error code on failure

accept(fd, addr, len)

Accepts a connection (the wrapper's underlying layer, blocking).

Parameters:

  • fd: listening socket descriptor
  • addr: output parameter, client address
  • len: address length

Return: client socket descriptor on success; -1 on failure

📌 tcp_client_msg_t struct: socket_id (connection ID), socket_fd (listening socket), ip_addr (client struct sockaddr_in*).


Full Code

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

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

FAQ & Troubleshooting

⚠️ Computer can't connect to the board's server
Cause: different subnets, router AP isolation enabled, or a wrong board IP entered
Fix: confirm the computer and board are on the same router and subnet; disable the router's AP isolation (guest network isolation); double-check the board IP

⚠️ Port occupied (bind fails)
Cause: another app occupies 7878, or the previous connection wasn't released
Fix: change the port; reboot the board to release residual connections

⚠️ Echoed content garbled/truncated
Cause: the client sent non-text data (containing \0), or more than 1024 bytes
Fix: with the data[1024] buffer keep single sends under 1KB; for text transfers confirm consistent encoding (UTF-8)

⚠️ Data corrupted when multiple clients connect at once
Cause: callback tasks share a global variable
Fix: pass each connection's data via the tcp_client_msg_t argument; don't share buffers outside the callback; use a semaphore when mutual exclusion is needed

⚠️ 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_server/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

After the computer client connects, sending any content makes the board echo it and the serial prints the client IP and content — the TCP server is verified.

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