一、概念
1.TCP/IP协议简介
? ? ? ? TCP/IP (Transmission Control Protocol / Internet Protocol)传输控制协议/网间协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的 RFC 793 定义从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。TCP/IP 一词泛指这些协议,因此,有时也称 TCP/IP 为网际协议群。
? ? ? ??互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。
? ? ? ? 应用层向 TCP 层发送用于网间传输的、用 8 位字节表示的数据流,然后 TCP 把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。之后 TCP 把结果包传给 IP 层,由它来通过网络将包传送给接收端实体的 TCP 层。TCP 为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP 用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
2.建立连接
? ? ? ? TCP 是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出 SYN 连接请求后,等待对方回答 SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP 使用的流量控制协议是可变大小的滑动窗口协议。B8:F0 ? ? ? ? TCP 三次握手的过程如下:
- 客户端发送 SYN(SEQ=x)报文给服务器端,进入 SYN_SEND 状态。
- 服务器端收到 SYN 报文,回应一个 SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
- 客户端收到服务器端的 SYN 报文,回应一个 ACK(ACK=y+1)报文,进入 Established状态。
3.TCP特点及流程
? ? ? ? TCP特点:
- 面向连接的:发数据前要进行连接。
- 可靠的连接:TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达。
- 点到点:TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达
- 最大长度有限:仅 1500 字节。(http 和 websocket 有了用武之地)
? ? ? ? TCP流程:
TCP编程的客户端流程一般为:
- ?创建一个 socket,用函数 socket();? ?
- ?设置 socket 属性,用函数 setsockopt();(可选)
- ?绑定 IP 地址、端口等信息到 socket 上,用函数 bind();* 可选
- ?设置要连接的对方的 IP 地址和端口等属性;
- ?连接服务器,用函数 connect();
- ?收发数据,用函数 send()发送数据和 recv()接收数据;
- ?关闭网络连接,close();
二、软件逻辑?
?三、程序编写:
?TcpClient.h文件内容:
#ifndef MAIN_TCPCLIENT_H_
#define MAIN_TCPCLIENT_H_
#include <string.h>
#include <sys/socket.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#define TAG "Wanfy-TCP" //打印的tag
/**
* STA模式配置信息
*/
#define GATEWAY_SSID "12-2-2" //路由器账号
#define GATEWAY_PAS "19790326" //路由器密码
#define TCP_SERVER_ADRESS "192.168.6.114" //作为client,要连接TCP服务器地址
#define TCP_PORT 8888 //统一的端口号,包括TCP客户端或者服务端
// FreeRTOS event group to signal when we are connected to wifi
#define WIFI_CONNECTED_BIT BIT0 //事件组标志。
extern EventGroupHandle_t tcp_event_group; //Tcp事件组
extern bool g_rxtx_need_restart; //断线重连
//配置ESP32为STA
void wifi_init_sta();
//创建一个TCP Client连接
esp_err_t create_tcp_client();
//数据接收任务,由FreeRTOS创建任务
void recv_data(void *pvParameters);
//关闭所有的socket
void close_socket();
#endif /* MAIN_TCPCLIENT_H_ */
TcpClient.c文件内容:
/*
* TcpClient.c
*
* Created on: 2021年12月23日
* Author: wangy
*/
#include "TcpClient.h"
EventGroupHandle_t tcp_event_group; //wifi建立成功信号量
bool g_rxtx_need_restart = false; //异常后,重新连接标记
/*
* wifi 事件
* @param[in] void :无
* @retval void :无
*/
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id)
{
case SYSTEM_EVENT_STA_START: //STA模式-开始连接
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_DISCONNECTED: //STA模式-断线
esp_wifi_connect();
xEventGroupClearBits(tcp_event_group, WIFI_CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_CONNECTED: //STA模式-连接成功
xEventGroupSetBits(tcp_event_group, WIFI_CONNECTED_BIT);//时间组发出连接成功
break;
case SYSTEM_EVENT_STA_GOT_IP: //STA模式-获取IP
ESP_LOGI(TAG, "got ip:%s\n",
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
xEventGroupSetBits(tcp_event_group, WIFI_CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
/*
* WIFI作为STA的初始化
* @param[in] void :无
* @retval void :无
*/
void wifi_init_sta()
{
tcp_event_group = xEventGroupCreate();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = GATEWAY_SSID, //STA账号
.password = GATEWAY_PAS}, //STA密码
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
ESP_LOGI(TAG, "connect to ap SSID:%s password:%s \n",
GATEWAY_SSID, GATEWAY_PAS);
}
static struct sockaddr_in server_addr; //server地址
static int connect_socket = 0; //连接socket
/**
* 创建一个TcpClient连接
*/
esp_err_t create_tcp_client()
{
ESP_LOGI(TAG, "will connect gateway ssid : %s port:%d",
TCP_SERVER_ADRESS, TCP_PORT);
//新建socket对象
connect_socket = socket(AF_INET, SOCK_STREAM, 0);
if (connect_socket < 0)
{
ESP_LOGI(TAG, "Failed connect gateway ssid : %s port:%d",
TCP_SERVER_ADRESS, TCP_PORT);
//新建失败后,关闭新建的socket,等待下次新建
close(connect_socket);
return ESP_FAIL;
}
//配置连接服务器信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_PORT);
server_addr.sin_addr.s_addr = inet_addr(TCP_SERVER_ADRESS);
ESP_LOGI(TAG, "connectting server...");
//连接服务器
if (connect(connect_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
ESP_LOGE(TAG, "connect failed!");
//连接失败后,关闭之前新建的socket,等待下次新建
close(connect_socket);
return ESP_FAIL;
}
ESP_LOGI(TAG, "connect success!");
return ESP_OK;
}
/*
* 接收数据任务
* @param[in] void :无
* @retval void :无
*/
void recv_data(void *pvParameters)
{
int len = 0; //长度
char databuff[1024]; //缓存
while (1)
{
//清空缓存
memset(databuff, 0x00, sizeof(databuff));
//读取接收数据
len = recv(connect_socket, databuff, sizeof(databuff), 0);
bool g_rxtx_need_restart = false;
if (len > 0)
{
//打印接收到的数组
ESP_LOGI(TAG, "recvData: %s", databuff);
//接收数据回发
send(connect_socket, databuff, strlen(databuff), 0);
}
else
{
//打印错误信息
ESP_LOGI(TAG, "recvData error");
bool g_rxtx_need_restart = true; //连接异常,至断线重连标志为1,以便tcp_connect连接任务进行重连
break;
}
}
close_socket();
bool g_rxtx_need_restart = true;
vTaskDelete(NULL);
}
/*
* 关闭socket
* @param[in] socket :socket编号
* @retval void :无
*/
void close_socket()
{
close(connect_socket); //关闭客户端
}
main.c文件内容:
#include "TcpClient.h"
#include "nvs_flash.h"
/*
===========================
函数定义
===========================
*/
/*
* 任务:建立TCP连接并从TCP接收数据
* @param[in] void :无
* @retval void :无
*/
static void tcp_connect(void *pvParameters)
{
while (1)
{
g_rxtx_need_restart = false;
//等待WIFI连接信号量(即等待ESP32作为STA连接到热点的信号),死等
xEventGroupWaitBits(tcp_event_group, WIFI_CONNECTED_BIT, false, true, portMAX_DELAY);
ESP_LOGI(TAG, "start tcp connected");
TaskHandle_t tx_rx_task = NULL; //任务句柄
//延时3S准备建立clien
vTaskDelay(3000 / portTICK_RATE_MS);
ESP_LOGI(TAG, "create tcp Client");
//建立client
int socket_ret = create_tcp_client();
if (socket_ret == ESP_FAIL)
{
//建立失败
ESP_LOGI(TAG, "create tcp socket error,stop...");
continue;
}
else
{
//建立成功
ESP_LOGI(TAG, "create tcp socket succeed...");
//建立tcp接收数据任务
if (pdPASS != xTaskCreate(&recv_data, "recv_data", 4096, NULL, 4, &tx_rx_task)) //tx_rx_task 任务句柄
{
//建立失败
ESP_LOGI(TAG, "Recv task create fail!");
}
else
{
//建立成功
ESP_LOGI(TAG, "Recv task create succeed!");
}
}
while(1){ //每3秒钟检查一次是否需要重新连接
vTaskDelay(3000 / portTICK_RATE_MS);
if(g_rxtx_need_restart){
vTaskDelay(3000 / portTICK_RATE_MS);
ESP_LOGI(TAG, "reStart create tcp client...");
//建立client
int socket_ret = create_tcp_client();
if (socket_ret == ESP_FAIL)
{
ESP_LOGE(TAG, "reStart create tcp socket error,stop...");
continue;
}
else
{
ESP_LOGI(TAG, "reStart create tcp socket succeed...");
//重新建立完成,清除标记
g_rxtx_need_restart = false;
//建立tcp接收数据任务
if (pdPASS != xTaskCreate(&recv_data, "recv_data", 4096, NULL, 4, &tx_rx_task))
{
ESP_LOGE(TAG, "reStart Recv task create fail!");
}
else
{
ESP_LOGI(TAG, "reStart Recv task create succeed!");
}
}
}
}
}
vTaskDelete(NULL); //删除任务本身
}
void app_main(void)
{
//初始化flash
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
wifi_init_sta();
xTaskCreate(&tcp_connect, "tcp_connect", 4096, NULL, 5, NULL); //新建一个TCP连接任务
}
效果图:
四、结束
? ? ? ? 本节介绍了ESP32以STA模式连接热点,并作为Tcp客户端与服务器端进行交互。?
|