ESP32 Websocket Server

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Tue Jul 21, 2020 7:22 pm

No espressif expert to help ????

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Thu Jul 23, 2020 7:34 am

Hi,

I have a javascript client running in a browser.

When i open the "html_page(html,css,javascript)" file from my computer, ie: not downloading from esp32 server, open one single socket per one open browser tab. When i close the browser tab the socket are closed. Five sockets open and five sockets closed, don't open any more sockets(config.max_open_sockets = 5;). Everything looks OK.

When i open the "html_page(html,css,javascript)" file, downloading from esp32 server, first download open 3 sockets per one single open browser tab. One socket are certainly websockets, but the other 2 sockets ? I think that are tcp or http handshake sockets that "esp_http_server" dont immediately closed after handshake or my javascript code could be wrong ( get html page and favicon.ico from http server, close http/tcp socket and switch to websocket ??? i dont know ). I try to disable favicon uri to see if one socket was from the GET html page and the other socket was from GET the favicon icon, but remained with 3 sockets, by my logic should have only 2 sockets. When i close the browser tab the socket(websocket) are closed. The other 2 sockets stay open, sometimes the system(os ??) close sporadically these 2 sockets after some time.

I see the open sockets and closed sockets in the callback functions "esp_err_t my_open_fn(httpd_handle_t hd, int sockfd)" and "void my_close_fn(httpd_handle_t hd, int sockfd)".

Why there are these 2 extra sockets ???

How to know what type of socket "sockfd" are ( http, tcp, websocket ) ???

Code: Select all

//typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd);

esp_err_t my_open_fn(httpd_handle_t hd, int sockfd)
{
    printf("Open Function:\n");

    printf("hd = %p\n", hd );

    printf("sockfd = %d \n\n", sockfd );

    return ESP_OK;
}

Code: Select all

//typedef void (*httpd_close_func_t)(httpd_handle_t hd, int sockfd);

void my_close_fn(httpd_handle_t hd, int sockfd)
{
    printf("Close Function:\n");

    printf("hd = %p\n", hd );

    printf("sockfd = %d \n\n", sockfd );
}

Code: Select all

httpd_handle_t server = NULL;

esp_err_t start_http_server( )  
{    
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();  
    config.core_id = PRO_CPU_NUM;
    config.max_open_sockets = 5; 

    config.open_fn = my_open_fn;
    config.close_fn = my_close_fn;

    ESP_LOGI(TAG, "Starting WebSocket Server");

    if ( httpd_start(&server, &config) != ESP_OK )
    {
        ESP_LOGE(TAG, "Failed to start the server!");
        return ESP_FAIL;
    }
    			
    printf("\nserver = %p\n\n", server );
	
	 
    // URI handler for getting "html page" file
    httpd_uri_t html_page_file_download = {
        .uri       = "/",  
        .method    = HTTP_GET,
        .handler   = html_download_get_handler,
        .user_ctx  = NULL 
    };
    httpd_register_uri_handler(server, &html_page_file_download);



    // URI handler for getting "html page" file
    httpd_uri_t html_page_file_download_index = {
        .uri       = "/index.html",  
        .method    = HTTP_GET,
        .handler   = html_download_get_handler,
        .user_ctx  = NULL 
    };
    httpd_register_uri_handler(server, &html_page_file_download_index);



    // URI handler for getting "favicon ico" file
    httpd_uri_t favicon_file_download = {
        .uri       = "/favicon.ico",  
        .method    = HTTP_GET,
        .handler   = favicon_download_get_handler,
        .user_ctx  = NULL    
    };
    httpd_register_uri_handler(server, &favicon_file_download);


    
    // URI handler for send state of variables using webSockets.
    httpd_uri_t state_download_async = {
        .uri       = "/ws",  
        .method    = HTTP_GET,
        .handler   = state_get_handler_async,
        .user_ctx  = NULL,
        .is_websocket = true
    };
    httpd_register_uri_handler(server, &state_download_async);


    return ESP_OK;
}
Thank's.

ESP_cermak
Posts: 69
Joined: Thu Nov 01, 2018 8:32 am

Re: ESP32 Websocket Server

Postby ESP_cermak » Thu Jul 23, 2020 2:40 pm

Hi Baldhead,

