请选择 进入手机版 | 继续访问电脑版

ESP32C3连接TCP/UDP服务器

[复制链接]
查看1765 | 回复0 | 2021-5-17 00:43:12 | 显示全部楼层 |阅读模式
本帖最后由 GE-Rita 于 2021-5-18 15:51 编辑

开发环境:esp-idf:v4.3-beta3

开发环境搭建以及使用参考链接:
ESP32-C3上手笔记 | 01 - ESP32 C3开发环境搭建(ESP-IDF)

工程代码:
链接:TCP工程代码
提取码:axke
链接:UDP工程代码
提取码:axke

在讲解工程之前,我们先来了解一下TCP或UDP协议的实现基础——lwip协议
LWIP协议是一个小型开源TCP/IP协议栈,其特点是有无系统都可运行,适用于资源有限的小型平台如嵌入式系统中。
socket API接口是LWIP内核作者为了使开发者快速上手LWIP而设计的第三种应用程序接口,而另外两种接口分别为RAW/Callback API以及NETCONN API。
而socket API正是对NETCONN API进行封装的,在使用上要比NETCONN API简单容易上手,但由于socket API在内核程序和应用程序之间存在数据的拷贝,因此,数据递交的效率更低。

关于socket库的功能函数有:
  1. socket();                                      //创建secket,向内核申请一个套接字
  2. bind();                                         //绑定一个套接字与本地地址信息,用于服务器程序
  3. accept();                                       //从套接字的连接请求队列中获取一个新建立的连接
  4. shutdown();                                  //以自定义方式关闭套接字
  5. getpeername();                             //获取套接字的本地地址信息/远程地址信息
  6. getsockopt(); & setsockopt();       //设置或获取套接字的一些基本操作选项
  7. close();                                          //以默认方式关闭套接字
  8. read(), readv(), write(), writev()     //在稳定的连接上接收数据
  9. recv(), recvmsg(), recvfrom()        //在一个套接字中接收数据,recvfrom的参数与sendto一致,一般用于UDP协议
  10. send(), sendmsg(), sendto()         //在稳定的连接上发送数据
  11. poll(); & select();                          //监听一个或多个套接字的状态变化,poll()已在select()函数中调用
  12. fcntl();                                          //获取或设置套接字的数据流属性,目前唯一支持的标志是O_NONBLOCK
  13. /* Non-standard functions: */
  14. ioctl();                                          //用于提供访问一些TCP/IP堆栈特征的方式
复制代码

更多socket驱动函数使用参考文章:
中文文章:TCP/IP协议栈之LwIP(十)---Socket API编程
乐鑫官方文档:BSD Sockets API标准BSD Socket API文档:http://pubs.opengroup.org/onlinepubs/007908799/xnsix.html
BSD Socket API操作指南:https://csperkins.org/teaching/2007-2008/networked-systems/lecture04.pdf

操作过程:
  • 程序结构:
该工程主要由两部分组成:TCP功能配置以及主程序
(1)TCP or UDP功能配置、连接与数据处理设置配置参数:包括要访问的TCP or UDP 服务器IP地址以及端口号
  1. /* TCP or UDP config parameter */
  2. #define HOST_IP_ADDR "your server ip"
  3. #define PORT 8080     //设置服务器的访问端口号
复制代码

参数配置完成以后创建socket
  1. struct sockaddr_in dest_addr;
  2. dest_addr.sin_addr.s_addr = inet_addr(host_ip);        //配置服务器IP
  3. dest_addr.sin_family = AF_INET;                //AF_INET FOR IPv4;OR AF_INET6 FOR IPv6
  4. dest_addr.sin_port = htons(PORT);           //配置服务器访问端口
  5. addr_family = AF_INET;
  6. /* IPPROTO_IP:默认;IPPROTO_TCP:TCP;IPPROTO_UDP:UDP */
  7. ip_protocol = IPPROTO_IP;
  8. /* SOCK_STREAM:TCP数据格式; SOCK_DGRAM:UDP数据格式*/  
  9. int sock = socket(addr_family, SOCK_STREAM, ip_protocol);  
  10. if (sock < 0)
  11. {
  12.     ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
  13.     break;
  14. }
  15. ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);
复制代码


socket创建成功以后开始连接服务器
  1. int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in));          //连接服务器
  2. if (err != 0) {
  3.      ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
  4.      break;
  5. }
  6. ESP_LOGI(TAG, "Successfully connected");
复制代码

最后在while循环中发送数据给服务器,并监听服务器的回复信息
TCP协议下收发数据程序如下:
  1. int err = send(sock, payload, strlen(payload), 0);          //发送数据到服务器
  2. if (err < 0) {
  3.     ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
  4.     break;
  5. }
  6. int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);   //监听服务器消息的到来
  7. // Error occurred during receiving
  8. if (len < 0) {
  9.     ESP_LOGE(TAG, "recv failed: errno %d", errno);
  10.     break;
  11. }
  12. // Data received
  13. else {
  14.     rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
  15.     ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
  16.     ESP_LOGI(TAG, "%s", rx_buffer);
  17. }
复制代码

UDP协议下收发数据操作如下:
  1. int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
  2. if (err < 0) {
  3.         ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
  4.         break;
  5. }
  6. ESP_LOGI(TAG, "Message sent");

  7. struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
  8. socklen_t socklen = sizeof(source_addr);
  9. int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

  10. // Error occurred during receiving
  11. if (len < 0) {
  12.         ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
  13.         break;
  14. }
  15. // Data received
  16. else {
  17.         rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
  18.         ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
  19.         ESP_LOGI(TAG, "%s", rx_buffer);
  20.         if (strncmp(rx_buffer, "OK: ", 4) == 0) {
  21.                 ESP_LOGI(TAG, "Received expected message, reconnecting");
  22.                 break;
  23.         }
  24. }
复制代码

(2)主程序
  1. ESP_ERROR_CHECK(nvs_flash_init());
  2. ESP_ERROR_CHECK(esp_netif_init());
  3. ESP_ERROR_CHECK(esp_event_loop_create_default());

  4. ESP_ERROR_CHECK(example_connect());
  5. xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
  6. /* UDP Task */
  7. xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
复制代码


  • 烧录过程重点配置:
(1)输入 idf.py menuconfig 命令,在主界面中选择 Example connection configuration 输入本地WiFi账号与密码
(1)

(2)

(2)返回主界面,选择 component config 进入元件设置界面,选择 esp32c3-Specific 进入esp32c3配置界面,选择 Minimum supported Esp32-C3 Revision 进入版本选择界面,这里选择 Rev2 ,最后单击“s”保存并退出。
(1)

(2)

  • 调试过程(以本地局域网通信为例)
打开网络调试助手,配置为TCP Server模式,选择WiFi的ip地址,设置端口为8080,打开TCP服务器

烧录程序至开发板中,打开串口监视器,查看开发板工作情况,在网络助手中发送数据给开发板,效果如下:

更多与TCP/IP协议接口配置操作参考乐鑫官方文档:lwIP的介绍
也可参考中文版的:第十六章 ESP32的TCP连接

结语:学无止境,让我们一起加油























回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

7

主题

8

帖子

80

积分

注册会员

Rank: 2

积分
80