Skip to content

Contributed by WildboarG, organized by Ai-Thinker

[AI-WB2 Advanced] TCP Wireless Communication

Last edited by WildboarG at 2024-9-12 16:26 First, let's look at the concept of network communication: Network communication is the process of data exchange between different devices (such as computers, mobile phones, servers, etc.) in a computer network through certain communication protocols and transmission media (such as wired, cable, optical fiber, or wireless networks).

Basic Principles of Network Communication

Network communication is mainly implemented by sending and receiving data packets. This process can be divided into the following stages:

1. Data Encapsulation

When an application needs to send data, the data first goes through an application layer protocol (such as HTTP or FTP) and is then encapsulated layer by layer. Each protocol layer adds the corresponding header information (such as destination address, sequence number, etc.) to the packet so that the receiver can correctly de-encapsulate it.

2. Data Transmission

Data is transmitted over the network through electrical or optical signals. Depending on the transmission method, it can be divided into:

  • Wired transmission: for example, over Ethernet or optical fiber.
  • Wireless transmission: for example, over Wi-Fi, mobile networks (4G, 5G), and other wireless methods.

3. Routing and Forwarding

In complex networks, data packets usually do not reach their destination directly but are forwarded through multiple network nodes (such as routers). The router decides which path a packet should be forwarded to based on the packet's destination address.

4. Data De-encapsulation

When the data packet reaches its destination, the receiver de-encapsulates the data layer by layer, removing the header information of each protocol layer until the original data is obtained and passed to the application.

The OSI Seven-Layer Model

The OSI (Open System Interconnect) seven-layer model is a standardized framework that divides computer network communication protocols into seven distinct layers. Each layer is responsible for different functions, from the physical connection to application processing. This model helps different systems better understand and manage the network communication process when communicating with each other. OSI defines the seven-layer framework for network interconnection (physical layer, data link layer, network layer, transport layer, session layer, presentation layer, and application layer), i.e., the ISO Open Systems Interconnection Reference Model. Although this section focuses on the TCP protocol, which belongs to the transport layer, it is still worth understanding what data goes through during network transmission so that encapsulation and decapsulation make sense. Pastedimage20240912102650.png User data, i.e., your data (assume it is transmitted over HTTP), gets an HTTP header added by the application layer and is passed down to the transport layer. The transport layer adds a TCP header to the packet, turning it into a TCP packet, which is then passed to the network layer. The network layer adds an IP header at the front, making it an IP packet, which is then passed to the link layer, where the Ethernet MAC or Wi-Fi (802.11) MAC prepends its own header, turning the packet into an Ethernet frame or an 802.11 frame. The PHY network card then converts the data into electrical/optical signals at the physical layer and sends them to the router, which forwards the data to the server. The server decodes the electrical/optical signals from the physical layer into Ethernet frames all the way up to the application layer, stripping off each layer's header one by one until the message is delivered to the server. The WB2 uses Wi-Fi, and the counterpart of the Ethernet frame is the 802.11 frame format. Its frame format is as follows: Pastedimage20240912102858.png

1. Frame Control – 2 Bytes

The Frame Control field contains several subfields that define information such as the frame type, encryption status, and priority:

BitsNameDescription
0-1Protocol VersionUsually 0, indicating the current protocol version (802.11).
2-3TypeIndicates the frame type: management frame, control frame, or data frame.
4-7SubtypeSpecifies the frame subtype, e.g., normal data or QoS data within data frames.
8To DSIndicates whether the frame is destined for the distribution system (usually the access point).
9From DSIndicates whether the frame is sent from the distribution system (access point).
10More FragmentsIndicates whether the frame is one of more fragments.
11RetryIndicates whether the frame is a retransmission.
12Power ManagementIndicates whether the device is in power-save mode.
13More DataIndicates whether the access point has more frames pending for this device.
14Protected FrameIndicates whether the frame is encrypted.
15OrderIndicates whether the frame must be strictly ordered.

2. Duration/ID – 2 Bytes

The Duration field indicates how long the current frame occupies the wireless medium; it is commonly used for the RTS/CTS mechanism, or it represents the slot duration in a network packet-switching system. For some control frames (such as PS-Poll), this field carries an ID.

3. Address Fields – 6 Bytes Each

A data frame usually contains three or four address fields; which addresses are used depends on the frame's transmission mode (e.g., whether relaying is involved). The address fields typically include the following:

  • Address 1 (Destination Address): the MAC address of the destination device.
  • Address 2 (Source Address): the MAC address of the sending device.
  • Address 3 (BSSID or relay address): the Basic Service Set Identifier (BSSID) or the MAC address of the access point (AP), used in infrastructure mode.
  • Address 4 (optional): this field may be used when the frame is transmitted in a Wireless Distribution System (WDS).

4. Sequence Control – 2 Bytes

The Sequence Control field consists of two parts:

  • Sequence number: used for frame ordering, ensuring that the receiver can reassemble fragmented frames in the correct order and detect duplicate frames.
  • Fragment number: indicates whether the frame was transmitted as a fragment and which fragment this frame is.

5. Frame Body – Variable Length

The Frame Body contains the actual user data or control information. For example, for data frames, the frame body carries the IP packet (including the IP header and TCP/UDP data). For management or control frames, the frame body contains fields specific to the frame's management operations (such as authentication and association).

6. Frame Check Sequence (FCS) – 4 Bytes