For asynchronous send from the ws server, you can use `httpd_queue_work()` as shown in the example with ws_async_send(). This is triggered as a response to some request (for testing purpose only), but could be used any time for any connected client. It only needs the httpd handle, and the underlying fd, which could be retrieved after the client connects from their `req` using `httpd_req_to_sockfd()`.

As for the open sockets, there should be one socket per one client. Please test with command line tools or scripts, rather then opening browser window. (browsers could use speculative connections, opening multiple connections in parallel). Suggest using netcat or python to make sure only one client connects. Please prefer some existing websocket library to implementing lower layers yourself since there is less chance for making an error. (our python example tests use low level sockets for now, but this will be reworked)

You can also check the opened sockets or enable socket level debugging in lwip:

Code: Select all

for (int i=0; i<CONFIG_LWIP_MAX_SOCKETS; ++i) {
    struct sockaddr_in6 addr;
    socklen_t addr_size = sizeof(addr);
    int sock = LWIP_SOCKET_OFFSET + i;
    int res = getpeername(sock, (struct sockaddr *)&addr, &addr_size);
    if (res == 0) {
        ESP_LOGI(TAG, "sock: %d -- addr: %x, port: %d", sock, addr.sin6_addr.un.u32_addr[3], addr.sin6_port);        
    }
}
This might give you some idea about the remote endpoints which opened the socket (note that there should be only a single client socket which is connected as a TCP socket, then used as HTTP connection and updated to WS protocol)

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Fri Jul 24, 2020 3:37 am

Hi ESP_cermak,

Thank's for the reply.

I will do some comments in your text.
ESP_cermak wrote:
Thu Jul 23, 2020 2:40 pm
Hi Baldhead,

For asynchronous send from the ws server, you can use `httpd_queue_work()` as shown in the example with ws_async_send(). This is triggered as a response to some request (for testing purpose only), but could be used any time for any connected client. It only needs the httpd handle, and the underlying fd, which could be retrieved after the client connects from their `req` using `httpd_req_to_sockfd()`.

Answer: When i enter "http://192.168.0.7"(ip from esp32 http server) in chrome browser, i download "html_page(html,css,javascript)" file from esp32 server, and the chrome open 3 sockets, one socket are websocket socket, the other 2 sockets i dont know.
So there are a websocket socket open in esp32 server, but if the client doesn't send any message to the server the callback function are not called, so, i can not use "httpd_req_to_sockfd()" because i dont have the req.
Imagine that the client opens the connection to the server, but do not send any messages.
How the server will send a message to the client ?
I need another way of get the fd of each connection and if possible i would like to close the other 2 sockets that chrome open and don't close.

When i enter "http://192.168.0.7"(ip from esp32 http server) in firefox browser, i download "html_page(html,css,javascript)" file from esp32 server, and the firefox open 2 sockets, one socket are websocket socket, the other 1 sockets i dont know.

Using Curl i can only download the "html_page(html,css,javascript)" file from esp32 server, because javascript websocket code in that page doesn't execute obviously.
In this case open a single socket and after the page download the socket are closed.


As for the open sockets, there should be one socket per one client. Please test with command line tools or scripts, rather then opening browser window. (browsers could use speculative connections, opening multiple connections in parallel). Suggest using netcat or python to make sure only one client connects. Please prefer some existing websocket library to implementing lower layers yourself since there is less chance for making an error. (our python example tests use low level sockets for now, but this will be reworked)

Answer: I need to renderize my html page to send and receive websockets commands from objects in this page.

I am using low level javascript websockets, i think are low level.
In html5 websockets is a standard i think.

In the future i want to do an android and ios app.


You can also check the opened sockets or enable socket level debugging in lwip:

Code: Select all

for (int i=0; i<CONFIG_LWIP_MAX_SOCKETS; ++i) {
    struct sockaddr_in6 addr;
    socklen_t addr_size = sizeof(addr);
    int sock = LWIP_SOCKET_OFFSET + i;
    int res = getpeername(sock, (struct sockaddr *)&addr, &addr_size);
    if (res == 0) {
        ESP_LOGI(TAG, "sock: %d -- addr: %x, port: %d", sock, addr.sin6_addr.un.u32_addr[3], addr.sin6_port);        
    }
}
This might give you some idea about the remote endpoints which opened the socket (note that there should be only a single client socket which is connected as a TCP socket, then used as HTTP connection and updated to WS protocol)

