ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
tcp_client_link.cpp
Go to the documentation of this file.
1#include "tcp_client_link.h"
2
3#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
4
6#include "esphome/core/log.h"
7
8#include <algorithm>
9#include <cerrno>
10#include <cstring>
11
12namespace esphome::socket {
13
14// After this long in SYN, the stack's own retries are cut short.
15static constexpr uint32_t CONNECT_TIMEOUT_MS = 10000;
16
17// Non-blocking options and TCP keepalive for a bridged stream socket.
18// Keepalive is best-effort: the raw lwIP implementation (ESP8266, RP2040)
19// rejects it, so a half-open link there is only detected by a failed write.
20static void set_stream_options(Socket *sock, const char *tag) {
21 int yes = 1;
22 // Fails only on an invalid descriptor, or on raw lwIP after a peer reset that the next read() reports.
23 sock->setblocking(false);
24 int err = sock->setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
25 err |= sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
26#ifdef TCP_KEEPIDLE
27 int idle = 30;
28 int interval = 10;
29 int count = 3;
30 err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
31 err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
32 err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
33#endif
34 if (err != 0) {
35 ESP_LOGV(tag, "Nodelay/keepalive not fully applied");
36 }
37}
38
39void TcpClientLink::begin(const char *tag) {
40 this->tag_ = tag;
41 // The first attempt must not wait out a full interval.
43}
44
46 if (this->sock_ == nullptr) {
47 this->try_connect_();
48 return;
49 }
50 int err = 0;
51 switch (poll_connect(*this->sock_, err)) {
53 // Give up before the stack's SYN retries do, so the interval stays honest
54 // and the next attempt resolves the host again.
55 if (App.get_loop_component_start_time() - this->last_attempt_ms_ >=
56 std::max(this->reconnect_interval_ms_, CONNECT_TIMEOUT_MS)) {
57 this->drop_(LOG_STR("Connect failed"), ETIMEDOUT);
58 }
59 return;
61 this->drop_(LOG_STR("Connect failed"), err);
62 return;
63 default:
64 break;
65 }
66 this->connected_ = true;
67 ESP_LOGI(this->tag_, "Connected to %s:%u", this->host_.c_str(), this->port_);
68}
69
71 if (this->resolved_.consume_failure()) {
72 this->note_attempt();
73 return;
74 }
75 this->resolved_.start(this->host_.c_str(), this->port_, this->tag_);
76 if (!this->resolved_.ready()) {
77 return;
78 }
79 struct sockaddr_storage dest;
80 socklen_t dest_len =
81 this->resolved_.to_sockaddr(reinterpret_cast<struct sockaddr *>(&dest), sizeof(dest), this->port_);
82 if (dest_len == 0) {
83 this->note_attempt();
84 return;
85 }
86 this->sock_ = socket_loop_monitored(dest.ss_family, SOCK_STREAM, IPPROTO_TCP);
87 if (this->sock_ == nullptr) {
88 this->drop_(LOG_STR("Connect failed"), errno);
89 return;
90 }
91 set_stream_options(this->sock_.get(), this->tag_);
92 // Starts the pending-connect clock that poll() times out against.
93 this->note_attempt();
94 // An immediate success is reported by the next poll(); poll_connect() sees it writable.
95 if (this->sock_->connect(reinterpret_cast<struct sockaddr *>(&dest), dest_len) != 0 && errno != EINPROGRESS) {
96 this->drop_(LOG_STR("Connect failed"), errno);
97 }
98}
99
100void TcpClientLink::adopt(std::unique_ptr<Socket> sock) {
101 this->close();
102 set_stream_options(sock.get(), this->tag_);
103 this->sock_ = std::move(sock);
104 this->connected_ = true;
105}
106
107ssize_t TcpClientLink::read(uint8_t *buf, size_t len) {
108 if (!this->connected_) {
109 return 0;
110 }
111 ssize_t count = this->sock_->read(buf, len);
112 if (count > 0) {
113 return count;
114 }
115 if (count == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
116 this->drop_(LOG_STR("Connection lost"), count == 0 ? 0 : errno);
117 return -1;
118 }
119 return 0;
120}
121
122ssize_t TcpClientLink::write_(const uint8_t *buf, size_t len) {
123 if (!this->connected_ || len == 0) {
124 return 0;
125 }
126 ssize_t sent = this->sock_->write(buf, len);
127 if (sent >= 0) {
128 return sent;
129 }
130 if (errno == EAGAIN || errno == EWOULDBLOCK) {
131 return 0;
132 }
133 this->drop_(LOG_STR("Connection lost"), errno);
134 return -1;
135}
136
137size_t TcpClientLink::queue(const uint8_t *data, size_t len) {
138 size_t room = this->tx_free();
139 if (len > room) {
140 len = room;
141 }
142 std::memcpy(this->tx_ + this->tx_len_, data, len);
143 this->tx_len_ += static_cast<uint16_t>(len);
144 return len;
145}
146
148 ssize_t sent = this->write_(this->tx_, this->tx_len_);
149 if (sent > 0) {
150 this->tx_len_ -= static_cast<uint16_t>(sent);
151 if (this->tx_len_ != 0) {
152 std::memmove(this->tx_, this->tx_ + sent, this->tx_len_);
153 }
154 }
155}
156
158 if (this->sock_ != nullptr) {
159 this->sock_->shutdown(SHUT_RDWR);
160 this->sock_->close();
161 this->sock_.reset();
162 }
163 this->connected_ = false;
164 this->tx_len_ = 0;
165 this->resolved_.forget();
166}
167
168void TcpClientLink::drop_(const LogString *what, int err) {
169 ESP_LOGW(this->tag_, "%s: %d", LOG_STR_ARG(what), err);
170 this->close();
171 this->note_attempt();
172}
173
174} // namespace esphome::socket
175
176#endif
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
constexpr const char * c_str() const
Definition string_ref.h:78
void start(const char *host, uint16_t port, const char *tag)
Resolve host; tag names the failure log.
bool consume_failure()
Drop a failed lookup so the next start() tries again.
socklen_t to_sockaddr(struct sockaddr *dest, socklen_t destlen, uint16_t port) const
Write the stored address into dest. Returns 0 until ready() is true.
void forget()
Drop the stored address so the next start() resolves again.
unsigned count
Definition gif.cpp:51
uint32_t socklen_t
Definition headers.h:109
__int64 ssize_t
Definition httplib.h:178
ConnectPollResult poll_connect(Socket &sock, int &err_out)
Poll a connect() that returned EINPROGRESS.
Definition socket.h:223
BSDSocketImpl Socket
Definition socket.h:36
std::unique_ptr< Socket > socket_loop_monitored(int domain, int type, int protocol)
Create a socket and monitor it for data in the main loop.
const void size_t len
Definition hal.h:68
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
sa_family_t ss_family
Definition headers.h:104