The FCS field is used to detect errors during frame transmission. It is computed with the Cyclic Redundancy Check (CRC) algorithm and appended to the end of the frame. The receiver can verify that the data was transmitted correctly by computing the frame's FCS value. The frame body contains the IP packet. Next, we unpack the IP frame. Pastedimage20240912124311.png The IPv4 datagram header is usually 20 bytes long, but it can be longer when option fields are used. The structure of an IPv4 datagram is shown below:

IPv4 Datagram Header Structure

Field NameSize (bytes)Description

Version
4 bitsThe IP protocol version number; it is 4 for IPv4.
IHL4 bitsThe length of the header in 32-bit words (minimum 5, i.e., 20 bytes).
TOS1 byteType of Service, used to differentiate data priority.
Total Length2 bytesThe total length of the datagram, including the header and data, up to 65535 bytes.
Identification2 bytesAn identifier used to identify datagram fragments.
Flags3 bitsControl datagram fragmentation (e.g., the "More Fragments" bit).
Fragment Offset13 bitsUsed for reassembling fragmented datagrams.
TTL1 byteTime to Live: the number of hops the datagram may survive in the network (each router decrements TTL by 1; the datagram is discarded when TTL reaches 0).
Protocol1 byteIndicates the upper-layer protocol, e.g., TCP (6) or UDP (17).
Header Checksum2 bytesUsed to check header integrity; the receiver verifies whether the header was corrupted in transit by computing the checksum.
Source IP Address4 bytesThe IP address of the source device.
Destination IP Address4 bytesThe IP address of the destination device.
OptionsOptionalAn optional field for special purposes such as debugging and routing control (rarely used).
PaddingOptionalEnsures that the header is a multiple of 32 bits.
The data part is a TCP or UDP packet. Since we are discussing TCP here, it is a TCP packet.
Unpacking the TCP frame:
Pastedimage20240912102335.png
① 32-bit sequence number: the starting sequence number carried by this TCP segment.
② 32-bit acknowledgment number: the sequence number from which the sender expects the other side to start sending data.
③ 4-bit header length: the maximum value is 0xF (15); it indicates the length of the TCP header.
Header length = 4-bit header length (DEC) * 4, in bytes.
Note: DEC means a decimal number.
④ 6 flag bits (6 bits):
URG: urgent flag (data waiting in the send buffer is prioritized and transmitted to the network layer directly; it is usually used together with the 16-bit urgent pointer below)
ACK: acknowledgment flag
PSH: push flag (send data)
RST: reset flag (used when a connection request from the other side cannot be recognized). In other words, when parties X and Y are disconnecting, X believes the connection is already closed while Y still thinks it is open. At this point, when Y sends a data packet to X, X replies to Y with a packet carrying the RST flag.
SYN: synchronization flag (initiate a connection)
FIN: finish flag (when one side of the connection wants to end the connection, this is called disconnecting)
⑤ 16-bit window size: tells the sender how much data the receiver can accept; this value changes dynamically.
⑥ 16-bit checksum: verifies whether the data was corrupted during transmission.
⑦ 16-bit urgent pointer: used with the URG flag to send out-of-band data (urgent data).
⑧ MSS: Maximum Segment Size
Click to expand full code
在三次握手过程中,双方协商MSS的大小,取两者的最小值。

What follows is the application layer data. OK. To use TCP, you need to understand how TCP works. As a reliable full-duplex protocol, TCP requires a reliable connection and transmission mechanism. TCP (Transmission Control Protocol) is a connection-oriented, reliable, byte-stream-based communication protocol. TCP treats the connection as its most basic abstraction unit. Each TCP connection has two endpoints, and the endpoints of a TCP connection are sockets. Socket = (IP address + port number) TCP connection = {socket1, socket2} = {(IP1:port1), (IP2:port2)} TCP provides full-duplex communication. TCP establishes a reliable connection through the three-way handshake and uses the four-way wave to ensure that data transmission is not interrupted before both sides disconnect. Before looking at connection establishment and teardown, let's first understand the 11 TCP states and what they mean.

