第十六章 ESP32的TCP连接

  • 源码地址:https://github.com/HX-IoT/ESP32-Developer-Guide
  • ESP32开发指南QQ群:824870185,内有pdf版,排版整洁。

 

学习目的及目标

  • 掌握TCP原理和工作过程
  • 掌握乐鑫ESP32的TCP的程序设计
  • 主要掌握TCP作为Client的详细过程

TCP科普(来自百度百科)

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内, 另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

连接建立

TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。

TCP三次握手的过程如下:

  • 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
  • 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
  • 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
      1. 连接终止

终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。具体过程如下图所示。 [1] 

  • 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
  • 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。
  • 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
  • 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN

TCP特点和流程

上面的原理很重要,但毕竟我们只是在API之上做应用。只需要了解特点流程。知道特点可以做方案时候考量可行性,流程就是可行后的实施。

TCP特点:

  • 面向连接的:发数据前要进行连接。
  • 可靠的连接:TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。
  • 点到点:TCP连接传送的数据,无差错,不丢失,不重复,且按序到达
  • 最大长度有限:仅1500字节。(http和websocket有了用武之地

TCP流程: (本段来源

TCP编程的客户端一般步骤是:

  • 创建一个socket,用函数socket(); 
  • 设置socket属性,用函数setsockopt();(可选)
  • 绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
  • 设置要连接的对方的IP地址和端口等属性; 
  • 连接服务器,用函数connect(); 
  • 收发数据,用函数send()和recv(),或者read()和write(); 
  • 关闭网络连接;

TCP编程的服务器端一般步骤是: 

  1. 创建一个socket,用函数socket();
  2. 设置socket属性,用函数setsockopt();(可选) 
  3. 绑定IP地址、端口等信息到socket上,用函数bind();
  4. 开启监听,用函数listen();
  5. 接收客户端上来的连接,用函数accept();
  6. 收发数据,用函数send()和recv(),或者read()和write(); 
  7. 关闭网络连接; closesocket();
  8. 关闭监听; 

TCP和UDP(下一章讲)互怼

第十六章 ESP32的TCP连接_第1张图片

 

软件设计

ESP32的TCP Client(Server类似)主逻辑

第十六章 ESP32的TCP连接_第2张图片

 

TCP Client的新建任务和接收任务详细过程逻辑

第十六章 ESP32的TCP连接_第3张图片

 

 

ESP32的TCP接口介绍

ESP32使用的是LwIP,LwIP是特别适用于嵌入式设备的小型开源TCP/IP协议栈,对内存资源占用很小。ESP-IDF即是移植了LwIP协议栈。学习了解LwIP,给大家推荐本书,《嵌入式网络那些事:LwIP协议深度剖析与实战演练》。

我们的这个例程是直接怼的是标准socket接口(内部是LwIP封装的),没有用LwIP的,关于LwIP的接口讲解在Websocket中讲解,用法都是一样,知道流程后,API调用即可,处理好异常。流程+接口,打遍无敌手。LwIP的教程可以参考安富莱、野火的文档。

在src/include/lwip/socket.h文件中可以看到下面的宏定义,lwip的socket也提供标准的socket接口函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#if LWIP_COMPAT_SOCKETS

#define accept(a,b,c)         lwip_accept(a,b,c)

#define bind(a,b,c)           lwip_bind(a,b,c)

#define shutdown(a,b)         lwip_shutdown(a,b)

#define closesocket(s)        lwip_close(s)

#define connect(a,b,c)        lwip_connect(a,b,c)

#define getsockname(a,b,c)    lwip_getsockname(a,b,c)

#define getpeername(a,b,c)    lwip_getpeername(a,b,c)

#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)

#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)

#define listen(a,b)           lwip_listen(a,b)

#define recv(a,b,c,d)         lwip_recv(a,b,c,d)

#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)

#define send(a,b,c,d)         lwip_send(a,b,c,d)

#define sendto(a,b,c,d,e,f)   lwip_sendto(a,b,c,d,e,f)

#define socket(a,b,c)         lwip_socket(a,b,c)

#define select(a,b,c,d,e)     lwip_select(a,b,c,d,e)

#define ioctlsocket(a,b,c)    lwip_ioctl(a,b,c)

 

#if LWIP_POSIX_SOCKETS_IO_NAMES

#define read(a,b,c)           lwip_read(a,b,c)

#define write(a,b,c)          lwip_write(a,b,c)

#define close(s)              lwip_close(s)

  • 新建socket函数:socket();
  • 连接函数:connect();
  • 关闭socket函数:close();
  • 获取socket错误代码:getsocketopt();
  • 接收数据函数:recv();
  • 发送数据函数:send();
  • 绑定函数:bing();
  • 监听函数:listen();
  • 获取连接函数:accept();

更多更详细接口请参考官方指南

 

ESP32的TCP总结

初始化wifi配置后,程序会根据WIFI的实时状态,在回调函数中给出状态返回,所以只需要在回调中进行相关操作,STA开始事件触发TCP进行连接,连接上后就可以进行数据的交互。其中对连接的异常情况做出来显得异常重要,TCP是可靠的,不能玩成地摊货。

 

 

TCP新建任务编写

只讲Client,server看源码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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)

    {

        //打印报错信息

        show_socket_error_reason("create client", connect_socket);

        //新建失败后,关闭新建的socket,等待下次新建

        close(connect_socket);

        return ESP_FAIL;

    }

    //配置连接服务器信息:端口+ip

    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)

    {

        //打印报错信息

        show_socket_error_reason("client connect", connect_socket);

        ESP_LOGE(TAG, "connect failed!");

        //连接失败后,关闭之前新建的socket,等待下次新建

        close(connect_socket);

        return ESP_FAIL;

    }

    ESP_LOGI(TAG, "connect success!");

    return ESP_OK;

}

 

 