Answer: i understand that there should be only a tcp connection(socket) to send the html page, the favicon icon and then upgrade to the websocket protocol.
I don't know why browsers open extra sockets and don't close them.

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Sun Jul 26, 2020 10:48 pm

Hi,

After send "Connection: close" in the http header from esp32 server to chrome client request, apparently solved the problem of several open sockets in server per client connection.

I put "Connection: close" in "html_download_get_handler" and "favicon_download_get_handler".

Now the chrome client yet open several sockets, but close them, and only websocket socket stay open.

Code: Select all

/* Handler to download "upload_script.html" file kept on the server */
static esp_err_t html_download_get_handler(httpd_req_t *req)
{   
    int fd = httpd_req_to_sockfd(req);    
    printf("\nSocket html = %d", fd );

    // Get handle to embedded file "upload_script.html" 
    extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
    extern const unsigned char upload_script_end[]   asm("_binary_upload_script_html_end");
    const size_t upload_script_size = (upload_script_end - upload_script_start);

    const char* field = "Connection";
    const char* value = "close";

    esp_err_t ret = httpd_resp_set_hdr( req, field, value);  
    printf("\nhtml ret = %d", ret);
        
    ret = httpd_resp_send( req, (const char *)upload_script_start, upload_script_size );
    printf("\nhtml ret1 = %d\n", ret);
       
    return ESP_OK;    // return ret;    
}

Code: Select all

// Handler to download a "favicon.ico" file kept on the server 
static esp_err_t favicon_download_get_handler(httpd_req_t *req)
{   
    int fd = httpd_req_to_sockfd(req);    
    printf("\nSocket favicon = %d", fd );
            
    extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
    extern const unsigned char favicon_ico_end[]   asm("_binary_favicon_ico_end");
    const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
    httpd_resp_set_type(req, "image/x-icon");

    const char *field = "Connection";
    const char *value = "close";

    esp_err_t ret = httpd_resp_set_hdr( req, field, value);  
    printf("\nfavicon header set ret = %d", ret);

    ret = httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
    printf("\nfavicon ret1 = %d\n", ret);

    return ESP_OK;    // return ret; 
}

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Sun Jul 26, 2020 11:13 pm

Hi,

I would like to create a list of connected websocket sockets clients in esp32 server to be able to send "common messages" and "ping messages" to the all the clients.
Although, from what i researched, i believe that websockets api in javascript in the browser does not support responding with pong yet, i think only "common text messages".

Suggestions for list types are appreciated ( linked list ??? ).
Is there in esp idf a generic linked list lib that i can use ?
Other list types are better for this application ?

Is there a way to scan the sockets in the system and verify what of that sockets are of type websockets ?
ie:
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */

Code: Select all

/**
 * @brief A database of all the open sockets in the system.
 */
struct sock_db {
    int fd;                                 /*!< The file descriptor for this socket */
    void *ctx;                              /*!< A custom context for this socket */
    bool ignore_sess_ctx_changes;           /*!< Flag indicating if session context changes should be ignored */
    void *transport_ctx;                    /*!< A custom 'transport' context for this socket, to be used by send/recv/pending */
    httpd_handle_t handle;                  /*!< Server handle */
    httpd_free_ctx_fn_t free_ctx;      /*!< Function for freeing the context */
    httpd_free_ctx_fn_t free_transport_ctx; /*!< Function for freeing the 'transport' context */
    httpd_send_func_t send_fn;              /*!< Send function for this socket */
    httpd_recv_func_t recv_fn;              /*!< Receive function for this socket */
    httpd_pending_func_t pending_fn;        /*!< Pending function for this socket */
    uint64_t lru_counter;                   /*!< LRU Counter indicating when the socket was last used */
    char pending_data[PARSER_BLOCK_SIZE];   /*!< Buffer for pending data to be received */
    size_t pending_len;                     /*!< Length of pending data to be received */
#ifdef CONFIG_HTTPD_WS_SUPPORT
    bool ws_handshake_done;                 /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
    bool ws_close;                          /*!< Set to true to close the socket later (when WS Close frame received) */
    esp_err_t (*ws_handler)(httpd_req_t *r);   /*!< WebSocket handler, leave to null if it's not WebSocket */
#endif
};