The 11 TCP States:

  1. CLOSED (closed)
  • No connection exists, or the connection has ended.
  • This is the initial or terminal state.
  1. LISTEN (listening)
  • The server is waiting for connection requests; this state is usually entered by calling socket() and listen().
  • The server waits in this state for connection requests from clients.
  1. SYN-SENT (synchronization sent)
  • The client has sent a SYN request and is waiting for the server's SYN-ACK response.
  • The client enters this state when calling connect(), indicating that it has initiated a connection request.
  1. SYN-RECEIVED (synchronization received)
  • The server has received the client's SYN request and sent a SYN-ACK response, and is waiting for the client's acknowledgment (ACK).
  • After receiving SYN and replying with SYN-ACK, the server enters this state, which marks the initial phase of the connection.
  1. ESTABLISHED (connection established)
  • The connection has been successfully established, and the client and server can send and receive data to each other.
  • After the client receives the server's SYN-ACK and replies with ACK, both the client and the server enter this state.
  1. FIN-WAIT-1 (finish wait 1)
  • One side has actively initiated connection closure, sent a FIN request, and is waiting for the other side's ACK.
  • A side enters this state after calling close() and sending a FIN packet.
  1. FIN-WAIT-2 (finish wait 2)
  • Waiting for the other side to send FIN; this side has already received the ACK for its own FIN and is waiting for the other side's FIN request.
  • A side enters this state after receiving the other side's ACK.
  1. CLOSE-WAIT (close wait)
  • The passively closing side has received the FIN and is waiting for the local application to finish processing the data and issue a close request.
  • This state is entered after receiving the other side's FIN, and the local application is awaited to call close().
  1. CLOSING (closing)
  • Both sides send FIN requests simultaneously, and both are waiting for the other side's ACK.
  • This state is rare and indicates that both sides closed the connection almost simultaneously.
  1. LAST-ACK (last acknowledgment)
  • The passively closing side has sent a FIN request and is waiting for the other side's ACK.
  • After sending FIN and receiving an ACK, a side enters this state and waits for the other side's final acknowledgment.
  1. TIME-WAIT (time wait)
  • The actively closing side waits for a period of time to ensure that the other side has received the ACK.
  • After entering this state, it waits for 2 maximum segment lifetimes (2MSL, usually from a few seconds to a few minutes) to ensure that all packets have been delivered and acknowledged before entering CLOSED. Now that we roughly understand what these 11 states mean, let's look at the TCP three-way handshake and four-way wave. Three-way handshake (TCP connection establishment): Pastedimage20240912133044.png Four-way wave (connection teardown): Pastedimage20240912133056.png What exactly do the three-way handshake and the four-way wave do? This is where the TCP states described earlier come in. Let's look at the state transition diagram of both sides to see what connection establishment and teardown actually do: Pastedimage20240712094715.png First of all, TCP is full-duplex communication, which means there are two invisible channels for sending and receiving (2 channels). Connection establishment:
  1. Initially, both the client and the server are in the CLOSED state.
  2. When the two sides want to establish a connection, the server starts listening on a port and enters the LISTEN state. The client initiates the first handshake (CONNECT/SYN) and sends a SYN synchronization packet (indicating that the client wants to establish a sending channel 1 to the server). At this point, the client transitions to the SYN-SENT state.
  3. The server detects the client's SYN handshake request, accepts the client's connection request, and immediately replies with an ACK packet. The server then enters the SYN-RECEIVED state. The server also needs to establish a channel for sending information to the client, so it sends its own SYN packet (indicating that the server wants to establish another sending channel 2 to the client).
  4. The client receives the server's SYN+ACK reply and, knowing that the server has acknowledged its identity and also wants to establish a connection, replies with an ACK signal to agree to the connection.
  5. The server then receives the ACK reply, and both sides transition to the ESTABLISHED state, meaning the connection is successfully established. Established: Send some data...... Connection teardown:
  6. When side A finishes sending data and has nothing left to do, it wants to break the connection. It first sends a FIN close-request signal (to close channel 1) and transitions to the FIN-WAIT-1 state. The other side, B, receives the FIN request and agrees, but asks A to wait until it finishes sending the data to A, then replies with an ACK signal and transitions to the CLOSE-WAIT state.
  7. After A receives the ACK, it agrees to wait until B is done talking and transitions to the FIN-WAIT-2 state.
  8. After some time, B finishes sending all its data and sends the FIN signal (to close channel 2), transitioning to the LAST-ACK state and waiting for A to reply with an ACK.
  9. A replies with an ACK and transitions to the TIME-WAIT state. After a timeout period, both sides are disconnected. A simple way to understand it: Establishing a connection means A gives B a sending channel (SYN) and must get B's acknowledgment (ACK). Since TCP is full-duplex, B also gives A a sending channel (SYN), which also requires A's acknowledgment (ACK). Only then is the connection successfully established. Disconnecting can happen in several ways, but the general idea is: A says it wants to disconnect and sends FIN to close its channel; B agrees (ACK), but may still have data to send to A and says "wait a moment"; once B finishes sending, it disconnects its other channel (FIN); A, after waiting, finally receives the agreement (ACK), and after two wait periods both sides are automatically disconnected. That is all you need to understand, because when we use TCP we program with SOCKET sockets, which makes the above less important. What is a socket? A socket is an abstraction layer between the application layer and the transport layer. It abstracts the complex operations of the TCP/IP layers into a few simple interfaces for the application layer to call, enabling processes to communicate over the network. Socket originated from UNIX. Under the Unix philosophy that "everything is a file", a socket is an implementation of the "open — read/write — close" pattern: the server and client each maintain a "file". After the connection is established and opened, either side can write content to its own file for the other side to read, or read the other side's content. When communication ends, the file is closed. A socket is an implementation of the "open — read/write — close" pattern. Taking a socket communicating over TCP as an example, its interaction flow is roughly as follows: ) The server creates a socket based on the address type (IPv4, IPv6), socket type, and protocol The server binds an IP address and port number to the socket The server socket listens for requests on the port, ready to accept incoming client connections; at this point the server's socket is not open yet The client creates a socket The client opens its socket and tries to connect to the server socket using the server's IP address and port number The server socket receives the client's request, is passively opened, and starts accepting the client's request until the client returns connection information. At this point the socket enters the blocking state: "blocking" means the accept() method does not return until the client returns connection information, and then the server starts accepting the next client connection request The client connects successfully and sends connection status information to the server The server's accept() method returns and the connection is successful The client writes information to the socket The server reads the information The client closes The server closes The Ai-Thinker WB2 SDK already includes SOCKET TCP programming examples. With just a basic understanding, you can happily write your own socket-based programs. Of course, you must first connect to the network and obtain an IP address before you can use TCP socket programming. As long as you are proficient at creating a socket, connecting, sending data through the socket, receiving data, and closing the socket, you can use it with ease. These 5 steps: Create a socket:
