| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- /* BSD non-blocking socket example
- This example code is in the Public Domain (or CC0 licensed, at your option.)
- Unless required by applicable law or agreed to in writing, this
- software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
- CONDITIONS OF ANY KIND, either express or implied.
- */
- #include <string.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "sys/socket.h"
- #include "netdb.h"
- #include "errno.h"
- #include "esp_system.h"
- #include "esp_event.h"
- #include "esp_log.h"
- #include "nvs_flash.h"
- #include "protocol_examples_common.h"
- /**
- * @brief Indicates that the file descriptor represents an invalid (uninitialized or closed) socket
- *
- * Used in the TCP server structure `sock[]` which holds list of active clients we serve.
- */
- #define INVALID_SOCK (-1)
- /**
- * @brief Time in ms to yield to all tasks when a non-blocking socket would block
- *
- * Non-blocking socket operations are typically executed in a separate task validating
- * the socket status. Whenever the socket returns `EAGAIN` (idle status, i.e. would block)
- * we have to yield to all tasks to prevent lower priority tasks from starving.
- */
- #define YIELD_TO_ALL_MS 50
- /**
- * @brief Utility to log socket errors
- *
- * @param[in] tag Logging tag
- * @param[in] sock Socket number
- * @param[in] err Socket errno
- * @param[in] message Message to print
- */
- static void log_socket_error(const char *tag, const int sock, const int err, const char *message)
- {
- ESP_LOGE(tag, "[sock=%d]: %s\n"
- "error=%d: %s", sock, message, err, strerror(err));
- }
- /**
- * @brief Tries to receive data from specified sockets in a non-blocking way,
- * i.e. returns immediately if no data.
- *
- * @param[in] tag Logging tag
- * @param[in] sock Socket for reception
- * @param[out] data Data pointer to write the received data
- * @param[in] max_len Maximum size of the allocated space for receiving data
- * @return
- * >0 : Size of received data
- * =0 : No data available
- * -1 : Error occurred during socket read operation
- * -2 : Socket is not connected, to distinguish between an actual socket error and active disconnection
- */
- static int try_receive(const char *tag, const int sock, char * data, size_t max_len)
- {
- int len = recv(sock, data, max_len, 0);
- if (len < 0) {
- if (errno == EINPROGRESS || errno == EAGAIN || errno == EWOULDBLOCK) {
- return 0; // Not an error
- }
- if (errno == ENOTCONN) {
- ESP_LOGW(tag, "[sock=%d]: Connection closed", sock);
- return -2; // Socket has been disconnected
- }
- log_socket_error(tag, sock, errno, "Error occurred during receiving");
- return -1;
- }
- return len;
- }
- /**
- * @brief Sends the specified data to the socket. This function blocks until all bytes got sent.
- *
- * @param[in] tag Logging tag
- * @param[in] sock Socket to write data
- * @param[in] data Data to be written
- * @param[in] len Length of the data
- * @return
- * >0 : Size the written data
- * -1 : Error occurred during socket write operation
- */
- static int socket_send(const char *tag, const int sock, const char * data, const size_t len)
- {
- int to_write = len;
- while (to_write > 0) {
- int written = send(sock, data + (len - to_write), to_write, 0);
- if (written < 0 && errno != EINPROGRESS && errno != EAGAIN && errno != EWOULDBLOCK) {
- log_socket_error(tag, sock, errno, "Error occurred during sending");
- return -1;
- }
- to_write -= written;
- }
- return len;
- }
- #ifdef CONFIG_EXAMPLE_TCP_CLIENT
- static void tcp_client_task(void *pvParameters)
- {
- static const char *TAG = "nonblocking-socket-client";
- static const char *payload = "GET / HTTP/1.1\r\n\r\n";
- static char rx_buffer[128];
- struct addrinfo hints = { .ai_socktype = SOCK_STREAM };
- struct addrinfo *address_info;
- int sock = INVALID_SOCK;
- int res = getaddrinfo(CONFIG_EXAMPLE_TCP_CLIENT_CONNECT_ADDRESS, CONFIG_EXAMPLE_TCP_CLIENT_CONNECT_PORT, &hints, &address_info);
- if (res != 0 || address_info == NULL) {
- ESP_LOGE(TAG, "couldn't get hostname for `%s` "
- "getaddrinfo() returns %d, addrinfo=%p", CONFIG_EXAMPLE_TCP_CLIENT_CONNECT_ADDRESS, res, address_info);
- goto error;
- }
- // Creating client's socket
- sock = socket(address_info->ai_family, address_info->ai_socktype, address_info->ai_protocol);
- if (sock < 0) {
- log_socket_error(TAG, sock, errno, "Unable to create socket");
- goto error;
- }
- ESP_LOGI(TAG, "Socket created, connecting to %s:%s", CONFIG_EXAMPLE_TCP_CLIENT_CONNECT_ADDRESS, CONFIG_EXAMPLE_TCP_CLIENT_CONNECT_PORT);
- // Marking the socket as non-blocking
- int flags = fcntl(sock, F_GETFL);
- if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
- log_socket_error(TAG, sock, errno, "Unable to set socket non blocking");
- }
- if (connect(sock, address_info->ai_addr, address_info->ai_addrlen) != 0) {
- if (errno == EINPROGRESS) {
- ESP_LOGD(TAG, "connection in progress");
- fd_set fdset;
- FD_ZERO(&fdset);
- FD_SET(sock, &fdset);
- // Connection in progress -> have to wait until the connecting socket is marked as writable, i.e. connection completes
- res = select(sock+1, NULL, &fdset, NULL, NULL);
- if (res < 0) {
- log_socket_error(TAG, sock, errno, "Error during connection: select for socket to be writable");
- goto error;
- } else if (res == 0) {
- log_socket_error(TAG, sock, errno, "Connection timeout: select for socket to be writable");
- goto error;
- } else {
- int sockerr;
- socklen_t len = (socklen_t)sizeof(int);
- if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&sockerr), &len) < 0) {
- log_socket_error(TAG, sock, errno, "Error when getting socket error using getsockopt()");
- goto error;
- }
- if (sockerr) {
- log_socket_error(TAG, sock, sockerr, "Connection error");
- goto error;
- }
- }
- } else {
- log_socket_error(TAG, sock, errno, "Socket is unable to connect");
- goto error;
- }
- }
- ESP_LOGI(TAG, "Client sends data to the server...");
- int len = socket_send(TAG, sock, payload, strlen(payload));
- if (len < 0) {
- ESP_LOGE(TAG, "Error occurred during socket_send");
- goto error;
- }
- ESP_LOGI(TAG, "Written: %.*s", len, payload);
- // Keep receiving until we have a reply
- do {
- len = try_receive(TAG, sock, rx_buffer, sizeof(rx_buffer));
- if (len < 0) {
- ESP_LOGE(TAG, "Error occurred during try_receive");
- goto error;
- }
- vTaskDelay(pdMS_TO_TICKS(YIELD_TO_ALL_MS));
- } while (len == 0);
- ESP_LOGI(TAG, "Received: %.*s", len, rx_buffer);
- error:
- if (sock != INVALID_SOCK) {
- close(sock);
- }
- free(address_info);
- vTaskDelete(NULL);
- }
- #endif // CONFIG_EXAMPLE_TCP_CLIENT
- #ifdef CONFIG_EXAMPLE_TCP_SERVER
- /**
- * @brief Returns the string representation of client's address (accepted on this server)
- */
- static inline char* get_clients_address(struct sockaddr_storage *source_addr)
- {
- static char address_str[128];
- char *res = NULL;
- // Convert ip address to string
- if (source_addr->ss_family == PF_INET) {
- res = inet_ntoa_r(((struct sockaddr_in *)source_addr)->sin_addr, address_str, sizeof(address_str) - 1);
- }
- #ifdef CONFIG_LWIP_IPV6
- else if (source_addr->ss_family == PF_INET6) {
- res = inet6_ntoa_r(((struct sockaddr_in6 *)source_addr)->sin6_addr, address_str, sizeof(address_str) - 1);
- }
- #endif
- if (!res) {
- address_str[0] = '\0'; // Returns empty string if conversion didn't succeed
- }
- return address_str;
- }
- static void tcp_server_task(void *pvParameters)
- {
- static char rx_buffer[128];
- static const char *TAG = "nonblocking-socket-server";
- xSemaphoreHandle *server_ready = pvParameters;
- struct addrinfo hints = { .ai_socktype = SOCK_STREAM };
- struct addrinfo *address_info;
- int listen_sock = INVALID_SOCK;
- const size_t max_socks = CONFIG_LWIP_MAX_SOCKETS - 1;
- static int sock[CONFIG_LWIP_MAX_SOCKETS - 1];
- // Prepare a list of file descriptors to hold client's sockets, mark all of them as invalid, i.e. available
- for (int i=0; i<max_socks; ++i) {
- sock[i] = INVALID_SOCK;
- }
- // Translating the hostname or a string representation of an IP to address_info
- int res = getaddrinfo(CONFIG_EXAMPLE_TCP_SERVER_BIND_ADDRESS, CONFIG_EXAMPLE_TCP_SERVER_BIND_PORT, &hints, &address_info);
- if (res != 0 || address_info == NULL) {
- ESP_LOGE(TAG, "couldn't get hostname for `%s` "
- "getaddrinfo() returns %d, addrinfo=%p", CONFIG_EXAMPLE_TCP_SERVER_BIND_ADDRESS, res, address_info);
- goto error;
- }
- // Creating a listener socket
- listen_sock = socket(address_info->ai_family, address_info->ai_socktype, address_info->ai_protocol);
- if (listen_sock < 0) {
- log_socket_error(TAG, listen_sock, errno, "Unable to create socket");
- goto error;
- }
- ESP_LOGI(TAG, "Listener socket created");
- // Marking the socket as non-blocking
- int flags = fcntl(listen_sock, F_GETFL);
- if (fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK) == -1) {
- log_socket_error(TAG, listen_sock, errno, "Unable to set socket non blocking");
- goto error;
- }
- ESP_LOGI(TAG, "Socket marked as non blocking");
- // Binding socket to the given address
- int err = bind(listen_sock, address_info->ai_addr, address_info->ai_addrlen);
- if (err != 0) {
- log_socket_error(TAG, listen_sock, errno, "Socket unable to bind");
- goto error;
- }
- ESP_LOGI(TAG, "Socket bound on %s:%s", CONFIG_EXAMPLE_TCP_SERVER_BIND_ADDRESS, CONFIG_EXAMPLE_TCP_SERVER_BIND_PORT);
- // Set queue (backlog) of pending connections to one (can be more)
- err = listen(listen_sock, 1);
- if (err != 0) {
- log_socket_error(TAG, listen_sock, errno, "Error occurred during listen");
- goto error;
- }
- ESP_LOGI(TAG, "Socket listening");
- xSemaphoreGive(*server_ready);
- // Main loop for accepting new connections and serving all connected clients
- while (1) {
- struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
- socklen_t addr_len = sizeof(source_addr);
- // Find a free socket
- int new_sock_index = 0;
- for (new_sock_index=0; new_sock_index<max_socks; ++new_sock_index) {
- if (sock[new_sock_index] == INVALID_SOCK) {
- break;
- }
- }
- // We accept a new connection only if we have a free socket
- if (new_sock_index < max_socks) {
- // Try to accept a new connections
- sock[new_sock_index] = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
- if (sock[new_sock_index] < 0) {
- if (errno == EWOULDBLOCK) { // The listener socket did not accepts any connection
- // continue to serve open connections and try to accept again upon the next iteration
- ESP_LOGV(TAG, "No pending connections...");
- } else {
- log_socket_error(TAG, listen_sock, errno, "Error when accepting connection");
- goto error;
- }
- } else {
- // We have a new client connected -> print it's address
- ESP_LOGI(TAG, "[sock=%d]: Connection accepted from IP:%s", sock[new_sock_index], get_clients_address(&source_addr));
- // ...and set the client's socket non-blocking
- flags = fcntl(sock[new_sock_index], F_GETFL);
- if (fcntl(sock[new_sock_index], F_SETFL, flags | O_NONBLOCK) == -1) {
- log_socket_error(TAG, sock[new_sock_index], errno, "Unable to set socket non blocking");
- goto error;
- }
- ESP_LOGI(TAG, "[sock=%d]: Socket marked as non blocking", sock[new_sock_index]);
- }
- }
- // We serve all the connected clients in this loop
- for (int i=0; i<max_socks; ++i) {
- if (sock[i] != INVALID_SOCK) {
- // This is an open socket -> try to serve it
- int len = try_receive(TAG, sock[i], rx_buffer, sizeof(rx_buffer));
- if (len < 0) {
- // Error occurred within this client's socket -> close and mark invalid
- ESP_LOGI(TAG, "[sock=%d]: try_receive() returned %d -> closing the socket", sock[i], len);
- close(sock[i]);
- sock[i] = INVALID_SOCK;
- } else if (len > 0) {
- // Received some data -> echo back
- ESP_LOGI(TAG, "[sock=%d]: Received %.*s", sock[i], len, rx_buffer);
- len = socket_send(TAG, sock[i], rx_buffer, len);
- if (len < 0) {
- // Error occurred on write to this socket -> close it and mark invalid
- ESP_LOGI(TAG, "[sock=%d]: socket_send() returned %d -> closing the socket", sock[i], len);
- close(sock[i]);
- sock[i] = INVALID_SOCK;
- } else {
- // Successfully echoed to this socket
- ESP_LOGI(TAG, "[sock=%d]: Written %.*s", sock[i], len, rx_buffer);
- }
- }
- } // one client's socket
- } // for all sockets
- // Yield to other tasks
- vTaskDelay(pdMS_TO_TICKS(YIELD_TO_ALL_MS));
- }
- error:
- if (listen_sock != INVALID_SOCK) {
- close(listen_sock);
- }
- for (int i=0; i<max_socks; ++i) {
- if (sock[i] != INVALID_SOCK) {
- close(sock[i]);
- }
- }
- free(address_info);
- vTaskDelete(NULL);
- }
- #endif // CONFIG_EXAMPLE_TCP_SERVER
- 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_TCP_SERVER
- xSemaphoreHandle server_ready = xSemaphoreCreateBinary();
- assert(server_ready);
- xTaskCreate(tcp_server_task, "tcp_server", 4096, &server_ready, 5, NULL);
- xSemaphoreTake(server_ready, portMAX_DELAY);
- vSemaphoreDelete(server_ready);
- #endif // CONFIG_EXAMPLE_TCP_SERVER
- #ifdef CONFIG_EXAMPLE_TCP_CLIENT
- xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
- #endif // CONFIG_EXAMPLE_TCP_CLIENT
- }
|