TCP Sockets

TCP sockets provide reliable, ordered communication over IP network sockets and have built-in protocols for handling packet acknowledgement as well as transmission speed / bandwidth adjustment.

TCP sockets cannot be used with multicast (many to one, one to many).

The implementation supports both client-style request/response flows and server-style bind(...) / listen(...) / accept() loops. close() and reinit() are used by the updated teardown and reconnect paths to ensure connection state is reset cleanly.

API Reference

Header File

Classes

class TcpSocket : public espp::Socket

Class for managing sending and receiving data using TCP/IP. Can be used to create client or server sockets.

TCP Client Example

    espp::TcpSocket client_socket({.log_level = espp::Logger::Verbosity::WARN});
    if (!client_socket.connect({.ip_address = kLoopbackAddress, .port = port})) {
      server_socket.close();
      server_task->stop();
      return fail("TCP unicast teardown", "client failed to connect");
    }
    auto client_task = espp::Task::make_unique({
        .callback = [&client_socket](std::mutex &m, std::condition_variable &cv,
                                     bool &notified) -> bool {
          static size_t iterations = 0;
          auto data = make_payload(24, static_cast<uint8_t>(iterations * 5));
          client_socket.transmit(data);
          iterations++;
          return wait_or_stop(m, cv, notified);
        },
        .task_config = make_task_config("TcpClient", 5 * 1024),
    });
    client_task->start();

TCP Server Example

    espp::TcpSocket server_socket({.log_level = espp::Logger::Verbosity::WARN});
    if (!server_socket.bind(port) || !server_socket.listen(kMaxConnections)) {
      return fail("TCP unicast teardown", "failed to bind/listen");
    }
    auto server_task = espp::Task::make_unique({
        .callback = [&server_socket, &received_messages](std::mutex &m, std::condition_variable &cv,
                                                         bool &notified) -> bool {
          static std::unique_ptr<espp::TcpSocket> client_socket;
          if (!client_socket) {
            client_socket = server_socket.accept();
            if (!client_socket) {
              return wait_or_stop(m, cv, notified, 10ms);
            }
          }

          ByteVector data;
          if (client_socket->receive(data, kMaxPacketSize)) {
            received_messages++;
            fmt::print("TCP server received {} bytes\n", data.size());
          } else if (!client_socket->is_connected()) {
            client_socket.reset();
          }
          return wait_or_stop(m, cv, notified, 10ms);
        },
        .task_config = make_task_config("TcpServer"),
    });
    server_task->start();

TCP Client Response Example

    auto exchange_once = [port](uint8_t seed, ByteVector &response) -> bool {
      espp::TcpSocket client_socket({.log_level = espp::Logger::Verbosity::WARN});
      if (!client_socket.connect({.ip_address = kLoopbackAddress, .port = port})) {
        return false;
      }
      auto request = make_payload(40, seed);
      auto transmit_config = espp::TcpSocket::TransmitConfig{
          .wait_for_response = true,
          .response_size = kMaxPacketSize,
          .on_response_callback =
              [&response](const auto &received_response) { response = received_response; },
          .response_timeout = 500ms,
      };
      return client_socket.transmit(request, transmit_config);
    };

TCP Server Response Example

    espp::TcpSocket server_socket({.log_level = espp::Logger::Verbosity::WARN});
    if (!server_socket.bind(port) || !server_socket.listen(kMaxConnections)) {
      return fail("TCP response/reconnect", "failed to bind/listen");
    }
    auto server_task = espp::Task::make_unique({
        .callback = [&server_socket, &accepted_connections](
                        std::mutex &m, std::condition_variable &cv, bool &notified) -> bool {
          static std::unique_ptr<espp::TcpSocket> client_socket;
          if (!client_socket) {
            client_socket = server_socket.accept();
            if (client_socket) {
              accepted_connections++;
            } else {
              return wait_or_stop(m, cv, notified, 10ms);
            }
          }

          ByteVector data;
          if (client_socket->receive(data, kMaxPacketSize)) {
            client_socket->transmit(reversed(data));
          } else if (!client_socket->is_connected()) {
            client_socket.reset();
          }
          return wait_or_stop(m, cv, notified, 10ms);
        },
        .task_config = make_task_config("TcpResponseServer"),
    });
    server_task->start();
    auto stop_server = [&server_socket, &server_task]() {
      server_socket.close();
      server_task->stop();
    };