Click to expand full code
int tcp_client_init(char* server_ip, int port)
{
int socket_fd = 0;
if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0))<0) { //指定ipv4版本和连接为TCP类型
blog_error("socket creat fail\r\n");
return -1;
}
memset(&dest, 0, sizeof(dest)); //将ip 端口 都保存到dest 这个结构体中
// inet_aton
dest.sin_family = AF_INET;
dest.sin_port = htons(port);
dest.sin_addr.s_addr = inet_addr(server_ip);
printf("Server ip Address : %s port:%d\r\n", inet_ntoa(dest.sin_addr.s_addr), ntohs(dest.sin_port));
return socket_fd; //创建成功就返回tcp套接字的文件描述符
}

Connect:

Click to expand full code
int tcp_client_connect(int sockect_fd)
{
//通过刚才的文件表示符指定的tcp 控制块 写入ip 端口等信息,由TCP PCB 来发起请求并维护连接。
if (connect(sockect_fd, (struct sockaddr*)&dest, sizeof(dest))!=0) {
printf("tcp client connect servet:%s fail\r\n", inet_ntoa(dest.sin_addr.s_addr));
return -1;
}
else return 0;
}

Send:

Click to expand full code
int tcp_client_send(int sockect_fd, const char* data)
{
//发送就是写入数据到这个TCP PCB控制块 直接用WRITE()函数写就行,linux一切皆文件,socketz和各概念本来就是linux中先有的
return write(sockect_fd, data, strlen(data));
}

Receive:

Click to expand full code
int tcp_client_receive(int sockect_fd, char* data)
{
// 从tcp PCB中读取数据到缓冲区
return read(sockect_fd, data, TCP_CLIENT_BUFF);
}

Close the connection:

Click to expand full code
int tcp_client_deinit(int socket_fd)
{
shutdown(socket_fd, SHUT_RDWR); //关掉连接
return close(socket_fd); //CLOSE 掉这个文件描述符,避免内存溢出
}

Client connection example:

Click to expand full code
#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 "TP-LINK_4450"
#define ROUTER_PWD "HYGS3305"
//This is Ai-Thinker Remote TCP Server: [http://tt.ai-thinker.com:8000/ttcloud](http://tt.ai-thinker.com:8000/ttcloud)
#define TCP_SERVER_IP "192.168.0.123"
#define TCP_SERVER_PORT 43210
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; //声明一个socket的文件描述符
int ret = 0;
char* tcp_buff = pvPortMalloc(512); //申请接收区内存
memset(tcp_buff, 0, 512);
socketfd = tcp_client_init(TCP_SERVER_IP, TCP_SERVER_PORT); //将ip 和 端口填写进去获取一个socket 套接字
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) { //发送hello tcp server
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);
// 连接成功获取IP后,创建TCP发送任务
}
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);
}

The server needs to listen for connections from clients; creating a socket is a bit different from connecting: Create: creation requires a fixed service port and a bound IP

Click to expand full code
int tcp_server_init(char* s_ip, int s_port)
{
struct netif* s_netif;
if (s_ip==NULL) {
s_netif = netif_find("st1");
if (s_netif) {
s_dest.sin_addr.s_addr = s_netif->ip_addr.addr;
}
else {
s_dest.sin_addr.s_addr = inet_addr(s_ip);
}
s_dest.sin_family = AF_INET;
s_dest.sin_port = htons(s_port);
//Creat socket
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd<0) return -1;
//bind IP addr
int ret = bind(socketfd, (struct sockaddr*)&s_dest, sizeof(s_dest));
if (ret<0) {
printf("Socket unable to bind: errno %d\r\n", ret);
return -1;
}
printf("tcp server start ip:%s:%d\r\n", inet_ntoa(s_dest.sin_addr.s_addr), ntohs(s_dest.sin_port));
//listening connections.The maximum number of connections is 4
ret = listen(socketfd, MAX_CLIENT_NUM);
if (ret!=0) {
printf("Error occured during listen: errno %d\r\n", ret);
return -1;
}
printf("tcp server listening.....\r\n");
return socketfd;
}

Wait for connections: listen for connections from different clients and process them.

Click to expand full code
static int sock_fd[MAX_CLIENT_NUM]; //由于连接可能是多个这里要保存每个连接的文件描述符
int tcp_server_accept(int socketfd, tcp_accpet_t tcp_accpet_cb)
{
struct sockaddr_in s_addr;
int sock_cnt = 0;
u32_t socket_len = sizeof(s_addr);
while (1) {
sock_fd[sock_cnt] = accept(socketfd, (struct sockaddr*)&s_addr, &socket_len);
if (sock_fd[sock_cnt]<0) {
printf("Unable to accept connection: errno %d\r\n", sock_fd[sock_cnt]);
return -1;
}
else if (sock_fd[sock_cnt]>0) {
tcp_client.ip_addr = (void*)&s_addr;
tcp_client.socket_fd = sock_fd[sock_cnt];
tcp_client.socket_id = sock_cnt;
xTaskCreate(tcp_accpet_cb, "tcp_accpet_cb", 512, &tcp_client, 17, NULL);
printf("client:%s:%d,id:%d\r\n", inet_ntoa(s_addr.sin_addr.s_addr), sock_fd[sock_cnt], sock_cnt);
sock_cnt++;
}
else {
goto _exit;
}
_exit:
tcp_server_deinit();
return 0;
}

Server example:

Click to expand full code
#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 "CU_e6f6"
#define ROUTER_PWD "c9g3geyu"
#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);
}

