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).

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({});
  client_socket.connect({.ip_address = server_address, .port = port});
  // create thread for sending data using the socket
  auto client_task_fn = [&server_address, &client_socket, &port](auto &, auto &) {
    static size_t iterations = 0;
    std::vector<uint8_t> data{0, 1, 2, 3, 4};
    std::transform(data.begin(), data.end(), data.begin(),
                   [](const auto &d) { return d + iterations; });
    client_socket.transmit(data);
    iterations++;
    std::this_thread::sleep_for(1s);
    // don't want to stop the task
    return false;
  };
  auto client_task = espp::Task::make_unique(
      {.callback = client_task_fn,
       .task_config = {.name = "Client Task", .stack_size_bytes = 5 * 1024}});
  client_task->start();

TCP Server Example

  std::string server_address = "127.0.0.1";
  size_t port = 5000;
  int max_connections = 1;
  espp::TcpSocket server_socket({.log_level = espp::Logger::Verbosity::WARN});
  server_socket.bind(port);
  server_socket.listen(max_connections);
  auto server_task_fn = [&server_socket](auto &m, auto &cv) -> bool {
    static std::unique_ptr<espp::TcpSocket> client_socket;
    if (!client_socket) {
      client_socket = server_socket.accept();
      if (client_socket) {
        auto info = client_socket->get_remote_info();
        fmt::print("Server accepted connection from: {}\n", info);
      }
    }
    if (client_socket) {
      std::vector<uint8_t> data;
      size_t max_receive_size = 1024;
      if (client_socket->receive(data, max_receive_size)) {
        fmt::print("Server received: {}\n", data);
      }
    }
    {
      std::unique_lock<std::mutex> lock(m);
      cv.wait_for(lock, 10ms);
    }
    // don't want to stop the task
    return false;
  };

  auto server_task_config = espp::Task::Config{
      .callback = server_task_fn,
      .task_config =
          {
              .name = "TcpServer",
              .stack_size_bytes = 6 * 1024,
          },
  };
  auto server_task = espp::Task::make_unique(server_task_config);
  server_task->start();

TCP Client Response Example

  espp::TcpSocket client_socket({.log_level = espp::Logger::Verbosity::WARN});
  client_socket.connect({
      .ip_address = server_address,
      .port = port,
  });
  // create threads
  auto client_task_fn = [&server_address, &client_socket, &port](auto &, auto &) {
    static size_t iterations = 0;
    std::vector<uint8_t> data{0, 1, 2, 3, 4};
    std::transform(data.begin(), data.end(), data.begin(),
                   [](const auto &d) { return d + iterations; });
    auto transmit_config = espp::TcpSocket::TransmitConfig{
        .wait_for_response = true,
        .response_size = 128,
        .on_response_callback =
            [](auto &response) { fmt::print("Client received: {}\n", response); },
    };
    // NOTE: now this call blocks until the response is received
    client_socket.transmit(data, transmit_config);
    iterations++;
    std::this_thread::sleep_for(1s);
    // don't want to stop the task
    return false;
  };
  auto client_task = espp::Task::make_unique(
      {.callback = client_task_fn,
       .task_config = {.name = "Client Task", .stack_size_bytes = 5 * 1024}});
  client_task->start();

TCP Server Response Example

  std::string server_address = "127.0.0.1";
  size_t port = 5000;
  int max_connections = 1;
  espp::TcpSocket server_socket({.log_level = espp::Logger::Verbosity::WARN});
  server_socket.bind(port);
  server_socket.listen(max_connections);
  auto server_task_fn = [&server_socket](auto &m, auto &cv) -> bool {
    static std::unique_ptr<espp::TcpSocket> client_socket;
    if (!client_socket) {
      client_socket = server_socket.accept();
      if (client_socket) {
        auto info = client_socket->get_remote_info();
        fmt::print("Server accepted connection from: {}\n", info);
      }
    }
    if (client_socket) {
      std::vector<uint8_t> data;
      size_t max_receive_size = 1024;
      if (client_socket->receive(data, max_receive_size)) {
        fmt::print("Server received: {}\n", data);
        // reverse the data
        std::reverse(data.begin(), data.end());
        // and send it back
        client_socket->transmit(data);
      }
    }
    {
      std::unique_lock<std::mutex> lock(m);
      cv.wait_for(lock, 10ms);
    }
    // don't want to stop the task
    return false;
  };

  auto server_task_config = espp::Task::Config{
      .callback = server_task_fn,
      .task_config =
          {
              .name = "TcpServer",
              .stack_size_bytes = 6 * 1024,
          },
  };
  auto server_task = espp::Task::make_unique(server_task_config);
  server_task->start();

Public Types

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 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_fdSocket 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.