Public Types

typedef std::function<std::optional<std::vector<uint8_t>>(std::vector<uint8_t> &data, const Info &sender_info)> receive_callback_fn

Callback function to be called when receiving data from a client.

Param data:

Byte array of data received from client

Param sender_info:

Sender information (address, port)

Return:

std::optional<std::vector<uint8_t>> optional data to return to sender.

typedef std::function<void(std::vector<uint8_t> &data)> response_callback_fn

Callback function to be called with data returned after transmitting data to a server.

Param data:

The data that the server responded with

Public Functions

explicit TcpSocket(const espp::TcpSocket::Config &config)

Initialize the socket and associated resources.

Note

Enables keepalive on the socket.

Parameters:

configConfig for the socket.

~TcpSocket()

Tear down any resources associted with the socket.

void reinit()

Reinitialize the socket, cleaning it up if first it is already initalized.

void close()

Close the socket.

bool is_connected() const

Check if the socket is connected to a remote endpoint.

Returns:

true if the socket is connected to a remote endpoint.

bool connect(const espp::TcpSocket::ConnectConfig &connect_config)

Open a connection to the remote TCP server.

Parameters:

connect_configConnectConfig struct describing the server endpoint.

Returns:

true if the client successfully connected to the server.

const espp::Socket::Info &get_remote_info() const

Get the remote endpoint info.

Returns:

The remote endpoint info.

bool transmit(const std::vector<uint8_t> &data, const espp::TcpSocket::TransmitConfig &transmit_config = espp::TcpSocket::TransmitConfig::Default())

Send data to the endpoint already connected to by TcpSocket::connect. Can be configured to block waiting for a response from the remote.

If response is requested, a callback can be provided in send_config which will be provided the response data for processing.

Parameters:
  • data – vector of bytes to send to the remote endpoint.

  • transmit_configTransmitConfig struct indicating whether to wait for a response.

Returns:

true if the data was sent, false otherwise.

bool transmit(const std::vector<char> &data, const espp::TcpSocket::TransmitConfig &transmit_config = espp::TcpSocket::TransmitConfig::Default())

Send data to the endpoint already connected to by TcpSocket::connect. Can be configured to block waiting for a response from the remote.

If response is requested, a callback can be provided in send_config which will be provided the response data for processing.

Parameters:
  • data – vector of bytes to send to the remote endpoint.

  • transmit_configTransmitConfig struct indicating whether to wait for a response.

Returns:

true if the data was sent, false otherwise.

bool transmit(std::string_view data, const espp::TcpSocket::TransmitConfig &transmit_config = espp::TcpSocket::TransmitConfig::Default())

Send data to the endpoint already connected to by TcpSocket::connect. Can be configured to block waiting for a response from the remote.

If response is requested, a callback can be provided in send_config which will be provided the response data for processing.

Parameters:
  • data – string view of bytes to send to the remote endpoint.

  • transmit_configTransmitConfig struct indicating whether to wait for a response.

Returns:

true if the data was sent, false otherwise.

bool transmit(std::span<const uint8_t> data, const espp::TcpSocket::TransmitConfig &transmit_config = espp::TcpSocket::TransmitConfig::Default())

Send data to the endpoint already connected to by TcpSocket::connect. Can be configured to block waiting for a response from the remote.

If response is requested, a callback can be provided in send_config which will be provided the response data for processing.

Parameters:
  • data – span of bytes to send to the remote endpoint.

  • transmit_configTransmitConfig struct indicating whether to wait for a response.

Returns:

true if the data was sent, false otherwise.

bool receive(std::vector<uint8_t> &data, size_t max_num_bytes)

Call read on the socket, assuming it has already been configured appropriately.

Parameters:
  • data – Vector of bytes of received data.

  • max_num_bytes – Maximum number of bytes to receive.