Last edited by WildboarG at 2024-9-12 16:26

First, let's look at the concept of network communication:

Network communication is the process of data exchange between different devices (such as computers, mobile phones, servers, etc.) in a computer network through certain communication protocols and transmission media (such as wired, cable, optical fiber, or wireless networks).

Basic Principles of Network Communication

Network communication is mainly implemented by sending and receiving data packets. This process can be divided into the following stages:

1. Data Encapsulation

When an application needs to send data, the data first goes through an application layer protocol (such as HTTP or FTP) and is then encapsulated layer by layer. Each protocol layer adds the corresponding header information (such as destination address, sequence number, etc.) to the packet so that the receiver can correctly de-encapsulate it.

2. Data Transmission

Data is transmitted over the network through electrical or optical signals. Depending on the transmission method, it can be divided into:

  • Wired transmission: for example, over Ethernet or optical fiber.
  • Wireless transmission: for example, over Wi-Fi, mobile networks (4G, 5G), and other wireless methods.

3. Routing and Forwarding

In complex networks, data packets usually do not reach their destination directly but are forwarded through multiple network nodes (such as routers). The router decides which path a packet should be forwarded to based on the packet's destination address.

4. Data De-encapsulation

When the data packet reaches its destination, the receiver de-encapsulates the data layer by layer, removing the header information of each protocol layer until the original data is obtained and passed to the application.

The OSI Seven-Layer Model

The OSI (Open System Interconnect) seven-layer model is a standardized framework that divides computer network communication protocols into seven distinct layers. Each layer is responsible for different functions, from the physical connection to application processing. This model helps different systems better understand and manage the network communication process when communicating with each other.

OSI defines the seven-layer framework for network interconnection (physical layer, data link layer, network layer, transport layer, session layer, presentation layer, and application layer), i.e., the ISO Open Systems Interconnection Reference Model.

Although this section focuses on the TCP protocol, which belongs to the transport layer, it is still worth understanding what data goes through during network transmission so that encapsulation and decapsulation make sense.

User data, i.e., your data (assume it is transmitted over HTTP), gets an HTTP header added by the application layer and is passed down to the transport layer. The transport layer adds a TCP header to the packet, turning it into a TCP packet, which is then passed to the network layer. The network layer adds an IP header at the front, making it an IP packet, which is then passed to the link layer, where the Ethernet MAC or Wi-Fi (802.11) MAC prepends its own header, turning the packet into an Ethernet frame or an 802.11 frame. The PHY network card then converts the data into electrical/optical signals at the physical layer and sends them to the router, which forwards the data to the server. The server decodes the electrical/optical signals from the physical layer into Ethernet frames all the way up to the application layer, stripping off each layer's header one by one until the message is delivered to the server.

The WB2 uses Wi-Fi, and the counterpart of the Ethernet frame is the 802.11 frame format.

Its frame format is as follows:

1. Frame Control – 2 Bytes

The Frame Control field contains several subfields that define information such as the frame type, encryption status, and priority:

BitsNameDescription
0-1Protocol VersionUsually 0, indicating the current protocol version (802.11).
2-3TypeIndicates the frame type: management frame, control frame, or data frame.
4-7SubtypeSpecifies the frame subtype, e.g., normal data or QoS data within data frames.
8To DSIndicates whether the frame is destined for the distribution system (usually the access point).
9From DSIndicates whether the frame is sent from the distribution system (access point).
10More FragmentsIndicates whether the frame is one of more fragments.
11RetryIndicates whether the frame is a retransmission.
12Power ManagementIndicates whether the device is in power-save mode.
13More DataIndicates whether the access point has more frames pending for this device.
14Protected FrameIndicates whether the frame is encrypted.
15OrderIndicates whether the frame must be strictly ordered.

2. Duration/ID – 2 Bytes

The Duration field indicates how long the current frame occupies the wireless medium; it is commonly used for the RTS/CTS mechanism, or it represents the slot duration in a network packet-switching system. For some control frames (such as PS-Poll), this field carries an ID.

3. Address Fields – 6 Bytes Each

A data frame usually contains three or four address fields; which addresses are used depends on the frame's transmission mode (e.g., whether relaying is involved). The address fields typically include the following:

  • Address 1 (Destination Address): the MAC address of the destination device.
  • Address 2 (Source Address): the MAC address of the sending device.
  • Address 3 (BSSID or relay address): the Basic Service Set Identifier (BSSID) or the MAC address of the access point (AP), used in infrastructure mode.
  • Address 4 (optional): this field may be used when the frame is transmitted in a Wireless Distribution System (WDS).

4. Sequence Control – 2 Bytes

The Sequence Control field consists of two parts:

  • Sequence number: used for frame ordering, ensuring that the receiver can reassemble fragmented frames in the correct order and detect duplicate frames.
  • Fragment number: indicates whether the frame was transmitted as a fragment and which fragment this frame is.

5. Frame Body – Variable Length

The Frame Body contains the actual user data or control information. For example, for data frames, the frame body carries the IP packet (including the IP header and TCP/UDP data). For management or control frames, the frame body contains fields specific to the frame's management operations (such as authentication and association).

6. Frame Check Sequence (FCS) – 4 Bytes

The FCS field is used to detect errors during frame transmission. It is computed with the Cyclic Redundancy Check (CRC) algorithm and appended to the end of the frame. The receiver can verify that the data was transmitted correctly by computing the frame's FCS value.

The frame body contains the IP packet. Next, we unpack the IP frame.

The IPv4 datagram header is usually 20 bytes long, but it can be longer when option fields are used. The structure of an IPv4 datagram is shown below:

IPv4 Datagram Header Structure

Field NameSize (bytes)Description
Version4 bitsThe IP protocol version number; it is 4 for IPv4.
IHL4 bitsThe length of the header in 32-bit words (minimum 5, i.e., 20 bytes).
TOS1 byteType of Service, used to differentiate data priority.
Total Length2 bytesThe total length of the datagram, including the header and data, up to 65535 bytes.
Identification2 bytesAn identifier used to identify datagram fragments.
Flags3 bitsControl datagram fragmentation (e.g., the "More Fragments" bit).
Fragment Offset13 bitsUsed for reassembling fragmented datagrams.
TTL1 byteTime to Live: the number of hops the datagram may survive in the network (each router decrements TTL by 1; the datagram is discarded when TTL reaches 0).
Protocol1 byteIndicates the upper-layer protocol, e.g., TCP (6) or UDP (17).
Header Checksum2 bytesUsed to check header integrity; the receiver verifies whether the header was corrupted in transit by computing the checksum.
Source IP Address4 bytesThe IP address of the source device.
Destination IP Address4 bytesThe IP address of the destination device.
OptionsOptionalAn optional field for special purposes such as debugging and routing control (rarely used).
PaddingOptionalEnsures that the header is a multiple of 32 bits.

The data part is a TCP or UDP packet. Since we are discussing TCP here, it is a TCP packet.

Unpacking the TCP frame:

① 32-bit sequence number: the starting sequence number carried by this TCP segment.

② 32-bit acknowledgment number: the sequence number from which the sender expects the other side to start sending data.

③ 4-bit header length: the maximum value is 0xF (15); it indicates the length of the TCP header.

Header length = 4-bit header length (DEC) * 4, in bytes. Note: DEC means a decimal number.

④ 6 flag bits (6 bits):

URG: urgent flag (data waiting in the send buffer is prioritized and transmitted to the network layer directly; it is usually used together with the 16-bit urgent pointer below) ACK: acknowledgment flag PSH: push flag (send data) RST: reset flag (used when a connection request from the other side cannot be recognized). In other words, when parties X and Y are disconnecting, X believes the connection is already closed while Y still thinks it is open. At this point, when Y sends a data packet to X, X replies to Y with a packet carrying the RST flag. SYN: synchronization flag (initiate a connection) FIN: finish flag (when one side of the connection wants to end the connection, this is called disconnecting) ⑤ 16-bit window size: tells the sender how much data the receiver can accept; this value changes dynamically.

⑥ 16-bit checksum: verifies whether the data was corrupted during transmission.

⑦ 16-bit urgent pointer: used with the URG flag to send out-of-band data (urgent data).

⑧ MSS: Maximum Segment Size

Click to expand full code
html
在三次握手过程中,双方协商MSS的大小,取两者的最小值。

What follows is the application layer data.

OK. To use TCP, you need to understand how TCP works. As a reliable full-duplex protocol, TCP requires a reliable connection and transmission mechanism.

TCP (Transmission Control Protocol) is a connection-oriented, reliable, byte-stream-based communication protocol.

TCP treats the connection as its most basic abstraction unit. Each TCP connection has two endpoints, and the endpoints of a TCP connection are sockets. Socket = (IP address + port number) TCP connection = {socket1, socket2} = {(IP1:port1), (IP2:port2)} TCP provides full-duplex communication.

TCP establishes a reliable connection through the three-way handshake and uses the four-way wave to ensure that data transmission is not interrupted before both sides disconnect.

Before looking at connection establishment and teardown, let's first understand the 11 TCP states and what they mean.

The 11 TCP States:

  1. CLOSED (closed)
    • No connection exists, or the connection has ended.
    • This is the initial or terminal state.
  2. LISTEN (listening)
    • The server is waiting for connection requests; this state is usually entered by calling socket() and listen().
    • The server waits in this state for connection requests from clients.
  3. SYN-SENT (synchronization sent)
    • The client has sent a SYN request and is waiting for the server's SYN-ACK response.
    • The client enters this state when calling connect(), indicating that it has initiated a connection request.
  4. SYN-RECEIVED (synchronization received)
    • The server has received the client's SYN request and sent a SYN-ACK response, and is waiting for the client's acknowledgment (ACK).
    • After receiving SYN and replying with SYN-ACK, the server enters this state, which marks the initial phase of the connection.
  5. ESTABLISHED (connection established)
    • The connection has been successfully established, and the client and server can send and receive data to each other.
    • After the client receives the server's SYN-ACK and replies with ACK, both the client and the server enter this state.
  6. FIN-WAIT-1 (finish wait 1)
    • One side has actively initiated connection closure, sent a FIN request, and is waiting for the other side's ACK.
    • A side enters this state after calling close() and sending a FIN packet.
  7. FIN-WAIT-2 (finish wait 2)
    • Waiting for the other side to send FIN; this side has already received the ACK for its own FIN and is waiting for the other side's FIN request.
    • A side enters this state after receiving the other side's ACK.
  8. CLOSE-WAIT (close wait)
    • The passively closing side has received the FIN and is waiting for the local application to finish processing the data and issue a close request.
    • This state is entered after receiving the other side's FIN, and the local application is awaited to call close().
  9. CLOSING (closing)
    • Both sides send FIN requests simultaneously, and both are waiting for the other side's ACK.
    • This state is rare and indicates that both sides closed the connection almost simultaneously.
  10. LAST-ACK (last acknowledgment)
    • The passively closing side has sent a FIN request and is waiting for the other side's ACK.
    • After sending FIN and receiving an ACK, a side enters this state and waits for the other side's final acknowledgment.
  11. TIME-WAIT (time wait)
  • The actively closing side waits for a period of time to ensure that the other side has received the ACK.
  • After entering this state, it waits for 2 maximum segment lifetimes (2MSL, usually from a few seconds to a few minutes) to ensure that all packets have been delivered and acknowledged before entering CLOSED.

