一、TCP与UDP优缺点

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。

3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。

5、TCP对系统资源要求较多,UDP对系统资源要求较少。

二、概述

ESP-IDF使用开源 lwIP轻量级的TCP / IP堆栈。ESP-IDF版本lwIP(esp-lwip)与上游项目相比有一些修改和补充。

ESP-IDF支持以下功能 lwIP TCP / IP堆栈功能:

  • BSD套接字API

  • Netconn API已启用,但ESP-IDF应用程序未正式支持

BSD套接字API

BSD套接字API是一个通用的跨平台TCP / IP套接字API,该API起源于UNIX的Berkeley标准发行版,但现在已在POSIX规范的一部分中进行了标准化。BSD套接字有时称为POSIX套接字或Berkeley套接字。

正如ESP-IDF中实施的那样,lwIP支持BSD套接字API的所有常用用法。

ESP-IDF 编程指南——ESP-NETIF
ESP-IDF 编程指南——lwIP

三、API说明

以下 BSD Socket 接口位于 lwip/lwip/src/include/lwip/sockets.h

  • socket()
  • bind()
  • accept()
  • shutdown()
  • getpeername()
  • getsockopt()setsockopt()(请参阅套接字选项)
  • close()(通过虚拟文件系统组件)
  • read()readv()write()writev()(经由虚拟文件系统部件)
  • recv()recvmsg()recvfrom()
  • send()sendmsg()sendto()
  • select()(通过虚拟文件系统组件)
  • poll()(注意:在ESP-IDF上,poll()是通过内部调用select来实现的,因此,select()如果有可用的方法选择,建议直接使用。)
  • fcntl()(请参阅fcntl)

非标准功能:

  • ioctl()(请参阅ioctls)

四、TCP服务端

4.1 主要流程

4.1.1 第一步:新建socket

int addr_family = 0;
int ip_protocol = 0;addr_family = AF_INET;
ip_protocol = IPPROTO_IP;int listen_sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
if(listen_sock < 0)
{ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
}

4.1.2 第二步:配置服务器信息

#define TCP_PORT             3333                // TCP服务器端口号struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr.sin_port = htons(TCP_PORT);

4.1.3 第三步:绑定地址

int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if(err != 0)
{ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);ESP_LOGE(TAG, "IPPROTO: %d", addr_family);close(listen_sock);
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);

4.1.4 第四步:开始监听

err = listen(listen_sock, 1);    // 这里为啥是1,网上大多数是5
if(err != 0)
{ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);close(listen_sock);
}

4.1.5 第五步:等待客户端连接

while(1)
{struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6uint addr_len = sizeof(source_addr);int connect_sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);if(connect_sock < 0) {ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);close(listen_sock);}
}

4.1.6 第六步:接收数据

int len;
char rx_buffer[128];while(1)
{memset(rx_buffer, 0, sizeof(rx_buffer));    // 清空缓存        len = recv(connect_sock, rx_buffer, sizeof(rx_buffer), 0);  // 读取接收数据if(len < 0) {ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);} else if (len == 0) {ESP_LOGW(TAG, "Connection closed");} else {ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);}
}

4.1.7 第七步:发送数据

send(connect_socket, rx_buffer, sizeof(rx_buffer, 0);

4.2 配置SSID和密码连接WIFI创建TCP服务端

TCP Server类似

使用 esp-idf\examples\protocols\sockets\tcp_server 中的例程

/* BSD Socket API ExampleThis example code is in the Public Domain (or CC0 licensed, at your option.)Unless required by applicable law or agreed to in writing, thissoftware is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES ORCONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>#define PORT CONFIG_EXAMPLE_PORTstatic const char *TAG = "example";static void do_retransmit(const int sock)
{int len;char rx_buffer[128];do {len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);if (len < 0) {ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);} else if (len == 0) {ESP_LOGW(TAG, "Connection closed");} else {rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a stringESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);// send() can return less bytes than supplied length.// Walk-around for robust implementation. int to_write = len;while (to_write > 0) {int written = send(sock, rx_buffer + (len - to_write), to_write, 0);if (written < 0) {ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);}to_write -= written;}}} while (len > 0);
}static void tcp_server_task(void *pvParameters)
{char addr_str[128];int addr_family = (int)pvParameters;int ip_protocol = 0;struct sockaddr_in6 dest_addr;if (addr_family == AF_INET) {struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);dest_addr_ip4->sin_family = AF_INET;dest_addr_ip4->sin_port = htons(PORT);ip_protocol = IPPROTO_IP;} else if (addr_family == AF_INET6) {bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));dest_addr.sin6_family = AF_INET6;dest_addr.sin6_port = htons(PORT);ip_protocol = IPPROTO_IPV6;}int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);if (listen_sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);vTaskDelete(NULL);return;}
#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)// Note that by default IPV6 binds to both protocols, it is must be disabled// if both protocols used at the same time (used in CI)int opt = 1;setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endifESP_LOGI(TAG, "Socket created");int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err != 0) {ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);ESP_LOGE(TAG, "IPPROTO: %d", addr_family);goto CLEAN_UP;}ESP_LOGI(TAG, "Socket bound, port %d", PORT);err = listen(listen_sock, 1);if (err != 0) {ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);goto CLEAN_UP;}while (1) {ESP_LOGI(TAG, "Socket listening");struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6uint addr_len = sizeof(source_addr);int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);if (sock < 0) {ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);break;}// Convert ip address to stringif (source_addr.sin6_family == PF_INET) {inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);} else if (source_addr.sin6_family == PF_INET6) {inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);}ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);do_retransmit(sock);shutdown(sock, 0);close(sock);}CLEAN_UP:close(listen_sock);vTaskDelete(NULL);
}void app_main(void)
{ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.* Read "Establishing Wi-Fi or Ethernet Connection" section in* examples/protocols/README.md for more information about this function.*/ESP_ERROR_CHECK(example_connect());#ifdef CONFIG_EXAMPLE_IPV4xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET6, 5, NULL);
#endif
}

idf.py menuconfig 配置服务器端口

配置SSID和密码

然后 idf.py flash 编译下载

查看打印:
连接上WIFI后查看获取到的IP地址(如192.168.61.107)

打开TCP客户端,输入服务端IP和端口,选择连接,发送数据

4.3 作为AP创建TCP服务端

根据 esp-idf\examples\protocols\sockets\tcp_server 中的例程修改

/* BSD Socket API ExampleThis example code is in the Public Domain (or CC0 licensed, at your option.)Unless required by applicable law or agreed to in writing, thissoftware is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES ORCONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>#define PORT CONFIG_EXAMPLE_PORT#define EXAMPLE_ESP_WIFI_SSID      "ESP32_TEST"
#define EXAMPLE_ESP_WIFI_PASS      "12345678"
#define EXAMPLE_ESP_WIFI_CHANNEL   1
#define EXAMPLE_MAX_STA_CONN       4static const char *TAG = "example";static void wifi_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{if (event_id == WIFI_EVENT_AP_STACONNECTED) {wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",MAC2STR(event->mac), event->aid);} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",MAC2STR(event->mac), event->aid);}
}void wifi_init_softap(void)
{ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_ap();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));wifi_config_t wifi_config = {.ap = {.ssid = EXAMPLE_ESP_WIFI_SSID,.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),.channel = EXAMPLE_ESP_WIFI_CHANNEL,.password = EXAMPLE_ESP_WIFI_PASS,.max_connection = EXAMPLE_MAX_STA_CONN,.authmode = WIFI_AUTH_WPA_WPA2_PSK},};if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {wifi_config.ap.authmode = WIFI_AUTH_OPEN;}ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}static void do_retransmit(const int sock)
{int len;char rx_buffer[128];do {len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);if (len < 0) {ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);} else if (len == 0) {ESP_LOGW(TAG, "Connection closed");} else {rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a stringESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);// send() can return less bytes than supplied length.// Walk-around for robust implementation. int to_write = len;while (to_write > 0) {int written = send(sock, rx_buffer + (len - to_write), to_write, 0);if (written < 0) {ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);}to_write -= written;}}} while (len > 0);
}static void tcp_server_task(void *pvParameters)
{char addr_str[128];int addr_family = (int)pvParameters;int ip_protocol = 0;struct sockaddr_in6 dest_addr;if (addr_family == AF_INET) {struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);dest_addr_ip4->sin_family = AF_INET;dest_addr_ip4->sin_port = htons(PORT);ip_protocol = IPPROTO_IP;} else if (addr_family == AF_INET6) {bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));dest_addr.sin6_family = AF_INET6;dest_addr.sin6_port = htons(PORT);ip_protocol = IPPROTO_IPV6;}int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);if (listen_sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);vTaskDelete(NULL);return;}
#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)// Note that by default IPV6 binds to both protocols, it is must be disabled// if both protocols used at the same time (used in CI)int opt = 1;setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endifESP_LOGI(TAG, "Socket created");int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err != 0) {ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);ESP_LOGE(TAG, "IPPROTO: %d", addr_family);goto CLEAN_UP;}ESP_LOGI(TAG, "Socket bound, port %d", PORT);err = listen(listen_sock, 1);if (err != 0) {ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);goto CLEAN_UP;}while (1) {ESP_LOGI(TAG, "Socket listening");struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6uint addr_len = sizeof(source_addr);int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);if (sock < 0) {ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);break;}// Convert ip address to stringif (source_addr.sin6_family == PF_INET) {inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);} else if (source_addr.sin6_family == PF_INET6) {inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);}ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);do_retransmit(sock);shutdown(sock, 0);close(sock);}CLEAN_UP:close(listen_sock);vTaskDelete(NULL);
}void app_main(void)
{ESP_ERROR_CHECK(nvs_flash_init());// ESP_ERROR_CHECK(esp_netif_init());// ESP_ERROR_CHECK(esp_event_loop_create_default());/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.* Read "Establishing Wi-Fi or Ethernet Connection" section in* examples/protocols/README.md for more information about this function.*/// ESP_ERROR_CHECK(example_connect());wifi_init_softap();#ifdef CONFIG_EXAMPLE_IPV4xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET6, 5, NULL);
#endif
}

