本帖最后由 WildboarG 于 2024-9-12 16:26 编辑
首先,我们来了解一下网络通讯的概念:
网络通讯是指在计算机网络中,不同设备(如计算机、手机、服务器等)通过一定的通信协议和传输介质(如有线、电缆、光纤或无线网络)进行数据交换的过程。
网络通讯的基本原理
网络通讯主要是通过发送和接收数据包来实现的,这个过程可以分为以下几个阶段:
1. 数据封装
当一个应用程序需要发送数据时,数据首先会经过应用层协议(如 HTTP、FTP),然后逐层封装。每一层协议会向数据包添加相应的头部信息(如目标地址、序列号等),使得接收方能够正确解封数据。
2. 数据传输
数据在网络中通过电信号或光信号传输。根据传输方式的不同,可以分为:
- 有线传输:如通过以太网、光纤传输。
- 无线传输:如通过 Wi-Fi、移动网络(4G、5G)等无线方式传输。
3. 路由与转发
在复杂的网络中,数据包通常不会直接到达目的地,而是通过多个网络节点(如路由器)进行转发。路由器根据数据包的目标地址,决定数据包应该转发到哪条路径。
4. 数据解封装
当数据包到达目的地后,接收方会逐层解封数据,去除各个协议层的头部信息,直到得到原始数据并传递给应用程序。
OSI 七层模型
OSI(Open System Interconnect)七层模型是一种将计算机网络通信协议划分为七个不同层次的标准化框架。每一层都负责不同的功能,从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时,更好地理解和管理网络通信的过程。
OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层),即ISO开放互连系统参考模型。
本节我们来探讨的虽然是TCP协议,它属于传输层,但是为了更好的理解数据的封装与解包还是要了解一下,数据在网络传输种到底经历了什么。
用户数据也就是你的数据(假设经过HTTP协议传输),在经过应用层被加上了HTTP的协议头,然后传递给下层传输层,传输层在这包数据上有加了TCP的头部,这就变成一个tcp包,然后传递给网络层,网络层在头部加上了IP首部,然后这就算是IP的数据包,然后传递给链路层,以太网mac 或者 WIFI(802.11)mac将自己的头包又加到头部,这包数据就是以太网帧或者802.11帧,然后经过phy网卡物理层将数据转为光电信号发给路由器,路由器将数据转发给服务器,服务器依次从物理层解耦光电信号为以太网帧一直到应用层,一层一层将各自的头包拆掉,消息就传递给服务器。
WB2使用的是WIFI,与以太网帧对应的就是802.11帧格式。
其帧格式如下:
1. 帧控制(Frame Control) – 2字节
帧控制字段包含多个子字段,用于定义帧的类型、加密状态、优先级等信息:
位 |
名称 |
描述 |
0-1 |
协议版本(Protocol Version) |
一般为0,表示当前协议版本(802.11)。 |
2-3 |
帧类型(Type) |
指示帧的类型:管理帧、控制帧或数据帧。 |
4-7 |
帧子类型(Subtype) |
指定帧的子类型,例如数据帧中的普通数据、QoS数据等。 |
8 |
发送方为中继器(To DS) |
表示该帧是否发往分布式系统(通常是接入点)。 |
9 |
从中继器发送(From DS) |
表示该帧是否从分布式系统(接入点)发送。 |
10 |
更多分段(More Fragments) |
表示该帧是否是分段的更多部分。 |
11 |
重传(Retry) |
指示该帧是否是重传的帧。 |
12 |
电源管理(Power Management) |
表示设备是否处于省电模式。 |
13 |
更多数据(More Data) |
指示接入点是否还有其他帧待发给此设备。 |
14 |
受保护的帧(Protected Frame) |
表示帧是否加密。 |
15 |
顺序(Order) |
指示该帧是否为严格排序的帧。 |
2. 持续时间/ID(Duration/ID) – 2字节
持续时间字段表示当前帧在无线介质上保留的时间,通常用于设定RTS/CTS机制,或者表示网络分组交换系统中的时隙持续时间。对于某些控制帧(如PS-Poll),此字段表示ID。
3. 地址字段(Address Fields) – 每个6字节
在数据帧中,通常有三个或四个地址字段,具体地址的使用方式取决于帧的传输模式(如是否有中继)。地址字段通常包括以下几种:
- 地址1(Destination Address):目标设备的MAC地址。
- 地址2(Source Address):发送设备的MAC地址。
- 地址3(BSSID或中继地址):基本服务集标识符(BSSID)或接入点(AP)的MAC地址,用于基础架构模式下。
- 地址4(可选):当帧在无线分布式系统(WDS)中传输时,可能会使用此字段。
4. 序列控制(Sequence Control) – 2字节
序列控制字段包含两个部分:
- 序列号:用于帧的顺序控制,确保接收端能够按正确的顺序重组分段帧,并检测重复帧。
- 分段号:指示帧是否被分片传输,并指明该帧是哪一个片段。
5. 帧体(Frame Body) – 可变长度
帧体包含实际的用户数据或控制信息。例如,对于数据帧,帧体承载IP数据包(包括IP头部和TCP/UDP数据)。对于管理帧或控制帧,帧体包含与帧的管理操作(如认证、关联)相关的特定字段。
6. 帧校验序列(Frame Check Sequence, FCS) – 4字节
FCS字段用于检测帧在传输过程中的错误。它使用循环冗余校验(CRC)算法计算并附加到帧的末尾,接收端可以通过计算帧的FCS值来验证数据是否正确传输。
帧主体种包含的就是IP包。然后对IP帧解包。
IPv4数据报的头部长度通常为20字节,但在使用选项字段时可以更长。下面是IPv4数据报的结构:
IPv4数据报头部结构
字段名称 |
大小 (字节) |
描述 |
<br />版本(Version) |
4位 |
IP协议版本号,IPv4的值为4。 |
头部长度(IHL) |
4位 |
头部的长度,以32位字为单位(最小值为5,即20字节)。 |
服务类型(TOS) |
1字节 |
服务类型,用于区分数据优先级。 |
总长度(Total Length) |
2字节 |
数据报的总长度,包括头部和数据部分,最大值为65535字节。 |
标识(Identification) |
2字节 |
标识符,用于标识数据报的分片。 |
标志(Flags) |
3位 |
控制数据报的分片(例如“更多片段”位)。 |
片偏移(Fragment Offset) |
13位 |
用于数据报分片后的重新组装。 |
生存时间(TTL) |
1字节 |
生存时间,数据报在网络中的存活跳数(每经过一个路由器,TTL减1,TTL为0时数据报丢弃)。 |
协议(Protocol) |
1字节 |
表示上层协议,例如TCP(6)或UDP(17)。 |
头部校验和(Header Checksum) |
2字节 |
用于检查头部的完整性,接收方通过计算校验和验证头部是否在传输中损坏。 |
源IP地址(Source IP Address) |
4字节 |
源设备的IP地址。 |
目的IP地址(Destination IP Address) |
4字节 |
目标设备的IP地址。 |
选项(Options) |
可选 |
可选字段,用于调试、路由控制等特殊用途(不常使用)。 |
填充(Padding) |
可选 |
用于确保头部是32位的倍数。 |
其中数据就是TCP包或者UDP包,这里我们讲TCP,那就是TCP包。
对TCP帧解包
① 32位序号:该条TCP数据所携带的起始序号。
② 32位确认序号:期望对方发送数据从哪一个序号开始发送。
③ 4位首部长度:最大是0xF(15),指的是TCP头部的长度。
首部长度 = 4位首部长度(DEC) * 4 ,单位为Byte。
注:DEC:表示十进制的数字。
④ 6个标志位:(6个比特位)
URG:紧急标志位(直接越过发送缓冲区等待的数据优先传输到网络层,一般配合底下的16位紧急指针使用)
ACK:确认标志位
PSH:发送数据标志位
RST:重置连接标志位(当无法识别对方发来的连接请求时,就会使用RST标志位),换句话说,当连接双方X、Y要断开连接时,X方认为连接已经断开了,Y方却认为连接还没有断开,这个时候,当Y给X发送数据包时,X就会回复带有RST标志位的数据包给Y
SYN:发起连接标志位
FIN:断开连接标志位(连接双方有一方想要断开这种连接关系,称为断开)
⑤ 16位窗口大小:告知消息发送方,自己对消息的接收能力时多少,这个值是动态变化的。
⑥ 16位检验和:校验数据在传输过程中是否失真。
⑦ 16位紧急指针:配合URG标志位发送带外数据。(紧急的数据)
⑧ MSS:最大报文段长度(MAX Segmet Size)
在三次握手过程中,双方协商MSS的大小,取两者的最小值。
后边跟着就是应用层的数据。
OK , 要想使用tcp,就要了解tcp的工作流程,TCP作为一个可靠的双工通讯,就要有一个可靠的连接传输机制。
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为:TCP)是一种面向连接的、可靠的、基于字节流的通信协议
TCP把连接作为最基本的抽象单元,每条TCP连接有两个端点,TCP连接的端点即套接字。
套接字socket = (IP地址+端口号)
TCP连接={socket1,socket2}={(IP1:port1),(IP2,port2)}
TCP提供全双工通信。
TCP 通过三次握手建立可靠的连接,以及四次挥手来保证双方断开前的数据传输不被中断。
再了解连接建立和断开之前,先来了解一下TCP的11种状态和所表示的意思。
TCP 的 11 种状态:
- CLOSED(关闭状态)
- 没有任何连接存在,或者连接已经结束。
- 这是初始状态或终止状态。
- LISTEN(监听状态)
- 服务器处于等待连接请求的状态,通常由
socket()
和 listen()
调用进入此状态。
- 服务器在此状态等待客户端的连接请求。
- SYN-SENT(同步已发送状态)
- 客户端发送了 SYN 请求,等待服务器的 SYN-ACK 响应。
- 当客户端调用
connect()
时进入此状态,表示客户端发起了连接请求。
- SYN-RECEIVED(同步已接收状态)
- 服务器接收到客户端的 SYN 请求,并发送了 SYN-ACK 响应,等待客户端确认(ACK)。
- 当服务器收到 SYN 后,回复 SYN-ACK 后进入此状态,表示连接的初始阶段。
- ESTABLISHED(建立连接状态)
- 表示连接已经成功建立,客户端和服务器可以互相发送和接收数据。
- 当客户端收到服务器的 SYN-ACK 并回复 ACK 后,客户端和服务器都进入此状态。
- FIN-WAIT-1(终止等待 1 状态)
- 表示一方主动发起连接关闭,并发送 FIN 请求,等待对方的 ACK。
- 当一方调用
close()
函数,发送 FIN 数据包后进入此状态。
- FIN-WAIT-2(终止等待 2 状态)
- 处于等待对方发出 FIN 的状态,表示本方已收到对方对 FIN 的 ACK,等待对方的 FIN 请求。
- 当一方收到对方的 ACK 后进入此状态。
- CLOSE-WAIT(关闭等待状态)
- 表示被动关闭一方收到 FIN 后,等待本地应用程序处理完数据并发出关闭请求。
- 当收到对方的 FIN 后进入此状态,并等待本地应用程序调用
close()
。
- CLOSING(关闭中状态)
- 双方同时发送 FIN 请求,双方都处于等待对方的 ACK 状态。
- 这种状态比较少见,表示双方几乎同时关闭连接。
- LAST-ACK(最后确认状态)
- 表示被动关闭一方在发送 FIN 请求后,等待对方的 ACK。
- 当一方发送 FIN 并接收到 ACK 后,进入此状态,最后等待对方的确认。
- TIME-WAIT(时间等待状态)
- 表示主动关闭的一方等待一段时间以确保对方已经接收到 ACK。
- 进入此状态后会等待 2 个最大报文段生存时间(2MSL,通常为几秒到几分钟),确保所有数据包都已传递和确认后才进入 CLOSED 状态。
大概知道这11种状态是什么意思,再来看TCP三次握手和四次挥手。
三次握手(tcp连接的建立):
四次挥手(连接的断开):
三次握手和四次挥手到底在干嘛,这就用到之前TCP的状态。
对着双方的状态迁移图来看连接和断开都做了什么:
首先要明确tcp是双工通讯,即要有两根无形的线来收发(2个通道)。
连接建立:
- 开始时候,客户端和服务端都是CLOSED状态。
- 当双方要建立建立时候,服务端开启端口监听进入LISTEN状态,客户端发起第一次握手CONNECT/SYN,发送SYN同步包(表示客户端想要给服务端建立一条线发送通道1),此时客户端状态迁移到SYN/SENT状态。
- 服务端监听到客户端发送了的握手请求SYN同步包,同意了客户端的连接请求立马回复客户端一个应答包 ACK,然后服务端状态变为同步已接收状态SYN RECEIVED,然后服务端也要建立给客户端发送信息的通道,服务端也发送SYN同步包(表示服务端要给客户端建立另一条线发送通道2)。
- 客户端接收到来自服务端应答包SYN+ACK,知道服务器已经确认我的身份也要和我建立连接的SYN 我需要回复一个应答信号ACK表示同意服务端和我建立连接。
- 然后服务端接收到ACK应答,双方都此时状态都变成ESTATBLISHED,表示双方连接建立成功。
建立成功:
发送一些数据。。。。。。
连接断开:
- 当其中一端A发完数据没有什么任务了想要分手,率先发送一个FIN关闭请求信号(要关闭通道1),然后状态迁移到FIN WAIT1 。另一端B接收到了这个FIN请求,表示要分手可以,等我先把发送给你的数据发完,回复一个ACK应答信号,然后状态迁移到CLOSE WAIT。
- A接收到ACK后,表示好滴,等你把话说完,状态迁移到FIN WAIT 2。
- 过了一段时间,B把数据发完了,发送分手FIN信号(表示要关闭通道2),状态迁移到LAST ACK 然后等待A回复ACK。
- A回复了ACK, 然后迁移到TIME WAIT状态,一段时间超时后就双方就断开连接。
很好看到这了是不是很复杂,没关系,你只要知道TCP/IP协议在中间做了很多步骤,只要理解了TCP工作流程怎么建立连接,怎么断开连接就够了。
简单理解:
建立连接就是,A要给B一个发送通道(SYN),要得到B的确认(ACK),由于tcp是双工B也要给A一个发送通道(SYN),也需要A来确认(ACK)。 然后双方连接才建立成功。
断开连接其实分几种情况,总的意思就是,A说要断开了发送通道FIN,B说同意(ACK),可能B还在向A数据没发完,然后接着说你等等,等到发完了B要断开另一个通道(FIN),A等了半天终于等到了回复同意(ACK),然后双方再2个等待周期后就自动断开连接。
理解了这些就够了,因为我们再使用tcp时候用的是SOCKET套接字编程,那么上边变得不那么重要。
什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭
安信可WB2的SDK中已经有了SOCKET TCP编程的实例,我们这要简单的了解以下就可以愉快的使用套接字编写字节的程序了。当然首先我们要先联网分配到IP地址之后才能使用TCPSOCKET编程。
只要熟练创建socket套接字,连接,利用套接字发送数据,接受数据,会关闭就可以愉快的使用了。
这5个步骤:
创建套接字:
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套接字的文件描述符
}
连接:
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;
}
发送:
int tcp_client_send(int sockect_fd, const char* data)
{
//发送就是写入数据到这个TCP PCB控制块 直接用WRITE()函数写就行,linux一切皆文件,socketz和各概念本来就是linux中先有的
return write(sockect_fd, data, strlen(data));
}
接收:
int tcp_client_receive(int sockect_fd, char* data)
{
// 从tcp PCB中读取数据到缓冲区
return read(sockect_fd, data, TCP_CLIENT_BUFF);
}
关闭连接:
int tcp_client_deinit(int socket_fd)
{
shutdown(socket_fd, SHUT_RDWR); //关掉连接
return close(socket_fd); //CLOSE 掉这个文件描述符,避免内存溢出
}
客户端连接实例:
#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
#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);
}
服务端需要监听来自客户端的连接,创建和连接有所不同:
创建: 创建需要固定服务端口绑定IP
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;
}
等待连接: 监听来自不同客户端的连接并处理。
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;
}
服务器端实例:
#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);
}