Now that we roughly understand what these 11 states mean, let's look at the TCP three-way handshake and four-way wave.

Three-way handshake (TCP connection establishment):

Four-way wave (connection teardown):

What exactly do the three-way handshake and the four-way wave do? This is where the TCP states described earlier come in.

Let's look at the state transition diagram of both sides to see what connection establishment and teardown actually do:

First of all, TCP is full-duplex communication, which means there are two invisible channels for sending and receiving (2 channels).

Connection establishment:

  1. Initially, both the client and the server are in the CLOSED state.
  2. When the two sides want to establish a connection, the server starts listening on a port and enters the LISTEN state. The client initiates the first handshake (CONNECT/SYN) and sends a SYN synchronization packet (indicating that the client wants to establish a sending channel 1 to the server). At this point, the client transitions to the SYN-SENT state.
  3. The server detects the client's SYN handshake request, accepts the client's connection request, and immediately replies with an ACK packet. The server then enters the SYN-RECEIVED state. The server also needs to establish a channel for sending information to the client, so it sends its own SYN packet (indicating that the server wants to establish another sending channel 2 to the client).
  4. The client receives the server's SYN+ACK reply and, knowing that the server has acknowledged its identity and also wants to establish a connection, replies with an ACK signal to agree to the connection.
  5. The server then receives the ACK reply, and both sides transition to the ESTABLISHED state, meaning the connection is successfully established.

Established:

Send some data......

Connection teardown:

  1. When side A finishes sending data and has nothing left to do, it wants to break the connection. It first sends a FIN close-request signal (to close channel 1) and transitions to the FIN-WAIT-1 state. The other side, B, receives the FIN request and agrees, but asks A to wait until it finishes sending the data to A, then replies with an ACK signal and transitions to the CLOSE-WAIT state.
  2. After A receives the ACK, it agrees to wait until B is done talking and transitions to the FIN-WAIT-2 state.
  3. After some time, B finishes sending all its data and sends the FIN signal (to close channel 2), transitioning to the LAST-ACK state and waiting for A to reply with an ACK.
  4. A replies with an ACK and transitions to the TIME-WAIT state. After a timeout period, both sides are disconnected.

Now, I know this looks complicated. Don't worry: you only need to know that the TCP/IP protocol does a lot of work in between. As long as you understand how the TCP workflow establishes and tears down connections, that is enough.

A simple way to understand it:

Establishing a connection means A gives B a sending channel (SYN) and must get B's acknowledgment (ACK). Since TCP is full-duplex, B also gives A a sending channel (SYN), which also requires A's acknowledgment (ACK). Only then is the connection successfully established.

Disconnecting can happen in several ways, but the general idea is: A says it wants to disconnect and sends FIN to close its channel; B agrees (ACK), but may still have data to send to A and says "wait a moment"; once B finishes sending, it disconnects its other channel (FIN); A, after waiting, finally receives the agreement (ACK), and after two wait periods both sides are automatically disconnected.

That is all you need to understand, because when we use TCP we program with SOCKET sockets, which makes the above less important.

What is a socket? A socket is an abstraction layer between the application layer and the transport layer. It abstracts the complex operations of the TCP/IP layers into a few simple interfaces for the application layer to call, enabling processes to communicate over the network.

Socket originated from UNIX. Under the Unix philosophy that "everything is a file", a socket is an implementation of the "open — read/write — close" pattern: the server and client each maintain a "file". After the connection is established and opened, either side can write content to its own file for the other side to read, or read the other side's content. When communication ends, the file is closed.

A socket is an implementation of the "open — read/write — close" pattern. Taking a socket communicating over TCP as an example, its interaction flow is roughly as follows:

)

The server creates a socket based on the address type (IPv4, IPv6), socket type, and protocol

The server binds an IP address and port number to the socket

The server socket listens for requests on the port, ready to accept incoming client connections; at this point the server's socket is not open yet

The client creates a socket

The client opens its socket and tries to connect to the server socket using the server's IP address and port number

The server socket receives the client's request, is passively opened, and starts accepting the client's request until the client returns connection information. At this point the socket enters the blocking state: "blocking" means the accept() method does not return until the client returns connection information, and then the server starts accepting the next client connection request

The client connects successfully and sends connection status information to the server

The server's accept() method returns and the connection is successful

The client writes information to the socket

The server reads the information

The client closes

The server closes

The Ai-Thinker WB2 SDK already includes SOCKET TCP programming examples. With just a basic understanding, you can happily write your own socket-based programs. Of course, you must first connect to the network and obtain an IP address before you can use TCP socket programming.

As long as you are proficient at creating a socket, connecting, sending data through the socket, receiving data, and closing the socket, you can use it with ease.

These 5 steps: Create a socket:

