mirror of
https://github.com/esphome/esphome.git
synced 2026-02-11 03:57:34 -07:00
Compare commits
11 Commits
socket_rea
...
api-server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
419ea723b8 | ||
|
|
a671f6ea85 | ||
|
|
0c62781539 | ||
|
|
e6c743ea67 | ||
|
|
4c006d98af | ||
|
|
c08726036e | ||
|
|
d602a2e5e4 | ||
|
|
dcab12adae | ||
|
|
fb714636e3 | ||
|
|
05a431ea54 | ||
|
|
1a34b4e7d7 |
@@ -117,37 +117,7 @@ void APIServer::setup() {
|
|||||||
void APIServer::loop() {
|
void APIServer::loop() {
|
||||||
// Accept new clients only if the socket exists and has incoming connections
|
// Accept new clients only if the socket exists and has incoming connections
|
||||||
if (this->socket_ && this->socket_->ready()) {
|
if (this->socket_ && this->socket_->ready()) {
|
||||||
while (true) {
|
this->accept_new_connections_();
|
||||||
struct sockaddr_storage source_addr;
|
|
||||||
socklen_t addr_len = sizeof(source_addr);
|
|
||||||
|
|
||||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
|
||||||
if (!sock)
|
|
||||||
break;
|
|
||||||
|
|
||||||
char peername[socket::SOCKADDR_STR_LEN];
|
|
||||||
sock->getpeername_to(peername);
|
|
||||||
|
|
||||||
// Check if we're at the connection limit
|
|
||||||
if (this->clients_.size() >= this->max_connections_) {
|
|
||||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
|
|
||||||
// Immediately close - socket destructor will handle cleanup
|
|
||||||
sock.reset();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Accept %s", peername);
|
|
||||||
|
|
||||||
auto *conn = new APIConnection(std::move(sock), this);
|
|
||||||
this->clients_.emplace_back(conn);
|
|
||||||
conn->start();
|
|
||||||
|
|
||||||
// First client connected - clear warning and update timestamp
|
|
||||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
|
||||||
this->status_clear_warning();
|
|
||||||
this->last_connected_ = App.get_loop_component_start_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->clients_.empty()) {
|
if (this->clients_.empty()) {
|
||||||
@@ -178,46 +148,84 @@ void APIServer::loop() {
|
|||||||
while (client_index < this->clients_.size()) {
|
while (client_index < this->clients_.size()) {
|
||||||
auto &client = this->clients_[client_index];
|
auto &client = this->clients_[client_index];
|
||||||
|
|
||||||
if (!client->flags_.remove) {
|
if (client->flags_.remove) {
|
||||||
|
// Rare case: handle disconnection (don't increment - swapped element needs processing)
|
||||||
|
this->remove_client_(client_index);
|
||||||
|
} else {
|
||||||
// Common case: process active client
|
// Common case: process active client
|
||||||
client->loop();
|
client->loop();
|
||||||
client_index++;
|
client_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServer::remove_client_(size_t client_index) {
|
||||||
|
auto &client = this->clients_[client_index];
|
||||||
|
|
||||||
|
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||||
|
this->unregister_active_action_calls_for_connection(client.get());
|
||||||
|
#endif
|
||||||
|
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
|
||||||
|
|
||||||
|
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||||
|
// Save client info before closing socket and removal for the trigger
|
||||||
|
char peername_buf[socket::SOCKADDR_STR_LEN];
|
||||||
|
std::string client_name(client->get_name());
|
||||||
|
std::string client_peername(client->get_peername_to(peername_buf));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Close socket now (was deferred from on_fatal_error to allow getpeername)
|
||||||
|
client->helper_->close();
|
||||||
|
|
||||||
|
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||||
|
if (client_index < this->clients_.size() - 1) {
|
||||||
|
std::swap(this->clients_[client_index], this->clients_.back());
|
||||||
|
}
|
||||||
|
this->clients_.pop_back();
|
||||||
|
|
||||||
|
// Last client disconnected - set warning and start tracking for reboot timeout
|
||||||
|
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->last_connected_ = App.get_loop_component_start_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||||
|
// Fire trigger after client is removed so api.connected reflects the true state
|
||||||
|
this->client_disconnected_trigger_.trigger(client_name, client_peername);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServer::accept_new_connections_() {
|
||||||
|
while (true) {
|
||||||
|
struct sockaddr_storage source_addr;
|
||||||
|
socklen_t addr_len = sizeof(source_addr);
|
||||||
|
|
||||||
|
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||||
|
if (!sock)
|
||||||
|
break;
|
||||||
|
|
||||||
|
char peername[socket::SOCKADDR_STR_LEN];
|
||||||
|
sock->getpeername_to(peername);
|
||||||
|
|
||||||
|
// Check if we're at the connection limit
|
||||||
|
if (this->clients_.size() >= this->max_connections_) {
|
||||||
|
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
|
||||||
|
// Immediately close - socket destructor will handle cleanup
|
||||||
|
sock.reset();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rare case: handle disconnection
|
ESP_LOGD(TAG, "Accept %s", peername);
|
||||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
|
||||||
this->unregister_active_action_calls_for_connection(client.get());
|
|
||||||
#endif
|
|
||||||
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
|
|
||||||
|
|
||||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
auto *conn = new APIConnection(std::move(sock), this);
|
||||||
// Save client info before closing socket and removal for the trigger
|
this->clients_.emplace_back(conn);
|
||||||
char peername_buf[socket::SOCKADDR_STR_LEN];
|
conn->start();
|
||||||
std::string client_name(client->get_name());
|
|
||||||
std::string client_peername(client->get_peername_to(peername_buf));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Close socket now (was deferred from on_fatal_error to allow getpeername)
|
// First client connected - clear warning and update timestamp
|
||||||
client->helper_->close();
|
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||||
|
this->status_clear_warning();
|
||||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
|
||||||
if (client_index < this->clients_.size() - 1) {
|
|
||||||
std::swap(this->clients_[client_index], this->clients_.back());
|
|
||||||
}
|
|
||||||
this->clients_.pop_back();
|
|
||||||
|
|
||||||
// Last client disconnected - set warning and start tracking for reboot timeout
|
|
||||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
|
||||||
this->status_set_warning();
|
|
||||||
this->last_connected_ = App.get_loop_component_start_time();
|
this->last_connected_ = App.get_loop_component_start_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
|
||||||
// Fire trigger after client is removed so api.connected reflects the true state
|
|
||||||
this->client_disconnected_trigger_.trigger(client_name, client_peername);
|
|
||||||
#endif
|
|
||||||
// Don't increment client_index since we need to process the swapped element
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,11 @@ class APIServer : public Component,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Accept incoming socket connections. Only called when socket has pending connections.
|
||||||
|
void __attribute__((noinline)) accept_new_connections_();
|
||||||
|
// Remove a disconnected client by index. Swaps with last element and pops.
|
||||||
|
void __attribute__((noinline)) remove_client_(size_t client_index);
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||||
const psk_t &active_psk, bool make_active);
|
const psk_t &active_psk, bool make_active);
|
||||||
|
|||||||
@@ -16,13 +16,19 @@ namespace esphome::socket {
|
|||||||
|
|
||||||
class BSDSocketImpl final : public Socket {
|
class BSDSocketImpl final : public Socket {
|
||||||
public:
|
public:
|
||||||
BSDSocketImpl(int fd, bool monitor_loop = false) {
|
BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) {
|
||||||
this->fd_ = fd;
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
// Register new socket with the application for select() if monitoring requested
|
// Register new socket with the application for select() if monitoring requested
|
||||||
if (monitor_loop && this->fd_ >= 0) {
|
if (monitor_loop && this->fd_ >= 0) {
|
||||||
// Only set loop_monitored_ to true if registration succeeds
|
// Only set loop_monitored_ to true if registration succeeds
|
||||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||||
|
} else {
|
||||||
|
this->loop_monitored_ = false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Without select support, ignore monitor_loop parameter
|
||||||
|
(void) monitor_loop;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
~BSDSocketImpl() override {
|
~BSDSocketImpl() override {
|
||||||
if (!this->closed_) {
|
if (!this->closed_) {
|
||||||
@@ -46,10 +52,12 @@ class BSDSocketImpl final : public Socket {
|
|||||||
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); }
|
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); }
|
||||||
int close() override {
|
int close() override {
|
||||||
if (!this->closed_) {
|
if (!this->closed_) {
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
// Unregister from select() before closing if monitored
|
// Unregister from select() before closing if monitored
|
||||||
if (this->loop_monitored_) {
|
if (this->loop_monitored_) {
|
||||||
App.unregister_socket_fd(this->fd_);
|
App.unregister_socket_fd(this->fd_);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
int ret = ::close(this->fd_);
|
int ret = ::close(this->fd_);
|
||||||
this->closed_ = true;
|
this->closed_ = true;
|
||||||
return ret;
|
return ret;
|
||||||
@@ -122,6 +130,23 @@ class BSDSocketImpl final : public Socket {
|
|||||||
::fcntl(this->fd_, F_SETFL, fl);
|
::fcntl(this->fd_, F_SETFL, fl);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_fd() const override { return this->fd_; }
|
||||||
|
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
|
bool ready() const override {
|
||||||
|
if (!this->loop_monitored_)
|
||||||
|
return true;
|
||||||
|
return App.is_socket_ready(this->fd_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int fd_;
|
||||||
|
bool closed_{false};
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
|
bool loop_monitored_{false};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to create a socket with optional monitoring
|
// Helper to create a socket with optional monitoring
|
||||||
|
|||||||
@@ -452,8 +452,6 @@ class LWIPRawImpl : public Socket {
|
|||||||
errno = ENOSYS;
|
errno = ENOSYS;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bool ready() const override { return this->rx_buf_ != nullptr || this->rx_closed_ || this->pcb_ == nullptr; }
|
|
||||||
|
|
||||||
int setblocking(bool blocking) final {
|
int setblocking(bool blocking) final {
|
||||||
if (pcb_ == nullptr) {
|
if (pcb_ == nullptr) {
|
||||||
errno = ECONNRESET;
|
errno = ECONNRESET;
|
||||||
@@ -578,8 +576,6 @@ class LWIPRawListenImpl final : public LWIPRawImpl {
|
|||||||
tcp_err(pcb_, LWIPRawImpl::s_err_fn); // Use base class error handler
|
tcp_err(pcb_, LWIPRawImpl::s_err_fn); // Use base class error handler
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ready() const override { return this->accepted_socket_count_ > 0; }
|
|
||||||
|
|
||||||
std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
|
std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||||
if (pcb_ == nullptr) {
|
if (pcb_ == nullptr) {
|
||||||
errno = EBADF;
|
errno = EBADF;
|
||||||
|
|||||||
@@ -11,13 +11,19 @@ namespace esphome::socket {
|
|||||||
|
|
||||||
class LwIPSocketImpl final : public Socket {
|
class LwIPSocketImpl final : public Socket {
|
||||||
public:
|
public:
|
||||||
LwIPSocketImpl(int fd, bool monitor_loop = false) {
|
LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) {
|
||||||
this->fd_ = fd;
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
// Register new socket with the application for select() if monitoring requested
|
// Register new socket with the application for select() if monitoring requested
|
||||||
if (monitor_loop && this->fd_ >= 0) {
|
if (monitor_loop && this->fd_ >= 0) {
|
||||||
// Only set loop_monitored_ to true if registration succeeds
|
// Only set loop_monitored_ to true if registration succeeds
|
||||||
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
this->loop_monitored_ = App.register_socket_fd(this->fd_);
|
||||||
|
} else {
|
||||||
|
this->loop_monitored_ = false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Without select support, ignore monitor_loop parameter
|
||||||
|
(void) monitor_loop;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
~LwIPSocketImpl() override {
|
~LwIPSocketImpl() override {
|
||||||
if (!this->closed_) {
|
if (!this->closed_) {
|
||||||
@@ -43,10 +49,12 @@ class LwIPSocketImpl final : public Socket {
|
|||||||
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); }
|
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); }
|
||||||
int close() override {
|
int close() override {
|
||||||
if (!this->closed_) {
|
if (!this->closed_) {
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
// Unregister from select() before closing if monitored
|
// Unregister from select() before closing if monitored
|
||||||
if (this->loop_monitored_) {
|
if (this->loop_monitored_) {
|
||||||
App.unregister_socket_fd(this->fd_);
|
App.unregister_socket_fd(this->fd_);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
int ret = lwip_close(this->fd_);
|
int ret = lwip_close(this->fd_);
|
||||||
this->closed_ = true;
|
this->closed_ = true;
|
||||||
return ret;
|
return ret;
|
||||||
@@ -89,6 +97,23 @@ class LwIPSocketImpl final : public Socket {
|
|||||||
lwip_fcntl(this->fd_, F_SETFL, fl);
|
lwip_fcntl(this->fd_, F_SETFL, fl);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_fd() const override { return this->fd_; }
|
||||||
|
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
|
bool ready() const override {
|
||||||
|
if (!this->loop_monitored_)
|
||||||
|
return true;
|
||||||
|
return App.is_socket_ready(this->fd_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int fd_;
|
||||||
|
bool closed_{false};
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
|
bool loop_monitored_{false};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to create a socket with optional monitoring
|
// Helper to create a socket with optional monitoring
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ namespace esphome::socket {
|
|||||||
|
|
||||||
Socket::~Socket() {}
|
Socket::~Socket() {}
|
||||||
|
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
|
||||||
bool Socket::ready() const { return !this->loop_monitored_ || App.is_socket_ready_(this->fd_); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Platform-specific inet_ntop wrappers
|
// Platform-specific inet_ntop wrappers
|
||||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP)
|
#if defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||||
// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value
|
// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value
|
||||||
|
|||||||
@@ -63,29 +63,13 @@ class Socket {
|
|||||||
virtual int setblocking(bool blocking) = 0;
|
virtual int setblocking(bool blocking) = 0;
|
||||||
virtual int loop() { return 0; };
|
virtual int loop() { return 0; };
|
||||||
|
|
||||||
/// Get the underlying file descriptor (returns -1 if not supported)
|
/// Get the underlying file descriptor (returns -1 if not supported)
|
||||||
/// Non-virtual: only one socket implementation is active per build.
|
virtual int get_fd() const { return -1; }
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
|
||||||
int get_fd() const { return this->fd_; }
|
|
||||||
#else
|
|
||||||
int get_fd() const { return -1; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Check if socket has data ready to read
|
/// Check if socket has data ready to read
|
||||||
/// For select()-based sockets: non-virtual, checks Application's select() results
|
/// For loop-monitored sockets, checks with the Application's select() results
|
||||||
/// For LWIP raw TCP sockets: virtual, checks internal buffer state
|
/// For non-monitored sockets, always returns true (assumes data may be available)
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
|
||||||
bool ready() const;
|
|
||||||
#else
|
|
||||||
virtual bool ready() const { return true; }
|
virtual bool ready() const { return true; }
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
|
||||||
int fd_{-1};
|
|
||||||
bool closed_{false};
|
|
||||||
bool loop_monitored_{false};
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a socket of the given domain, type and protocol.
|
/// Create a socket of the given domain, type and protocol.
|
||||||
|
|||||||
@@ -605,6 +605,15 @@ void Application::unregister_socket_fd(int fd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::is_socket_ready(int fd) const {
|
||||||
|
// This function is thread-safe for reading the result of select()
|
||||||
|
// However, it should only be called after select() has been executed in the main loop
|
||||||
|
// The read_fds_ is only modified by select() in the main loop
|
||||||
|
if (fd < 0 || fd >= FD_SETSIZE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return FD_ISSET(fd, &this->read_fds_);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Application::yield_with_select_(uint32_t delay_ms) {
|
void Application::yield_with_select_(uint32_t delay_ms) {
|
||||||
|
|||||||
@@ -101,10 +101,6 @@
|
|||||||
#include "esphome/components/update/update_entity.h"
|
#include "esphome/components/update/update_entity.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome::socket {
|
|
||||||
class Socket;
|
|
||||||
} // namespace esphome::socket
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
// Teardown timeout constant (in milliseconds)
|
// Teardown timeout constant (in milliseconds)
|
||||||
@@ -495,8 +491,7 @@ class Application {
|
|||||||
void unregister_socket_fd(int fd);
|
void unregister_socket_fd(int fd);
|
||||||
/// Check if there's data available on a socket without blocking
|
/// Check if there's data available on a socket without blocking
|
||||||
/// This function is thread-safe for reading, but should be called after select() has run
|
/// This function is thread-safe for reading, but should be called after select() has run
|
||||||
/// The read_fds_ is only modified by select() in the main loop
|
bool is_socket_ready(int fd) const;
|
||||||
bool is_socket_ready(int fd) const { return fd >= 0 && this->is_socket_ready_(fd); }
|
|
||||||
|
|
||||||
#ifdef USE_WAKE_LOOP_THREADSAFE
|
#ifdef USE_WAKE_LOOP_THREADSAFE
|
||||||
/// Wake the main event loop from a FreeRTOS task
|
/// Wake the main event loop from a FreeRTOS task
|
||||||
@@ -508,15 +503,6 @@ class Application {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend Component;
|
friend Component;
|
||||||
friend class socket::Socket;
|
|
||||||
|
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
|
||||||
/// Fast path for Socket::ready() via friendship - skips negative fd check.
|
|
||||||
/// Safe because: fd was validated in register_socket_fd() at registration time,
|
|
||||||
/// and Socket::ready() only calls this when loop_monitored_ is true (registration succeeded).
|
|
||||||
/// FD_ISSET may include its own upper bounds check depending on platform.
|
|
||||||
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void register_component_(Component *comp);
|
void register_component_(Component *comp);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user