idf.py menuconfig 配置服务器端口

然后 idf.py flash 编译下载

查看打印:

IP默认是192.168.4.1 这个是乐鑫在出厂时候写死的

打开TCP客户端,输入服务端IP和端口,选择连接,发送数据


• 由 Leung 写于 2021 年 4 月 28 日

• 参考:乐鑫Esp32学习之旅⑨ esp32上实现本地 TCP 客户端和服务端角色,可断线重连原路返回数据
    ESP32 开发笔记(三)源码示例 20_WIFI_STA_TCP_Server 在站模式STA下实现TCP服务端
    ESP32 开发笔记(三)源码示例 16_WIFI_AP_TCP_Server 在AP模式下实现TCP服务端

ESP32学习笔记(9)——TCP服务端相关推荐

  1. ASP.Net学习笔记002--ASP.Net服务端控件做了什么2

    ASP.Net学习笔记002--ASP.Net服务端控件做了什么2 以前写的课程都没有附上源码,很抱歉! 课程中的源码可以加qq索要:1606841559 技术交流qq1群:251572072 技术交 ...

  2. Netty学习笔记(二) 实现服务端和客户端

    在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...

  3. Netty学习笔记(二)Netty服务端流程启动分析

    先贴下在NIO和Netty里启动服务端的代码 public class NioServer { /*** 指定端口号启动服务* */public boolean startServer(int por ...

  4. 《精通并发与Netty》学习笔记(02 - 服务端程序编写)

    上节我们介绍了开发netty项目所必需的开发环境及工具的使用,这节我们来写第一个netty项目 开发步骤 第一步:打开https://search.maven.org 找到netty依赖库 第二步:打 ...

  5. Web全栈开发学习笔记—Part2 与服务端通信—d.在服务端将数据Alert出来

    目录 REST Sending Data to the Server Changing the importance of notes Extracting communication with th ...

  6. ESP32学习笔记(30)——BLE GATT服务端自定义服务和特征

    一.简介 1.1 低功耗蓝牙(BLE)协议栈 链路层(LL) 控制设备的射频状态,有五个设备状态:待机.广播.扫描.初始化和连接. 广播 为广播数据包,而 扫描 则是监听广播. GAP通信中角色,中心 ...

  7. ESP32学习笔记(14)——HTTP服务器

    一.HTTP简介 HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明客 ...

  8. 关于esp32蓝牙模块的使用——esp32学习笔记

    关于esp32蓝牙模块的使用--esp32学习笔记 关于esp32蓝牙模块的使用--esp32学习笔记 关于esp32蓝牙模块的使用--esp32学习笔记 零.前言 一.经典蓝牙BT 二.低功耗蓝牙B ...

  9. 2-3 建立简易TCP服务端、客户端【socket server/client】【socket、bind、listen、accept、send、closesocket】【conect、recv】

    2-3 建立简易TCP服务端.客户端 文章目录 2-3 建立简易TCP服务端.客户端 0-前言 1-服务端简易功能 2-客户端简易功能 3-代码逻辑 4-服务端 4-1 建立socket 4-2 绑定 ...

最新文章

  1. keras回调监控函数
  2. javacurrentmap_Java 8 并发: 原子变量和 ConcurrentMap
  3. 银行事后监督及票据影像光盘缩微系统
  4. 工作中常用的第三放的框架
  5. 长篇小说《世界上最幸福的人》获得好评
  6. 【创业】创业团队的那些事(二)
  7. 微服务介绍及Asp.net Core实战项目系列之微服务介绍
  8. matlab期权风险评估算法,使用 MATLAB 应用程序根据期权价格估算风险中性密度 (risk-neutral density, RND)...
  9. curl header设置参数
  10. YUV420转RGB888
  11. 干货!10分钟,用Python生成图文并茂的PDF报告!
  12. 【线性代数01】矩阵的转置和逆
  13. Revit模型如何在网页上显示
  14. zookeeper-选举流程
  15. 【python】电商批量打标logo,超快速超简单!!!
  16. 物联网外设学习笔记-摄像头(一)
  17. Debug以及解题思路
  18. 2017IDC企业级WLAN榜单将揭晓,新华三继续领跑企业级市场、聚焦“十连冠”
  19. elementUI checkbox选中与取消选中
  20. 百度乐居能否引领房地产市场新风向标?

热门文章

  1. 人工智能非技术从业者必知的十件事
  2. dashu java_Java中的大数
  3. Python第三方库之MedPy
  4. HTTP洪水Gong击网站-演示
  5. (九)打印机驱动设置—USB接口的设置
  6. 如何实现一篇数据新闻报道
  7. 【Proteus仿真】51单片机+74HC164驱动两个四位数码管
  8. android app锁屏后定位,如何能让app在锁屏后还继续发送定位请求继续运行程序
  9. 大型传统企业的数字化创新之路
  10. C# 互操 调用COM组件