Re: ESP32 Websocket Server
Posted: Tue Jul 21, 2020 7:22 pm
No espressif expert to help ????
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;
}
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);
}
}
ESP_cermak wrote: ↑Thu Jul 23, 2020 2:40 pmHi 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: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)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); } }
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.
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;
}
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
};
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 */
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;
}