乐鑫Esp32学习之旅⑧ esp32上实现本地 UDP 客户端和服务端角色,在局域网内实现通讯。(附带Demo)



一. 前言;


  • 关于Esp32的学习,最近又落下了!心里有点不舒服,今天赶紧学习下demo,那么本篇带来的是esp32上实现UDP的客户端和服务器角色,可以在本地局域网和上位机或者其他一样协议的设备通讯!
  • 关于UDP的具体的协议,我就不多具体多说了! 或许你可以看看我之前在esp8266上提到的UDP内容!点击查看

二. UDP Client客户端;

  • 效果截图:

这里写图片描述


2.1 网络通讯常识和逻辑过程!


  • 我们知道,任何一个socket通讯,都是需要IP地址和port端口号的,那么我们的UDP Client的话,本地的IP地址和port端口号是默认为路由器分配的,而远程端口号是8265,服务器的地址我却选择255.255.255.255意思是不指定局域网内的某一设备,局域网所有的设备如果监听了这个端口号,那么都可以收到esp32发来的消息哦!!

  • 如果你要指定的IP地址的设备,那么就需要指定明确的地址,比如192.168.1.102类似!

下面开始说到代码实现的逻辑过程!

  • 第一步:上电后连接路由器,获取路由器分配的IP地址!

  • 第二步:系统消息监听,如果收到IP地址成功获取的回调,则开始创建socket

  • 第三步: 涉及到要连接服务器,是否存在?所以先判断下是否存在先,这样就比较全面,虽然说UDP是不可靠的,但是这样做,可以避免许多事情!或者连接成功路由器之后直接发送到指定的地址,不管是否存在!

  • 第四步:一旦服务器有心跳 ,我这里每时隔3s发送一个字符串到服务器!


2.2 代码过程!


  • ①. 连接路由器:
//wifi初始化,连接路由器
void wifi_init_sta() {

    udp_event_group = xEventGroupCreate();
    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    wifi_config_t wifi_config = { 
    .sta = { .ssid = GATEWAY_SSID, 
     .password = GATEWAY_PASSWORD } };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_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.");
    ESP_LOGI(TAG, "connect to ap SSID:%s password:%s \n",
    GATEWAY_SSID, GATEWAY_PASSWORD);
}

  • ②. 主要代码实现:
void udp_conn(void *pvParameters) {

    ESP_LOGI(TAG, "task udp_conn start... \n\r");

    //等待是否已经成功连接到路由器的标志位
    xEventGroupWaitBits(udp_event_group, WIFI_CONNECTED_BIT, false, true,
            portMAX_DELAY);

    //5秒之后开始创建 socket
    ESP_LOGI(TAG,"esp32 is ready !!! create udp client or connect servece after 5s... \n\r");
    vTaskDelay(5000 / portTICK_RATE_MS);

    //创建客户端并且检查是否创建成功
    ESP_LOGI(TAG, "Now Let us create udp client ... \n\r");
    if (create_udp_client() == ESP_FAIL) {
        ESP_LOGI(TAG, "client create socket error , stop !!! \n\r");
        vTaskDelete(NULL);
    } else {
        ESP_LOGI(TAG, "client create socket Succeed  !!! \n\r");
    }


    //创建一个发送和接收数据的任务
    TaskHandle_t tx_rx_task;
    xTaskCreate(&send_recv_data, "send_recv_data", 4096, NULL, 4, &tx_rx_task);

    //等待 UDP连接成功标志位
    xEventGroupWaitBits(udp_event_group, UDP_CONNCETED_SUCCESS, false, true,
            portMAX_DELAY);

    int bps;
    //下面要发送的消息
    char sendBuff[1024] = "hello xuhong,I am from Esp32 ...";

    while (1) {

        total_data = 0;
        vTaskDelay(3000 / portTICK_RATE_MS);
        //时隔三秒发送一次数据 
        send_Buff_with_UDP(sendBuff, 1024);
        bps = total_data / 3;

        if (total_data <= 0) {
            int err_ret = check_connected_socket();
            if (err_ret == -1) {
                //如果发送失败,则关闭 socket
                ESP_LOGW(TAG,"udp send & recv stop !!! will close socket ... \n\r");
                close_socket();
                break;
            }
        }
        //心跳
        ESP_LOGI(TAG, "udp recv %d byte per sec! total pack: %d \n\r", bps,
                success_pack);

    }

    vTaskDelete(tx_rx_task);
    vTaskDelete(NULL);
}

  • ③. 客户端创建的核心代码:
esp_err_t create_udp_client() {

    ESP_LOGI(TAG, "create_udp_client()");
    //打印下要连接的服务器地址
    ESP_LOGI(TAG, "connecting to %s:%d",SERVER_IP, SERVICE_PORT);

    mysocket = socket(AF_INET, SOCK_DGRAM, 0);

    if (mysocket < 0) {
        show_socket_error_reason(mysocket);
        return ESP_FAIL;
    }

    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(SERVICE_PORT);
    remote_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    return ESP_OK;
}

  • ④. 发送和接收的方法函数:

  • 发送方法:sendto(mysocket, sendBuff, 1024, 0, (struct sockaddr *) &remote_addr,
    sizeof(remote_addr));
    ,其中sendBuff是发送的数据。

  • 接收方法:recvfrom(mysocket, databuff, sizeof(databuff), 0,
    (struct sockaddr *) &remote_addr, &socklen);
    ,其中databuff是接收的数据。

三. UDP Server服务端;


  • 效果截图:

这里写图片描述


