mirror of
https://github.com/gabime/spdlog.git
synced 2025-09-29 01:29:35 +08:00
Added timeout for TCP calls such as connect, send, recv (#3432)
* Now lets test on windows * I guess testing on windows passes. * Update tcp_client-windows.h Added default value to argument * Final edit * Update tcp_client-windows.h Changed improper misplaced includes.
This commit is contained in:
@@ -58,8 +58,81 @@ public:
|
|||||||
|
|
||||||
SOCKET fd() const { return socket_; }
|
SOCKET fd() const { return socket_; }
|
||||||
|
|
||||||
|
int connect_socket_with_timeout(SOCKET sockfd,
|
||||||
|
const struct sockaddr *addr,
|
||||||
|
int addrlen,
|
||||||
|
const timeval &tv) {
|
||||||
|
// If no timeout requested, do a normal blocking connect.
|
||||||
|
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||||
|
int rv = ::connect(sockfd, addr, addrlen);
|
||||||
|
if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to non‐blocking mode
|
||||||
|
u_long mode = 1UL;
|
||||||
|
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rv = ::connect(sockfd, addr, addrlen);
|
||||||
|
int last_error = WSAGetLastError();
|
||||||
|
if (rv == 0 || last_error == WSAEISCONN) {
|
||||||
|
mode = 0UL;
|
||||||
|
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (last_error != WSAEWOULDBLOCK) {
|
||||||
|
// Real error
|
||||||
|
mode = 0UL;
|
||||||
|
if (::ioctlsocket(sockfd, FIONBIO, &mode)) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until socket is writable or timeout expires
|
||||||
|
fd_set wfds;
|
||||||
|
FD_ZERO(&wfds);
|
||||||
|
FD_SET(sockfd, &wfds);
|
||||||
|
|
||||||
|
rv = ::select(0, nullptr, &wfds, nullptr, const_cast<timeval *>(&tv));
|
||||||
|
|
||||||
|
// Restore blocking mode regardless of select result
|
||||||
|
mode = 0UL;
|
||||||
|
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
WSASetLastError(WSAETIMEDOUT);
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
if (rv == SOCKET_ERROR) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int so_error = 0;
|
||||||
|
int len = sizeof(so_error);
|
||||||
|
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&so_error), &len) ==
|
||||||
|
SOCKET_ERROR) {
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
if (so_error != 0 && so_error != WSAEISCONN) {
|
||||||
|
// connection failed
|
||||||
|
WSASetLastError(so_error);
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // success
|
||||||
|
}
|
||||||
|
|
||||||
// try to connect or throw on failure
|
// try to connect or throw on failure
|
||||||
void connect(const std::string &host, int port) {
|
void connect(const std::string &host, int port, int timeout_ms = 0) {
|
||||||
if (is_connected()) {
|
if (is_connected()) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -71,6 +144,10 @@ public:
|
|||||||
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
|
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
|
||||||
hints.ai_protocol = 0;
|
hints.ai_protocol = 0;
|
||||||
|
|
||||||
|
timeval tv;
|
||||||
|
tv.tv_sec = timeout_ms / 1000;
|
||||||
|
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||||
|
|
||||||
auto port_str = std::to_string(port);
|
auto port_str = std::to_string(port);
|
||||||
struct addrinfo *addrinfo_result;
|
struct addrinfo *addrinfo_result;
|
||||||
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
|
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
|
||||||
@@ -82,7 +159,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try each address until we successfully connect(2).
|
// Try each address until we successfully connect(2).
|
||||||
|
|
||||||
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
|
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
|
||||||
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
if (socket_ == INVALID_SOCKET) {
|
if (socket_ == INVALID_SOCKET) {
|
||||||
@@ -90,18 +166,24 @@ public:
|
|||||||
WSACleanup();
|
WSACleanup();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) {
|
if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) {
|
||||||
|
last_error = 0;
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
last_error = ::WSAGetLastError();
|
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
|
last_error = WSAGetLastError();
|
||||||
|
::closesocket(socket_);
|
||||||
|
socket_ = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
::freeaddrinfo(addrinfo_result);
|
::freeaddrinfo(addrinfo_result);
|
||||||
if (socket_ == INVALID_SOCKET) {
|
if (socket_ == INVALID_SOCKET) {
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
throw_winsock_error_("connect failed", last_error);
|
throw_winsock_error_("connect failed", last_error);
|
||||||
}
|
}
|
||||||
|
if (timeout_ms > 0) {
|
||||||
|
DWORD tv = static_cast<DWORD>(timeout_ms);
|
||||||
|
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
|
||||||
|
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
|
||||||
|
}
|
||||||
|
|
||||||
// set TCP_NODELAY
|
// set TCP_NODELAY
|
||||||
int enable_flag = 1;
|
int enable_flag = 1;
|
||||||
|
@@ -39,8 +39,72 @@ public:
|
|||||||
|
|
||||||
~tcp_client() { close(); }
|
~tcp_client() { close(); }
|
||||||
|
|
||||||
|
int connect_socket_with_timeout(int sockfd,
|
||||||
|
const struct sockaddr *addr,
|
||||||
|
socklen_t addrlen,
|
||||||
|
const timeval &tv) {
|
||||||
|
// Blocking connect if timeout is zero
|
||||||
|
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||||
|
int rv = ::connect(sockfd, addr, addrlen);
|
||||||
|
if (rv < 0 && errno == EISCONN) {
|
||||||
|
// already connected, treat as success
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-blocking path
|
||||||
|
int orig_flags = ::fcntl(sockfd, F_GETFL, 0);
|
||||||
|
if (orig_flags < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rv = ::connect(sockfd, addr, addrlen);
|
||||||
|
if (rv == 0 || (rv < 0 && errno == EISCONN)) {
|
||||||
|
// immediate connect or already connected
|
||||||
|
::fcntl(sockfd, F_SETFL, orig_flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (errno != EINPROGRESS) {
|
||||||
|
::fcntl(sockfd, F_SETFL, orig_flags);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for writability
|
||||||
|
fd_set wfds;
|
||||||
|
FD_ZERO(&wfds);
|
||||||
|
FD_SET(sockfd, &wfds);
|
||||||
|
|
||||||
|
struct timeval tv_copy = tv;
|
||||||
|
rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy);
|
||||||
|
if (rv <= 0) {
|
||||||
|
// timeout or error
|
||||||
|
::fcntl(sockfd, F_SETFL, orig_flags);
|
||||||
|
if (rv == 0) errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check socket error
|
||||||
|
int so_error = 0;
|
||||||
|
socklen_t len = sizeof(so_error);
|
||||||
|
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
|
||||||
|
::fcntl(sockfd, F_SETFL, orig_flags);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
::fcntl(sockfd, F_SETFL, orig_flags);
|
||||||
|
if (so_error != 0 && so_error != EISCONN) {
|
||||||
|
errno = so_error;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// try to connect or throw on failure
|
// try to connect or throw on failure
|
||||||
void connect(const std::string &host, int port) {
|
void connect(const std::string &host, int port, int timeout_ms = 0) {
|
||||||
close();
|
close();
|
||||||
struct addrinfo hints {};
|
struct addrinfo hints {};
|
||||||
memset(&hints, 0, sizeof(struct addrinfo));
|
memset(&hints, 0, sizeof(struct addrinfo));
|
||||||
@@ -49,6 +113,10 @@ public:
|
|||||||
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
|
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
|
||||||
hints.ai_protocol = 0;
|
hints.ai_protocol = 0;
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = timeout_ms / 1000;
|
||||||
|
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||||
|
|
||||||
auto port_str = std::to_string(port);
|
auto port_str = std::to_string(port);
|
||||||
struct addrinfo *addrinfo_result;
|
struct addrinfo *addrinfo_result;
|
||||||
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
|
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
|
||||||
@@ -69,8 +137,9 @@ public:
|
|||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen);
|
::fcntl(socket_, F_SETFD, FD_CLOEXEC);
|
||||||
if (rv == 0) {
|
if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) {
|
||||||
|
last_errno = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
@@ -82,6 +151,12 @@ public:
|
|||||||
throw_spdlog_ex("::connect failed", last_errno);
|
throw_spdlog_ex("::connect failed", last_errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeout_ms > 0) {
|
||||||
|
// Set timeouts for send and recv
|
||||||
|
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
|
||||||
|
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
|
||||||
|
}
|
||||||
|
|
||||||
// set TCP_NODELAY
|
// set TCP_NODELAY
|
||||||
int enable_flag = 1;
|
int enable_flag = 1;
|
||||||
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),
|
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),
|
||||||
|
@@ -31,6 +31,7 @@ namespace sinks {
|
|||||||
struct tcp_sink_config {
|
struct tcp_sink_config {
|
||||||
std::string server_host;
|
std::string server_host;
|
||||||
int server_port;
|
int server_port;
|
||||||
|
int timeout_ms = 0; // The timeout for all 3 major socket operations that is connect, send, and recv
|
||||||
bool lazy_connect = false; // if true connect on first log call instead of on construction
|
bool lazy_connect = false; // if true connect on first log call instead of on construction
|
||||||
|
|
||||||
tcp_sink_config(std::string host, int port)
|
tcp_sink_config(std::string host, int port)
|
||||||
@@ -44,10 +45,22 @@ public:
|
|||||||
// connect to tcp host/port or throw if failed
|
// connect to tcp host/port or throw if failed
|
||||||
// host can be hostname or ip address
|
// host can be hostname or ip address
|
||||||
|
|
||||||
|
explicit tcp_sink(const std::string &host,
|
||||||
|
int port,
|
||||||
|
int timeout_ms = 0,
|
||||||
|
bool lazy_connect = false)
|
||||||
|
: config_{host, port} {
|
||||||
|
config_.timeout_ms = timeout_ms;
|
||||||
|
config_.lazy_connect = lazy_connect;
|
||||||
|
if (!config_.lazy_connect) {
|
||||||
|
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
explicit tcp_sink(tcp_sink_config sink_config)
|
explicit tcp_sink(tcp_sink_config sink_config)
|
||||||
: config_{std::move(sink_config)} {
|
: config_{std::move(sink_config)} {
|
||||||
if (!config_.lazy_connect) {
|
if (!config_.lazy_connect) {
|
||||||
this->client_.connect(config_.server_host, config_.server_port);
|
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +71,7 @@ protected:
|
|||||||
spdlog::memory_buf_t formatted;
|
spdlog::memory_buf_t formatted;
|
||||||
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
|
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
if (!client_.is_connected()) {
|
if (!client_.is_connected()) {
|
||||||
client_.connect(config_.server_host, config_.server_port);
|
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
|
||||||
}
|
}
|
||||||
client_.send(formatted.data(), formatted.size());
|
client_.send(formatted.data(), formatted.size());
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user