Click to expand full code
html
int tcp_client_init(char* server_ip, int port)
{
    int socket_fd = 0;

    if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0))<0) { //指定ipv4版本和连接为TCP类型
        blog_error("socket creat fail\r\n");
        return -1;
    }
    memset(&dest, 0, sizeof(dest));  //将ip 端口 都保存到dest 这个结构体中
    // inet_aton

    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    dest.sin_addr.s_addr = inet_addr(server_ip);

    printf("Server ip Address : %s port:%d\r\n", inet_ntoa(dest.sin_addr.s_addr), ntohs(dest.sin_port));
    return socket_fd;  //创建成功就返回tcp套接字的文件描述符
}

Connect:

Click to expand full code
html
int tcp_client_connect(int sockect_fd)
{
    //通过刚才的文件表示符指定的tcp 控制块 写入ip 端口等信息,由TCP PCB 来发起请求并维护连接。
    if (connect(sockect_fd, (struct sockaddr*)&dest, sizeof(dest))!=0) {
        printf("tcp client connect servet:%s fail\r\n", inet_ntoa(dest.sin_addr.s_addr));
        return -1;
    }
    else return 0;
}

Send:

Click to expand full code
html
int tcp_client_send(int sockect_fd, const char* data)
{
      //发送就是写入数据到这个TCP PCB控制块  直接用WRITE()函数写就行,linux一切皆文件,socketz和各概念本来就是linux中先有的
    return write(sockect_fd, data, strlen(data));
}

Receive:

Click to expand full code
html
int tcp_client_receive(int sockect_fd, char* data)
{
//   从tcp PCB中读取数据到缓冲区
    return read(sockect_fd, data, TCP_CLIENT_BUFF);
}

Close the connection:

Click to expand full code
html
int tcp_client_deinit(int socket_fd)
{

    shutdown(socket_fd, SHUT_RDWR);  //关掉连接
    return close(socket_fd);  //CLOSE 掉这个文件描述符,避免内存溢出
}

Client connection example:

Click to expand full 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 "TP-LINK_4450"
#define ROUTER_PWD "HYGS3305"
//This is Ai-Thinker Remote TCP Server: [http://tt.ai-thinker.com:8000/ttcloud](http://tt.ai-thinker.com:8000/ttcloud)
#define TCP_SERVER_IP "192.168.0.123"
#define TCP_SERVER_PORT 43210

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;  //声明一个socket的文件描述符
    int ret = 0;
    char* tcp_buff = pvPortMalloc(512);  //申请接收区内存
    memset(tcp_buff, 0, 512);
    socketfd = tcp_client_init(TCP_SERVER_IP, TCP_SERVER_PORT);  //将ip 和 端口填写进去获取一个socket 套接字
    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) {  //发送hello tcp server
        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);
      // 连接成功获取IP后,创建TCP发送任务
      }
        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);
}

The server needs to listen for connections from clients; creating a socket is a bit different from connecting:

Create: creation requires a fixed service port and a bound IP

Click to expand full code
html
int tcp_server_init(char* s_ip, int s_port)
{
struct netif* s_netif;
if (s_ip==NULL) {
s_netif = netif_find("st1");
if (s_netif) {
s_dest.sin_addr.s_addr = s_netif->ip_addr.addr;
}
else {
s_dest.sin_addr.s_addr = inet_addr(s_ip);
}
s_dest.sin_family = AF_INET;
s_dest.sin_port = htons(s_port);
//Creat socket
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd<0) return -1;
//bind IP addr
int ret = bind(socketfd, (struct sockaddr*)&s_dest, sizeof(s_dest));
if (ret<0) {
printf("Socket unable to bind: errno %d\r\n", ret);
return -1;
}
printf("tcp server start ip:%s:%d\r\n", inet_ntoa(s_dest.sin_addr.s_addr), ntohs(s_dest.sin_port));
//listening connections.The maximum number of connections is 4
ret = listen(socketfd, MAX_CLIENT_NUM);
if (ret!=0) {
printf("Error occured during listen: errno %d\r\n", ret);
return -1;
}
printf("tcp server listening.....\r\n");
return socketfd;
}

Wait for connections: listen for connections from different clients and process them.

Click to expand full code
html
static int sock_fd[MAX_CLIENT_NUM];  //由于连接可能是多个这里要保存每个连接的文件描述符
int tcp_server_accept(int socketfd, tcp_accpet_t tcp_accpet_cb)
{
struct sockaddr_in s_addr;
int sock_cnt = 0;
u32_t socket_len = sizeof(s_addr);
while (1) {
sock_fd[sock_cnt] = accept(socketfd, (struct sockaddr*)&s_addr, &socket_len);
if (sock_fd[sock_cnt]<0) {
printf("Unable to accept connection: errno %d\r\n", sock_fd[sock_cnt]);
return -1;
}
else if (sock_fd[sock_cnt]>0) {
tcp_client.ip_addr = (void*)&s_addr;
tcp_client.socket_fd = sock_fd[sock_cnt];
tcp_client.socket_id = sock_cnt;
xTaskCreate(tcp_accpet_cb, "tcp_accpet_cb", 512, &tcp_client, 17, NULL);
printf("client:%s:%d,id:%d\r\n", inet_ntoa(s_addr.sin_addr.s_addr), sock_fd[sock_cnt], sock_cnt);
sock_cnt++;
}
else {
goto _exit;
}
_exit:
tcp_server_deinit();
return 0;
}

Server example:

Click to expand full 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_server.h"

#define ROUTER_SSID "CU_e6f6"
#define ROUTER_PWD "c9g3geyu"

#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);
}

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