TCP接收任务代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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

//异常标记

        g_rxtx_need_restart = false;

        if (len > 0)

        {

            //打印接收到的数组

            ESP_LOGI(TAG, "recvData: %s", databuff);

            //接收数据回发

            send(connect_socket, databuff, strlen(databuff), 0);

        }

        else

        {

            //打印错误信息

            show_socket_error_reason("recv_data", connect_socket);

            //服务器故障,标记重连

            g_rxtx_need_restart = true;

     

            break;

        }

    }

    close_socket();

    //标记重连

    g_rxtx_need_restart = true;

    vTaskDelete(NULL);

}

 

 

TCP异常处理

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

static void tcp_connect(void *pvParameters)

{

    。。。。。。。

        while (1)

        {

            vTaskDelay(3000 / portTICK_RATE_MS);

            //重新建立client,和新建一样一样

            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接收数据任务

                    xTaskCreate(&recv_data, "recv_data", 4096, NULL, 4, &tx_rx_task);

                }

            }

    }

    vTaskDelete(NULL);

}

    return ESP_OK;

}

 

测试流程和效果展示

测试流程

  • Client测试
  1. 修改AP和STA的账号密码
  2. #define TCP_SERVER_CLIENT_OPTION FALSE //esp32作为client
  3. 修改作为client连接server的IP(电脑/手机)和Port
  4. 使用手机或者电脑使用助手工具建立server,让esp32自动连接
  • Server测试
  1. #define TCP_SERVER_CLIENT_OPTION TRUE //esp32作为server
  2. 修改作为Server时监听的Port
  3. 手机或者电脑直连ESP32的AP
  4. 使用TCP助手工具作为Client,连接esp32的server
      1. 效果展示

Client效果展示

先建服务器,等ESP32过来连接。

第十六章 ESP32的TCP连接_第4张图片

测试发送数据

第十六章 ESP32的TCP连接_第5张图片

压力小测

第十六章 ESP32的TCP连接_第6张图片

Server效果展示

连接ESP32的AP

第十六章 ESP32的TCP连接_第7张图片

测试发送数据

第十六章 ESP32的TCP连接_第8张图片

压力小测

第十六章 ESP32的TCP连接_第9张图片

测试异常

第十六章 ESP32的TCP连接_第10张图片

 

TCP总结

  • 底层重原理,应用重流程+接口。
  • 压力小测不丢包,自己移植要大测产品稳定性。
  • 源码地址:https://github.com/xiaolongba/wireless-tech

点我->更多ESP32开发指南系列目录

你可能感兴趣的:(ESP32开发,ESP32开发指南)