Returns:

true if successfully received, false otherwise.

size_t receive(uint8_t *data, size_t max_num_bytes)

Call read on the socket, assuming it has already been configured appropriately.

Note

This function will block until max_num_bytes are received or the receive timeout is reached.

Note

The data pointed to by data must be at least max_num_bytes in size.

Parameters:
  • data – Pointer to buffer to receive data.

  • max_num_bytes – Maximum number of bytes to receive.

Returns:

Number of bytes received.

bool bind(int port)

Bind the socket as a server on port.

Parameters:

port – The port to which to bind the socket.

Returns:

true if the socket was bound.

bool listen(int max_pending_connections)

Listen for incoming client connections.

See also

bind

See also

accept

Note

Must be called after bind and before accept.

Parameters:

max_pending_connections – Max number of allowed pending connections.

Returns:

True if socket was able to start listening.

std::unique_ptr<espp::TcpSocket> accept()

Accept an incoming connection.

Note

Blocks until a connection is accepted.

Note

Must be called after listen.

Note

This function will block until a connection is accepted.

Returns:

A unique pointer to a TcpClientSession if a connection was accepted, nullptr otherwise.

bool is_valid() const

Is the socket valid.

Returns:

true if the socket file descriptor is >= 0.

std::optional<Info> get_ipv4_info()

Get the Socket::Info for the socket.

This will call getsockname() on the socket to get the sockaddr_storage structure, and then fill out the Socket::Info structure.

Returns:

Socket::Info for the socket.

bool set_receive_timeout(const std::chrono::duration<float> &timeout)

Set the receive timeout on the provided socket.

Parameters:

timeout – requested timeout, must be > 0.

Returns:

true if SO_RECVTIMEO was successfully set.

bool enable_reuse()

Allow others to use this address/port combination after we’re done with it.

Returns:

true if SO_REUSEADDR and SO_REUSEPORT were successfully set.

bool make_multicast(uint8_t time_to_live = 1, uint8_t loopback_enabled = true)

Configure the socket to be multicast (if time_to_live > 0). Sets the IP_MULTICAST_TTL (number of multicast hops allowed) and optionally configures whether this node should receive its own multicast packets (IP_MULTICAST_LOOP).

Parameters:
  • time_to_live – number of multicast hops allowed (TTL).

  • loopback_enabled – Whether to receive our own multicast packets.

Returns:

true if IP_MULTICAST_TTL and IP_MULTICAST_LOOP were set.

bool add_multicast_group(const std::string &multicast_group)

If this is a server socket, add it to the provided the multicast group.

See https://en.wikipedia.org/wiki/Multicast_address for more information.

Note

Multicast groups must be Class D addresses (224.0.0.0 to 239.255.255.255)

Parameters:

multicast_group – multicast group to join.

Returns:

true if IP_ADD_MEMBERSHIP was successfully set.

int select(const std::chrono::microseconds &timeout)

Select on the socket for read events.

Parameters:

timeout – how long to wait for an event.

Returns:

number of events that occurred.

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Functions

static bool is_valid_fd(sock_type_t socket_fd)

Is the socket valid.

Parameters:

socket_fd – Socket file descriptor.

Returns:

true if the socket file descriptor is >= 0.

struct Config

Config struct for the TCP socket.

Public Members

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Verbosity level for the TCP socket logger.

struct ConnectConfig

Config struct for connecting to a remote TCP server.

Public Members

std::string ip_address

Address to send data to.

size_t port

Port number to send data to.

struct TransmitConfig

Config struct for sending data to a remote TCP socket.

Note

This is only used when waiting for a response from the remote.

Public Members

bool wait_for_response = false

Whether to wait for a response from the remote or not.

size_t response_size = 0

If waiting for a response, this is the maximum size response we will receive.

espp::Socket::response_callback_fn on_response_callback = nullptr

If waiting for a response, this is an optional handler which is provided the response data.

std::chrono::duration<float> response_timeout = std::chrono::duration<float>(0.5f)

If waiting for a response, this is the maximum timeout to wait.