Thank's.

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Tue Jul 28, 2020 9:16 pm

Baldhead wrote:
Sun Jul 26, 2020 10:48 pm

After send "Connection: close" in the http header from esp32 server to chrome client request, apparently solved the problem of several open sockets in server per client connection.

Unfortunately it didn't solve the problem.

Probably the best solution is to program a websocket client from scratch.

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Mon Aug 03, 2020 12:16 am

Hi,

I am trying to implement a websocket server using the esp-idf component "esp_http_server".

I can send and receive from my websockets clients messages of type HTTPD_WS_TYPE_TEXT, all works ok.

When i try to send a reply with a ping frame from server to client using type "HTTPD_WS_TYPE_PING", the client Pong answer
to server and the server has an error:

********************************************************************************************************************************************************************
Test one: On javascript running in a chrome browser ( by my searchs the JavaScript websocket api automatically replies with a pong the received pings).

W (40157) httpd_ws: httpd_ws_recv_frame: WS frame is not properly masked.
E (40157) WS_Server: httpd_ws_recv_frame failed with 259
Close socket.
********************************************************************************************************************************************************************
Test two: On Android studio app using okhttp library(okhttp:4.8.0).

Packet Final Frame = true
Packet type: UNKNOWN
Got packet with message: ar<EBe) T-t����������������������������������������������������������������
Packet lenght: 97

W (78846) httpd_ws: httpd_ws_recv_frame: WS frame is not properly masked.
E (78847) WS_Server: httpd_ws_recv_frame failed with 259
Close socket.

P.S. The packet frame and message are not equal in each new request/answer.
*******************************************************************************************************************************************************************
Additional context:

https://github.com/espressif/esp-idf/issues/5686

My esp idf installed version: v4.2-dev-1235-g71dc5eb27


Thank's.

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Tue Aug 04, 2020 1:05 am

Hi,

I am trying to implement a heart beat from a ping sent from the client to the websocket server.

I would like to know the correct way to close a socket on the websocket server.

In the below function( httpd_ws.c file ) are the "if(aux->ws_type == HTTPD_WS_TYPE_PING)".

I'm still going to do the logic, but i would like to know what is the correct way to close a socket, in case i don't receive any more ping from the client.

Are there a clean socket close or i can use this directly ???

closesocket( fd );
closesocket( aux->sd->fd );

Code: Select all

esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
{
    esp_err_t ret = httpd_ws_check_req(req);
    if (ret != ESP_OK) {
        return ret;
    }

    struct httpd_req_aux *aux = req->aux;
    if (aux == NULL) {
        ESP_LOGW(TAG, LOG_FMT("Invalid Aux pointer"));
        return ESP_ERR_INVALID_ARG;
    }

    /* Read the first byte from the frame to get the FIN flag and Opcode */
    /* Please refer to RFC6455 Section 5.2 for more details */
    uint8_t first_byte = 0;
    if (httpd_recv_with_opt(req, (char *)&first_byte, sizeof(first_byte), false) <= 0) {
        /* If the recv() return code is <= 0, then this socket FD is invalid (i.e. a broken connection) */
        /* Here we mark it as a Close message and close it later. */
        ESP_LOGW(TAG, LOG_FMT("Failed to read header byte (socket FD invalid), closing socket now"));
        aux->ws_final = true;
        aux->ws_type = HTTPD_WS_TYPE_CLOSE;
        return ESP_OK;
    }

    ESP_LOGD(TAG, LOG_FMT("First byte received: 0x%02X"), first_byte);

    /* Decode the FIN flag and Opcode from the byte */
    aux->ws_final = (first_byte & HTTPD_WS_FIN_BIT) != 0;
    aux->ws_type = (first_byte & HTTPD_WS_OPCODE_BITS);

    /* Reply to PING. For PONG and CLOSE, it will be handled elsewhere. */
    if(aux->ws_type == HTTPD_WS_TYPE_PING) {
        ESP_LOGD(TAG, LOG_FMT("Got a WS PING frame, Replying PONG..."));


        /* Read the rest of the PING frame, for PONG to reply back. */
        /* Please refer to RFC6455 Section 5.5.2 for more details */
        httpd_ws_frame_t frame;
        uint8_t frame_buf[128] = { 0 };
        memset(&frame, 0, sizeof(httpd_ws_frame_t));
        frame.payload = frame_buf;

        if(httpd_ws_recv_frame(req, &frame, 126) != ESP_OK) {
            ESP_LOGD(TAG, LOG_FMT("Cannot receive the full PING frame"));
            return ESP_ERR_INVALID_STATE;
        }



// <my tests>   
      
        printf("\nGot a WS PING frame, Replying PONG...\n");        
        printf("Packet Final Frame = %s\n", frame.final ? "true" : "false");
        //printf("Packet type: %d\n", ws_pkt.type);

        switch (frame.type) 
        {
            case HTTPD_WS_TYPE_CONTINUE: printf("Packet type: HTTPD_WS_TYPE_CONTINUE");  break;
            case HTTPD_WS_TYPE_TEXT:         printf("Packet type: HTTPD_WS_TYPE_TEXT");          break;   
            case HTTPD_WS_TYPE_BINARY:      printf("Packet type: HTTPD_WS_TYPE_BINARY");       break;
            case HTTPD_WS_TYPE_CLOSE:       printf("Packet type: HTTPD_WS_TYPE_CLOSE");        break;
            case HTTPD_WS_TYPE_PING:         printf("Packet type: HTTPD_WS_TYPE_PING");          break;
            case HTTPD_WS_TYPE_PONG:        printf("Packet type: HTTPD_WS_TYPE_PONG");        break;
            default:                                        printf("Packet type: UNKNOWN");                            break;
        }
        
        printf("\n");
        printf("Got ping with message: %s\n", frame.payload);    
        printf("Packet lenght: %d\n", frame.len);
    

        int fd = httpd_req_to_sockfd( req );
        printf( "fd Socket = %d\n", fd );        
        printf( "aux->sd->fd Socket = %d\n", aux->sd->fd );
                       
        // closesocket( fd );               
                       
        closesocket( aux->sd->fd );

// </my tests>


        /*
        // Now turn the frame to PONG //
        frame.type = HTTPD_WS_TYPE_PONG;

        //frame.final = true;    // This lib in esp32 is already considering that the ping is coming with frame.final = true; Possibly if the ping was not being received with this "frame.final = true" it would give an error.

        return httpd_ws_send_frame(req, &frame);
        */
        return ESP_OK;

    }

    return ESP_OK;
}

#endif /* CONFIG_HTTPD_WS_SUPPORT */

Thank's.

Baldhead
Posts: 433
Joined: Sun Mar 31, 2019 5:16 am

Re: ESP32 Websocket Server

Postby Baldhead » Wed Aug 05, 2020 7:59 am

Hi again,

I am try to send a message with the "server state" from websocket server in esp32 to a client at the moment when the client socket are open on server, but the socket are closed wih an error and i verify in Wireshark that the websocket handshake are not taken.

I think that "websocket handshake" not finished is the problem.

How can i verify if "htdata->hd_sd->ws_handshake_done) == true" ????

Code: Select all

//typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd);

// Called on a new session socket just after accept(), but before reading any data.
// #include "esp_httpd_priv.h"

static int fd_list[CONFIG_LWIP_MAX_SOCKETS]; 

#define no_socket INT_MIN     // INT_MIN  stay in   #include <limits.h>

esp_err_t opened_socket_handler(httpd_handle_t hd, int sockfd)
{
    printf("\nOpen Socket = %d\n", sockfd );

    int i;

    for ( i = 0 ; i < CONFIG_LWIP_MAX_SOCKETS ; i++ )    // scan "all" list
    {
        if ( fd_list[i] == no_socket )    //  fd_list[i] == no_socket mean no socket.
        {
            fd_list[i] = sockfd;    // insert the socket at the list of connected sockets.

            break;
        }
    }

    printList();


/*
    struct httpd_data* htdata = (struct httpd_data *) hd;
    while ( (htdata->hd_sd->ws_handshake_done) == false )
*/


    send_Server_State( &fd_list[i] );   // Send current server status to the socket (client) that just opened.
                                        

    // Create ping object.

    return ESP_OK;
}

Note: I can already send a message(same message) to all connected clients, but after the websocket are open(ie: finished websocket handshake i think).


Thank's for the help.

Who is online

Users browsing this forum: jgrossholtz and 233 guests