本帖最后由 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库的功能函数有:
- socket(); //创建secket,向内核申请一个套接字
- bind(); //绑定一个套接字与本地地址信息,用于服务器程序
- accept(); //从套接字的连接请求队列中获取一个新建立的连接
- shutdown(); //以自定义方式关闭套接字
- getpeername(); //获取套接字的本地地址信息/远程地址信息
- getsockopt(); & setsockopt(); //设置或获取套接字的一些基本操作选项
- close(); //以默认方式关闭套接字
- read(), readv(), write(), writev() //在稳定的连接上接收数据
- recv(), recvmsg(), recvfrom() //在一个套接字中接收数据,recvfrom的参数与sendto一致,一般用于UDP协议
- send(), sendmsg(), sendto() //在稳定的连接上发送数据
- poll(); & select(); //监听一个或多个套接字的状态变化,poll()已在select()函数中调用
- fcntl(); //获取或设置套接字的数据流属性,目前唯一支持的标志是O_NONBLOCK
- /* Non-standard functions: */
- 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地址以及端口号
- /* TCP or UDP config parameter */
- #define HOST_IP_ADDR "your server ip"
- #define PORT 8080 //设置服务器的访问端口号
复制代码
参数配置完成以后创建socket- struct sockaddr_in dest_addr;
- dest_addr.sin_addr.s_addr = inet_addr(host_ip); //配置服务器IP
- dest_addr.sin_family = AF_INET; //AF_INET FOR IPv4;OR AF_INET6 FOR IPv6
- dest_addr.sin_port = htons(PORT); //配置服务器访问端口
- addr_family = AF_INET;
- /* IPPROTO_IP:默认;IPPROTO_TCP:TCP;IPPROTO_UDP:UDP */
- ip_protocol = IPPROTO_IP;
- /* SOCK_STREAM:TCP数据格式; SOCK_DGRAM:UDP数据格式*/
- int sock = socket(addr_family, SOCK_STREAM, ip_protocol);
- if (sock < 0)
- {
- ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
- break;
- }
- ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);
复制代码
socket创建成功以后开始连接服务器
- int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in)); //连接服务器
- if (err != 0) {
- ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
- break;
- }
- ESP_LOGI(TAG, "Successfully connected");
复制代码
最后在while循环中发送数据给服务器,并监听服务器的回复信息
TCP协议下收发数据程序如下:
- int err = send(sock, payload, strlen(payload), 0); //发送数据到服务器
- if (err < 0) {
- ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
- break;
- }
- int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); //监听服务器消息的到来
- // Error occurred during receiving
- if (len < 0) {
- ESP_LOGE(TAG, "recv failed: errno %d", errno);
- break;
- }
- // Data received
- else {
- rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
- ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
- ESP_LOGI(TAG, "%s", rx_buffer);
- }
复制代码
UDP协议下收发数据操作如下:
- int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
- if (err < 0) {
- ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
- break;
- }
- ESP_LOGI(TAG, "Message sent");
- struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
- socklen_t socklen = sizeof(source_addr);
- int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
- // Error occurred during receiving
- if (len < 0) {
- ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
- break;
- }
- // Data received
- else {
- rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
- ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
- ESP_LOGI(TAG, "%s", rx_buffer);
- if (strncmp(rx_buffer, "OK: ", 4) == 0) {
- ESP_LOGI(TAG, "Received expected message, reconnecting");
- break;
- }
- }
复制代码
(2)主程序- ESP_ERROR_CHECK(nvs_flash_init());
- ESP_ERROR_CHECK(esp_netif_init());
- ESP_ERROR_CHECK(esp_event_loop_create_default());
- ESP_ERROR_CHECK(example_connect());
- xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
- /* UDP Task */
- 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连接
结语:学无止境,让我们一起加油
|