3.1 开启服务端的注意点:

  • ①:作为服务器端,无疑是自己作为AP热点模式为好,这样的好处在于客户端连接进来之后,当前网关的IP地址就是服务器地址;虽然说不作为热点模式也可以开启服务器端,但是这样不容易获取esp32的地址呢!

  • ②:服务器端的要点在于监听一个端口,等待客户端的连接,之后彼此通讯。

  • ③:实现过程就是先开启热点模式,成功之后,创建服务器端,监听某个端口号;


3.2 代码实现:

  • 第一步:初始化热点模式并且开启:
//wifi的softap初始化
void wifi_init_softap() {

    udp_event_group = xEventGroupCreate();
    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    //设置wifi的名字和密码,注意这个是开启的wifi热点配置,不是要配置连接的路由器账号密码
    wifi_config_t wifi_config = { .ap = {
            .ssid = AP_SSID, 
            .ssid_len = 0,
            .password = AP_PAW,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK }};
    if (strlen(AP_SSID) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }
    //设置当前模式为AP模式
    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());
}

  • ②:创建一个UDP服务器端,剩下的步骤和客户端也就一样了;

esp_err_t create_udp_server() {

    ESP_LOGI(TAG, "Create Udp Server succeed port : %d \n", SERVICE_PORT);
    mysocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (mysocket < 0) {
        show_socket_error_reason(mysocket);
        return ESP_FAIL;
    }
    //指定连接的服务器IP地址和端口号
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVICE_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(mysocket, (struct sockaddr *) &server_addr, sizeof(server_addr))
            < 0) {
        show_socket_error_reason(mysocket);
        close(mysocket);
        return ESP_FAIL;
    }
    return ESP_OK;
}

四. 代码细节;


  • esp32的代码结构还是比较人性化的,在熟悉代码过程中,不断轮询这个系统信息,每个ID对应的信息不一样的,这样很符合面对对象的编程,不断有回调信息,只需要监听这个方法返回的数据即可知道系统在干嘛的了!
static esp_err_t event_handler(void *ctx, system_event_t *event) {
    switch (event->event_id) {
    //station模式开启回调
    case SYSTEM_EVENT_STA_START:
        esp_wifi_connect();
        break;
    //断开与路由器的连接
    case SYSTEM_EVENT_STA_DISCONNECTED:
        esp_wifi_connect();
        xEventGroupClearBits(udp_event_group, WIFI_CONNECTED_BIT);
        break;
    //成功连接到路由器,获取到Ip地址
    case SYSTEM_EVENT_STA_GOT_IP:
        ESP_LOGI(TAG, "event_handler:SYSTEM_EVENT_STA_GOT_IP!");
        ESP_LOGI(TAG, "got ip:%s\n",
                ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
        xEventGroupSetBits(udp_event_group, WIFI_CONNECTED_BIT);
        break;
    //作为AP热点模式,检测到有子设备接入
    case SYSTEM_EVENT_AP_STACONNECTED:
        ESP_LOGI(TAG, "station:"MACSTR" join,AID=%d\n",
                MAC2STR(event->event_info.sta_connected.mac),
                event->event_info.sta_connected.aid);
        xEventGroupSetBits(udp_event_group, WIFI_CONNECTED_BIT);
        break;
    //作为AP热点模式,检测到有子设备断开了连接
    case SYSTEM_EVENT_AP_STADISCONNECTED:
        ESP_LOGI(TAG, "station:"MACSTR"leave,AID=%d\n",
                MAC2STR(event->event_info.sta_disconnected.mac),
                event->event_info.sta_disconnected.aid);
        xEventGroupSetBits(udp_event_group, UDP_CONNCETED_SUCCESS);
        xEventGroupClearBits(udp_event_group, WIFI_CONNECTED_BIT);
        break;
    default:
        break;
    }
    return ESP_OK;
}

  • rtosxEventGroupSetBits()xEventGroupWaitBits()是一对一的!当调用xEventGroupWaitBits()时候,会处于阻塞等待,直到xEventGroupSetBits()发送标志位才会执行下面的代码!

  • 宏定义的一些参数:注意下面的255.255.255是指局域网不指定设备的地址!
//全局声明:是否server服务器或者station客户端 ------> true为服务器,false为客户端
#define Server_Station_Option false

/*
 * 要连接的路由器名字和密码
 */

//路由器的名字
#define GATEWAY_SSID "AliyunOnlyTest"
//连接的路由器密码
#define GATEWAY_PASSWORD "aliyun#123456"

//数据包大小
#define EXAMPLE_DEFAULT_PKTSIZE 1024

/*
 * 自己作为AP热点时候,配置信息如下
 */
//ssid
#define AP_SSID "XuHong_Esp32"
//密码
#define AP_PAW "xuhong123456"
//最大连接数
#define EXAMPLE_MAX_STA_CONN 1

/*
 * station模式时候,服务器地址配置
 */
//服务器的地址:这里的 255.255.255.255是在局域网发送,不指定某个设备
#define SERVER_IP "255.255.255.255"
//端口号
#define SERVICE_PORT 8265

五. 其他;


  • 当执行make flash发生错误时候LOG如下,说明在windows下找不到这个串口,检查下esp32是否正常连接到电脑!注意在windows下设置的端口号应该是COM+端口号,比如COM2
raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
serial.serialutil.SerialException: could not open port 'COM2': WindowsError(2, '\xcf\xb5\xcd\xb3\xd5\xd2\xb2\xbb\xb5\xbd\xd6\xb8\xb6\xa8\xb5\xc4\xce\xc4\xbc\xfe\xa1\xa3')
make: *** [/home/Administrator/esp-idf/components/esptool_py/Makefile.projbuild:55:flash] 错误 1

猜你喜欢

转载自blog.csdn.net/xh870189248/article/details/80737111