别迷路-导航栏
快速导航找到你想要的(文章目录)
此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。
1、博主写这篇技术文章的目标:
(1)、了解 tcp udp的通讯特点;
(2)、了解客户端和服务器的特点;
(3)、掌握tcp客户端和服务器编程;
2、 概述
在前面章节已经为大家先容了ESP32 WIFI STA 及AP模式的基本应用,本章是基于wifi sta模式开发。Tcp的理论知识这里就不过多先容,大家百度一下比我解提及来好多了。
(1) Tcp 与udp的区别及各自的特点
TCP:
•面向连接的:发数据前要举行连接。
•可靠的连接:TCP 连接传送的数据,无不对,不丢失,不重复,且按序到达。
•点到点:TCP 连接传送的数据,无不对,不丢失,不重复,且按序到达
•最大长度有限:仅 1500 字节。(http 和 websocket 都是采用TCP通讯)
UDP:
•无连接的:发数据前不需要创建连接。
•不可靠:尽最大努力交付,即不保证可靠交付。
•支持一对一,一对多,多对一和多对多的交互通讯
•占用资源少,发送数据快。
(2) 客户端与服务端的区别
服务端:创建并监听多个连接,服务端可以监听多个客户端发过来的连接;
客户端:创建并发起连接,多个客户端可以连接到同一个服务端;
3、 Socket API库的先容
1、 TCP 编程的客户端一般步调是:
(1)创建一个 socket,用函数 socket();
(2)设置 socket 属性,用函数 setsockopt();(可选)
(3)绑定 IP 地点、端口等信息到 socket 上,用函数 bind();* 可选
(4)设置要连接的对方的 IP 地点和端口等属性;
(5)连接服务器,用函数 connect();
(6) 收发数据,用函数 send()和 recv(),大概 read()和 write();
(7)关闭网络连接;
2、TCP 编程的服务器端一般步调是:
(1)创建一个 socket,用函数 socket();
(2)设置 socket 属性,用函数 setsockopt();(可选)
(3)绑定 IP 地点、端口等信息到 socket 上,用函数 bind();
(4)开启监听,用函数 listen();
(5)接收客户端上来的连接,用函数 accept();
(6)收发数据,用函数 send()和 recv(),大概 read()和 write();
(7)关闭网络连接; closesocket();
(8)关闭监听;
ESP32 使用的是 LwIP,LwIP 是特殊适用于嵌入式装备的小型开源 TCP/IP 协议栈,对内
存资源占用很小。ESP-IDF 即是移植了 LwIP 协议栈。
我们这里直接使用标准 socket API接口,API函数只做简单的先容,跟详细的socket API大家可以百度
创建连接 socket();
连接服务端函数:connect();
关闭 socket 函数:close();
获取 socket 错误代码:getsocketopt();
接收数据函数:recv();
发送数据函数:send();
绑定函数:bing();
监听函数:listen();
获取连接函数:accept();
更多API 参考esp-idf\components\lwip\lwip\src\include\lwip\sockets.h
4、 软件设计
(1)、客户端软件设计
①、等候wifi sta连接好路由器
②、创建连接服务端的socket
client_connect_socket = socket(AF_INET, SOCK_STREAM, 0);
③、设置连接服务器信息,端口和IP
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip);
④、然后开始连接
connect(client_connect_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)
⑤收发数据使用recv();send();函数
(2)、服务端软件设计
①、等候wifi sta连接好路由器
②、创建服务端的socket
③、设置新建的server socket参数
④、绑定ip和端口号bind()
⑤、开始监听有没有客户端连接上来
listen(server_socket, 1) 而且设置允许最大连接客户端的数量
⑥、获取连接的socket
accept(server_socket, (struct sockaddr *)&client_addr, &socklen)
⑦、收发数据使用recv();send();函数
5、 实例
复制wifi sta工程改名字为 idf_wifi_tcp(由于本例程是基于wifi sta)即可 文件名字改为 idf_wifi_tcp.C makefile文件也改成 PROJECT_NAME := idf_wifi_tcp即可,然后复制一下代码测试。
在编译之前,为大家收支一个知识点,解说一下如安在工程下面创建一个文件夹来管理.c和.h文件。
以此工程为例解说
(1)、在idf_wifi_tcp文件夹下面新建文件夹network,而且添加自己的.c和.h文件
(2)、在network文件夹下面添加文件component.mk,文件内容为COMPONENT_ADD_INCLUDEDIRS := .
(3)、然后在工程的Makefile文件下面添加编译路径:
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/lyy/idf_wifi_tcp/network
(4)、最后make clean
make flash monitor
代码:idf_wifi_tcp.c文件
- /**********************************************************************
- * 文件名: idf_wifi_tcp.c
- * 创建人:
- * 创建日期:
- * 修改人:
- * 修改日期:
- * 版本号: V1.1
- * 备注:
- * 公司:
- ********************************************************************/
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "freertos/event_groups.h"
- #include "nvs.h"
- #include "esp_log.h"
- #include "nvs_flash.h"
- #include "esp_system.h"
- #include "esp_wifi.h"
- #include "esp_event.h"
- #include "lwip/err.h"
- #include "lwip/sys.h"
- #include "tcp_bsp.h"
- static const char *TAG = "WIFI_TCP";
- //事件标志组
- EventGroupHandle_t xCreatedEventGroup_WifiConnect = NULL;
- static int retry_num = 0;
- /***********************************************************************
- * 函数:
- * 描述: wifi 回调函数
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
- {
- ESP_LOGI(TAG, "event_base: %s ; event_id : %d",event_base,event_id);
- if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
- {
- esp_wifi_connect();//开始连接
- }
- else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) //wifi断开时
- {
- if (retry_num < CONFIG_ESP_MAXIMUM_RETRY) //重新连接次数
- {
- esp_wifi_connect();
- retry_num++;
- ESP_LOGI(TAG, "retry to connect to the AP");
- }
- else
- {
- xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
- xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
- }
- ESP_LOGI(TAG,"connect to the AP fail");
- }
- else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)//获得IP
- {
- ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
- ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
- retry_num = 0;
- xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
- xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
- }
- }
- /***********************************************************************
- * 函数:
- * 描述: wifi sta初始化代码相对比较固定,一般不需要做修改
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- void wifi_init_sta(void)
- {
- ESP_ERROR_CHECK(esp_netif_init());//初始化TCP / IP堆栈和esp-netif
- ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建默认event loop
- esp_netif_create_default_wifi_sta();//创建默认的WIFI STA。
- wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据
- ESP_ERROR_CHECK(esp_wifi_init(&cfg));//使用默认的参数初始化wifi
- //注册wifi 连接过程中回调函数
- ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
- ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
- //设置账户和密码,在menuconfig中设置
- wifi_config_t wifi_config = {
- .sta = {
- .ssid = CONFIG_ESP_WIFI_SSID,
- .password = CONFIG_ESP_WIFI_PASSWORD
- //.ssid = "ssid",
- //.password = "password"
- },
- };
- ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );//设置为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.");
- }
- /***********************************************************************
- * 函数:
- * 描述: 主函数
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- void app_main()
- {
- //初始化NVS
- esp_err_t ret = nvs_flash_init();
- if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ret = nvs_flash_init();
- }
- ESP_ERROR_CHECK(ret);
- ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
- //创建事件标志组,用于等待wifi连接
- xCreatedEventGroup_WifiConnect = xEventGroupCreate();
- //WIFI sta初始化
- wifi_init_sta();
- AppTaskCreate();
- }
复制代码 代码:tcp_bsp.c文件
- #include <stdio.h>
- #include <string.h>
- #include <errno.h>
- #include <stdlib.h>
- #include "sdkconfig.h"
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "esp_system.h"
- #include "esp_spi_flash.h"
- #include "esp_wifi.h"
- #include <sys/socket.h>
- #include "freertos/event_groups.h"
- #include "esp_event.h"
- #include "esp_err.h"
- #include "nvs_flash.h"
- #include "esp_smartconfig.h"
- #include "esp_netif.h"
- #include "esp_wpa2.h"
- #include "esp_log.h"
- #include "esp_ota_ops.h"
- #include "nvs.h"
- #include "tcp_bsp.h"
- //任务句柄
- static TaskHandle_t xHandleTaskTcpClinent = NULL;
- static TaskHandle_t xHandleTaskTcpServer = NULL;
- //任务堆栈大小,主要是函数嵌套参数入栈和局部变量的内存
- #define TcpClinent_TASK_STACK_SIZE 8192
- #define TcpServer_TASK_STACK_SIZE 8192
- //任务优先级,越大越高,跟ucos相反
- #define TcpClinent_TASK_PRIO 2
- #define TcpServer_TASK_PRIO 3
- static int client_connect_socket = 0; //客户端连接socket
- static int server_socket = 0; //服务器创建的socket
- static int server_connect_socket = 0; //有客户端连接到服务器的socket
- //函数声明
- static esp_err_t CreateTcpClient(const char *ip,uint16_t port);
- static esp_err_t CreateTcpServer(bool isCreatServer,uint16_t port);
- static void vTaskTcpClinent(void *pvParameters);
- static void vTaskTcpServer(void *pvParameters);
- /***********************************************************************
- * 函数:
- * 描述: 创建tcp接收发送任务,给外部使用
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- void AppTaskCreate(void)
- {
- xTaskCreate(vTaskTcpClinent, //任务函数
- "vTaskTcpClinent", // 任务名
- TcpClinent_TASK_STACK_SIZE, // 任务栈大小,单位 word,也就是 4 字节
- NULL, //任务参数
- TcpClinent_TASK_PRIO, //任务优先级
- &xHandleTaskTcpClinent);
- xTaskCreate(vTaskTcpServer, //任务函数
- "vTaskTcpServer", // 任务名
- TcpServer_TASK_STACK_SIZE, // 任务栈大小,单位 word,也就是 4 字节
- NULL, //任务参数
- TcpServer_TASK_PRIO, //任务优先级
- &xHandleTaskTcpServer);
- }
- /***********************************************************************
- * 函数:
- * 描述: 创建tcp客户端连接
- * 参数:ip:IP地址,port:端口号
- * 返回: 无
- * 备注:
- ************************************************************************/
- static esp_err_t CreateTcpClient(const char *ip,uint16_t port)
- {
- static struct sockaddr_in server_addr; //server地址
- //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
- EventBits_t bits = xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄
- WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置
- pdFALSE, //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
- pdFALSE, //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
- portMAX_DELAY); //等待延迟时间,一直等待
- if (bits & WIFI_CONNECTED_BIT)
- {
- //新建socket
- client_connect_socket = socket(AF_INET, SOCK_STREAM, 0);
- if (client_connect_socket < 0)
- {
- close(client_connect_socket);
- return ESP_FAIL;
- }
- //配置连接服务器信息
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(port);
- server_addr.sin_addr.s_addr = inet_addr(ip);
- //连接服务器
- if (connect(client_connect_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
- {
- close(client_connect_socket);
- return ESP_FAIL;
- }
- return ESP_OK;
- }
- else
- {
- return ESP_FAIL;
- }
- }
- /***********************************************************************
- * 函数:
- * 描述: 客户端任务
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- static void vTaskTcpClinent(void *pvParameters)
- {
- int len = 0; //长度
- uint8_t databuff[512]; //缓存
- //创建tcp客户端
- if (CreateTcpClient(TCP_SERVER_ADRESS,TCP_SERVER_PORT) == ESP_OK)
- {
- ESP_LOGI("vTaskTcpClinent", "connect OK");
- }
- else
- {
- ESP_LOGI("vTaskTcpClinent", "connect FAIL");
- }
- for (;;)
- {
- //读取接收数据
- len = recv(client_connect_socket, databuff, sizeof(databuff), 0);//阻塞函数
- if (len > 0)//接收到数据
- {
- send(client_connect_socket, databuff, len, 0);
- }
- else
- {
- //断开连接
- if (CreateTcpClient(TCP_SERVER_ADRESS,TCP_SERVER_PORT)== ESP_OK)
- {
- ESP_LOGI("vTaskTcpClinent", "connect OK");
- }
- vTaskDelay(500 / portTICK_PERIOD_MS);
- }
- vTaskDelay(50 / portTICK_PERIOD_MS);
- }
- }
- /***********************************************************************
- * 函数:
- * 描述: 创建服务端监听任务
- * 参数:isCreatServer:true创建socket并监听,false只监听,port监听的端口号
- * 返回: 无
- * 备注:
- ************************************************************************/
- static esp_err_t CreateTcpServer(bool isCreatServer,uint16_t port)
- {
- static struct sockaddr_in client_addr;
- static unsigned int socklen = sizeof(client_addr); //地址长度
- //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
- EventBits_t bits = xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄
- WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置
- pdFALSE, //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
- pdFALSE, //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
- portMAX_DELAY); //等待延迟时间,一直等待
- if (bits & WIFI_CONNECTED_BIT)
- {
- if (isCreatServer == true)
- {
- server_socket = socket(AF_INET, SOCK_STREAM, 0);
- if (server_socket < 0)
- {
- close(server_socket);
- return ESP_FAIL;
- }
- //配置新建server socket参数
- client_addr.sin_family = AF_INET;
- client_addr.sin_port = htons(port);
- client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- //bind:地址的绑定
- if (bind(server_socket, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0)
- {
- //bind失败后,关闭新建的socket,等待下次新建
- close(server_socket);
- return ESP_FAIL;
- }
- }
-
- //listen,下次时,直接监听
- if (listen(server_socket, 1) < 0)
- {
- //listen失败后,关闭新建的socket,等待下次新建
- close(server_socket);
- return ESP_FAIL;
- }
- //accept,搜寻全连接队列
- server_connect_socket = accept(server_socket, (struct sockaddr *)&client_addr, &socklen);
- if (server_connect_socket < 0)
- {
- //accept失败后,关闭新建的socket,等待下次新建
- close(server_socket);
- return ESP_FAIL;
- }
- return ESP_OK;
- }
- else
- {
- return ESP_FAIL;
- }
- }
- /***********************************************************************
- * 函数:
- * 描述: 服务端任务
- * 参数:
- * 返回: 无
- * 备注:
- ************************************************************************/
- static void vTaskTcpServer(void *pvParameters)
- {
- int len = 0; //长度
- uint8_t databuff[512]; //缓存
- //创建tcp客户端
- CreateTcpServer(true,TCP_CLIENT_PORT);
- for (;;)
- {
- //读取接收数据
- len = recv(server_connect_socket, databuff, sizeof(databuff), 0);//阻塞函数
- if (len > 0)//接收到数据
- {
- send(server_connect_socket, databuff, len, 0);
- }
- else
- {
- //断开连接
- CreateTcpServer(false,TCP_CLIENT_PORT);
- vTaskDelay(500 / portTICK_PERIOD_MS);
- }
- vTaskDelay(50 / portTICK_PERIOD_MS);
- }
- }
-
复制代码 代码:tcp_bsp.h文件
- #ifndef __TCP_BSP_H__
- #define __TCP_BSP_H__
- #ifdef __cplusplus
- extern "C" {
- #endif
- #define TCP_SERVER_ADRESS "192.168.33.16" //作为client,要连接TCP服务器地址
- #define TCP_SERVER_PORT 5200 //服务端端口
- #define TCP_CLIENT_PORT 5000 //TCP客户端端口
- #define WIFI_CONNECTED_BIT BIT0
- #define WIFI_FAIL_BIT BIT1
- extern EventGroupHandle_t xCreatedEventGroup_WifiConnect;
- void AppTaskCreate(void);
- #ifdef __cplusplus
- }
- #endif
- #endif /*#ifndef __TCP_BSP_H__*/
复制代码 6、 调试结果
方法:打开网络调试助手这是这是为TCP Server,根据IP调解代码中的IP地点和端口
- #define TCP_SERVER_ADRESS "192.168.33.16" //作为client,要连接TCP服务器地址
- #define TCP_SERVER_PORT 5200 //服务端端口
- #define TCP_CLIENT_PORT 5000 //TCP客户端端口
复制代码
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |