ESP32学习10:TcpClient

一、概念

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编程的客户端流程一般为:

  1.  创建一个 socket,用函数 socket();   
  2.  设置 socket 属性,用函数 setsockopt();(可选)
  3.  绑定 IP 地址、端口等信息到 socket 上,用函数 bind();* 可选
  4.  设置要连接的对方的 IP 地址和端口等属性;
  5.  连接服务器,用函数 connect();
  6.  收发数据,用函数 send()发送数据和 recv()接收数据;
  7.  关闭网络连接,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客户端与服务器端进行交互。 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值