Initial commit
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PLANTSUNCAT_ERRC_H
|
||||
#define PLANTSUNCAT_ERRC_H
|
||||
#include "Core/Base/export.h"
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
|
||||
enum class NetError : unsigned char {
|
||||
// ===== 通用 / 取消 / 参数 =====
|
||||
unknown_error, // 被信号/中断打断(EINTR / WSAEINTR)
|
||||
interrupted, // 被信号/中断打断(EINTR / WSAEINTR)
|
||||
operation_canceled, // 取消(ECANCELED / WSAECANCELLED)
|
||||
invalid_argument, // 参数非法(EINVAL / WSAEINVAL)
|
||||
not_supported, // 不支持(EOPNOTSUPP/ENOTSUP 等)
|
||||
permission_denied, // 权限不足(EACCES)
|
||||
out_of_memory, // 内存不足(ENOMEM)
|
||||
resource_exhausted, // 资源耗尽(EMFILE/ENFILE/ENOBUFS 等)
|
||||
|
||||
// ===== I/O / 系统 =====
|
||||
io_error, // 其他 I/O 错误(EIO 等)
|
||||
bad_file_descriptor, // fd 无效(EBADF)
|
||||
not_a_socket, // 不是 socket(ENOTSOCK / WSAENOTSOCK)
|
||||
|
||||
// ===== 非阻塞 / 状态 =====
|
||||
would_block, // 非阻塞下暂时无数据/不可写(EAGAIN/EWOULDBLOCK / WSAEWOULDBLOCK)
|
||||
in_progress, // 操作进行中(EINPROGRESS / WSAEINPROGRESS)
|
||||
already_in_progress, // 已经在进行(EALREADY / WSAEALREADY)
|
||||
|
||||
// ===== 地址 / 协议 =====
|
||||
address_in_use, // 地址端口被占用(EADDRINUSE / WSAEADDRINUSE)
|
||||
address_not_available, // 地址不可用(EADDRNOTAVAIL / WSAEADDRNOTAVAIL)
|
||||
network_down, // 网络子系统 down(ENETDOWN / WSAENETDOWN)
|
||||
network_unreachable, // 网络不可达(ENETUNREACH / WSAENETUNREACH)
|
||||
host_unreachable, // 主机不可达(EHOSTUNREACH / WSAEHOSTUNREACH)
|
||||
protocol_error, // 协议/选项错误(EPROTO/EPROTOTYPE/ENOPROTOOPT...)
|
||||
|
||||
// ===== 连接语义(TCP 核心)=====
|
||||
not_connected, // 未连接(ENOTCONN / WSAENOTCONN)
|
||||
already_connected, // 已连接(EISCONN / WSAEISCONN)
|
||||
connection_refused, // 连接被拒绝(ECONNREFUSED / WSAECONNREFUSED)
|
||||
connection_reset, // 连接被重置(ECONNRESET / WSAECONNRESET)
|
||||
connection_aborted, // 连接被中止(ECONNABORTED / WSAECONNABORTED)
|
||||
connection_closed, // 对端正常关闭/EOF(recv 返回 0 的语义)
|
||||
timed_out, // 超时(ETIMEDOUT / WSAETIMEDOUT)
|
||||
broken_pipe, // 写入已关闭连接(EPIPE;Windows 近似用 shutdown/notconn/reset)
|
||||
|
||||
// ===== DNS / 名称解析(可选但建议)=====
|
||||
dns_not_found, // host 不存在/找不到(HOST_NOT_FOUND)
|
||||
dns_temporary_failure, // 临时失败(TRY_AGAIN)
|
||||
dns_failure, // 其他解析失败(NO_RECOVERY / 语义较泛)
|
||||
};
|
||||
|
||||
class NetErrorCategory final : public std::error_category {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const noexcept override {
|
||||
// enum_type_name 返回 string_view,静态存储即可
|
||||
static constexpr auto name_sv = magic_enum::enum_type_name<NetError>();
|
||||
return name_sv.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string message(int ev) const override {
|
||||
auto opt = magic_enum::enum_name(static_cast<NetError>(ev));
|
||||
if (!opt.empty())
|
||||
return std::string(opt);
|
||||
return "unknown enum error";
|
||||
}
|
||||
|
||||
[[nodiscard]] bool equivalent(const std::error_code& ec, int cond) const noexcept override {
|
||||
if (ec.category() != std::system_category()) return false;
|
||||
return map_system_error(ec.value()) == static_cast<NetError>(cond);
|
||||
}
|
||||
|
||||
static NetError map_system_error(int e) noexcept;
|
||||
};
|
||||
|
||||
inline const std::error_category& net_category() {
|
||||
static NetErrorCategory cat;
|
||||
return cat;
|
||||
}
|
||||
ERROR_CODE_TYPE last_socket_ec();
|
||||
|
||||
|
||||
|
||||
|
||||
template <typename T> using Ret = expected<T, Enum_Err<NetError>>;
|
||||
|
||||
|
||||
|
||||
|
||||
extern BaseLogger *socket_logger;
|
||||
#if WIN32
|
||||
using Socket_FD = unsigned long long;
|
||||
#else
|
||||
using Socket_FD = int;
|
||||
#endif
|
||||
|
||||
// 默认IPV4 TCP
|
||||
enum class Socket_Type { TCP, UDP, Null };
|
||||
struct Sockaddr_In {
|
||||
std::string ip;
|
||||
std::uint32_t port{};
|
||||
[[nodiscard]] std::string to_string() const;
|
||||
Sockaddr_In() = default;
|
||||
Sockaddr_In(const std::string &ip, std::uint32_t port) {
|
||||
this->ip = ip;
|
||||
this->port = port;
|
||||
}
|
||||
// 定义小于运算符,便于存入 std::set
|
||||
bool operator<(const Sockaddr_In &other) const {
|
||||
// 先比较 IP 地址
|
||||
if (ip != other.ip) {
|
||||
return ip < other.ip;
|
||||
}
|
||||
// 如果 IP 地址相同,再比较端口号
|
||||
return port < other.port;
|
||||
}
|
||||
bool operator==(const Sockaddr_In &other) const noexcept {
|
||||
return ip == other.ip && port == other.port;
|
||||
}
|
||||
|
||||
bool operator!=(const Sockaddr_In &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
struct Accept_Info {
|
||||
Sockaddr_In sockaddr;
|
||||
Socket_FD fd{};
|
||||
[[nodiscard]] std::string to_string() const {
|
||||
return "fd:" + std::to_string(fd) + ":[" + sockaddr.to_string() + "]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,421 @@
|
||||
#include "Core/Bit.h"
|
||||
#include "Core/RET.hpp"
|
||||
#include "Core/Statistics/Frequency_Limit.h"
|
||||
#include "Socket_p.h"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#ifdef __linux__
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
BaseLogger *socket_logger = nullptr;
|
||||
|
||||
std::string Sockaddr_In::to_string() const {
|
||||
return ip + ":" + std::to_string(port);
|
||||
}
|
||||
JSON Socket_Base::to_Json() const {
|
||||
JSON ret = JSON::object();
|
||||
ret.append({"fd", socket_fd});
|
||||
ret.append({"address",
|
||||
address.has_value() ? address.value().to_string() : "nullopt"});
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void log_error(BaseLogger *logger, const std::string &POS,
|
||||
Psc::socket::Socket_FD fd, const std::string &MSG) {
|
||||
static Frequency_Limit_Multi lm;
|
||||
bool ok = lm.test(std::to_string(fd) + "_" + Psc::to_string(last_socket_ec()) + POS);
|
||||
if (!ok)
|
||||
return;
|
||||
std::string msg =
|
||||
MSG + std::string(" ") + Psc::get_error_message() + " " + POS + "\n";
|
||||
if (!logger) {
|
||||
std::ostringstream oss;
|
||||
oss << "fd:" << fd << " " << msg << std::flush;
|
||||
std::cout << oss.str();
|
||||
} else {
|
||||
static Log_Type log_type({VAR_STR_1(fd)});
|
||||
logger->error("", log_type, msg);
|
||||
}
|
||||
}
|
||||
|
||||
Ret<Socket_FD> create_socket_fd(Socket_Type type) {
|
||||
if (type == Socket_Type::Null) {
|
||||
socket_logger->c_debug({}, {}, "严重错误 Socket type is Null: " + to_string(type));
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
int t = (type == Socket_Type::TCP) ? SOCK_STREAM : SOCK_DGRAM;
|
||||
#if defined(__linux__) && defined(SOCK_CLOEXEC)
|
||||
// 尽量在 socket() 阶段原子设置(避免 fork/exec 竞争窗口)
|
||||
t |= SOCK_CLOEXEC;
|
||||
#endif
|
||||
|
||||
|
||||
Socket_FD fd = ::socket(AF_INET, t, 0);
|
||||
if (fd == INVALID_SOCKET) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
Ret<void> set_buffer_size(Socket_FD that, size_t buffer_size) {
|
||||
// setsockopt(SO_RCVBUF) 在各平台基本都要求 int
|
||||
if (buffer_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
int sz = static_cast<int>(buffer_size);
|
||||
|
||||
// Windows 的 optval 类型是 const char*;POSIX 是 const void*
|
||||
#ifdef _WIN32
|
||||
const char* optval = reinterpret_cast<const char*>(&sz);
|
||||
#else
|
||||
const void* optval = reinterpret_cast<const void*>(&sz);
|
||||
#endif
|
||||
|
||||
if (::setsockopt(that, SOL_SOCKET, SO_RCVBUF, optval,
|
||||
static_cast<socklen_t>(sizeof(sz))) == SOCKET_ERROR) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
Ret<void> close(Socket_FD that) {
|
||||
if (that != -1) {
|
||||
#ifdef _WIN32
|
||||
int r = ::closesocket(that);
|
||||
#else
|
||||
int r = ::close(that); // 使用 POSIX 的 close 函数
|
||||
#endif
|
||||
if (r == SOCKET_ERROR) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
|
||||
Ret<void> bind(Socket_FD that, const Sockaddr_In& addr_in) {
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(addr_in.port);
|
||||
|
||||
if (inet_pton(AF_INET, addr_in.ip.c_str(), &addr.sin_addr) <= 0) {
|
||||
static Frequency_Limit fl;
|
||||
if (fl.test()) {
|
||||
//LOG_FD_Debug(that, VAR_STR_2(addr_in.ip, addr_in.port) + "不合法的地址!");
|
||||
}
|
||||
// 标准错误码:参数非法
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
if (::bind(that, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
|
||||
static Frequency_Limit fl;
|
||||
if (fl.test()) {
|
||||
//LOG_FD_Debug(that, VAR_STR_2(addr_in.ip, addr_in.port) + "绑定ip端口错误!");
|
||||
}
|
||||
// 标准错误码:直接上报系统错误(Windows=WSAGetLastError, Linux=errno)
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
Ret<int> send(Socket_FD connected_fd, void *data, std::size_t size) {
|
||||
#ifdef _WIN32
|
||||
int flags = 0;
|
||||
#else
|
||||
int flags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
const int bytes_sent =
|
||||
::send(connected_fd, (const char *)data, static_cast<int>(size), flags);
|
||||
|
||||
if (bytes_sent >= 0) {
|
||||
return bytes_sent; // 可能是部分发送,交给上层处理
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const int e = ::WSAGetLastError();
|
||||
|
||||
// 暂态:直接忽略(不报错),让上层保留缓冲下次继续发
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR || e == WSAEINPROGRESS || e == WSAEALREADY) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
const int e = errno;
|
||||
|
||||
// 暂态:直接忽略(不报错)
|
||||
if (e == EAGAIN
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
|| e == EWOULDBLOCK
|
||||
#endif
|
||||
|| e == EINTR || e == EINPROGRESS || e == EALREADY) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 其它错误:返回错误(带 native 错误码,供上层决定是否 close)
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
Ret<int> send(Socket_FD connected_fd, const std::string &data) {
|
||||
if (data.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return send(connected_fd, (uint8_t*)data.data(), data.size());
|
||||
}
|
||||
|
||||
|
||||
namespace TCP {
|
||||
Ret<void> set_no_delay(Socket_FD that, bool no_delay) {
|
||||
int flag = no_delay ? 1 : 0;
|
||||
auto ret = setsockopt(that, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<char *>(&flag), sizeof(flag));
|
||||
if (ret < 0) {
|
||||
LOG_FD_Debug(that, "设置socket阻塞属性错误!")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
Ret<void> set_keep_alive(Socket_FD that, bool enable) {
|
||||
int flag = enable ? 1 : 0;
|
||||
auto ret = setsockopt(that, SOL_SOCKET, SO_KEEPALIVE,
|
||||
reinterpret_cast<char *>(&flag), sizeof(flag));
|
||||
if (ret < 0) {
|
||||
LOG_FD_Debug(that, VAR_STR_2(that, enable) + "无法设置 SO_KEEPALIVE!")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
|
||||
|
||||
Ret<void> listen(Socket_FD that, int backlog) {
|
||||
if (::listen(that, backlog) == SOCKET_ERROR) {
|
||||
|
||||
auto ec = last_socket_ec();
|
||||
LOG_FD_Debug(that,
|
||||
VAR_STR_2(that, backlog) +
|
||||
"listen 错误");
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
} // namespace TCP
|
||||
namespace UDP {
|
||||
|
||||
|
||||
Ret<int> sendto(Socket_FD that, const std::string &client_ip,
|
||||
uint16_t client_port, const std::string &data) {
|
||||
#ifdef _WIN32
|
||||
int flags = 0;
|
||||
#else
|
||||
int flags = MSG_NOSIGNAL; // UDP 一般不需要,但保留也无妨
|
||||
#endif
|
||||
|
||||
if (data.empty()) {
|
||||
return 0; // 发送 0 字节:正常
|
||||
}
|
||||
|
||||
sockaddr_in client_addr{};
|
||||
client_addr.sin_family = AF_INET;
|
||||
client_addr.sin_port = htons(client_port);
|
||||
|
||||
if (inet_pton(AF_INET, client_ip.c_str(), &client_addr.sin_addr) <= 0) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
int bytes_sent = ::sendto(
|
||||
that, data.data(), static_cast<int>(data.size()), flags,
|
||||
reinterpret_cast<const sockaddr *>(&client_addr), sizeof(client_addr));
|
||||
|
||||
if (bytes_sent == SOCKET_ERROR) {
|
||||
auto ec = last_socket_ec();
|
||||
LOG_FD_Debug(that, std::string("sendto error: ") + get_error_message());
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
|
||||
// UDP sendto 成功时通常 >0;若出现 0,多半是 data 为空(已提前返回)
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace UDP
|
||||
void get_fd_set(const std::vector<Socket_FD> &fds, fd_set &ret_fd_set,
|
||||
int &max_fd) {
|
||||
FD_ZERO(&ret_fd_set); // 清空读集合
|
||||
// 初始化最大文件描述符为 -1
|
||||
max_fd = -1;
|
||||
// 将所有套接字添加到fd_set中,并找到最大套接字描述符
|
||||
for (Socket_FD fd : fds) {
|
||||
FD_SET(fd, &ret_fd_set);
|
||||
if (fd > max_fd) {
|
||||
max_fd = fd; // 更新最大文件描述符
|
||||
}
|
||||
}
|
||||
}
|
||||
timeval get_timeout(int timeout_ms) {
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = timeout_ms / 1000; // 秒
|
||||
timeout.tv_usec = (timeout_ms % 1000) * 1000; // 微秒
|
||||
return timeout;
|
||||
}
|
||||
Ret<std::vector<Socket_FD>>
|
||||
select_read(const std::vector<Socket_FD> &recv_fd_vector, int timeout_ms) {
|
||||
std::vector<Socket_FD> ret;
|
||||
fd_set fds;
|
||||
int max_fd = -1;
|
||||
get_fd_set(recv_fd_vector, fds, max_fd);
|
||||
timeval timeout = get_timeout(timeout_ms);
|
||||
int result = select(max_fd + 1, &fds, nullptr, nullptr, &timeout);
|
||||
if (result <= 0) {
|
||||
return ret;
|
||||
}
|
||||
for (Socket_FD fd : recv_fd_vector) {
|
||||
if (FD_ISSET(fd, &fds)) {
|
||||
ret.push_back(fd);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Ret<std::vector<Socket_FD>> select_write(std::vector<Socket_FD> fd_vector,
|
||||
int timeout_ms) {
|
||||
std::vector<Socket_FD> ret;
|
||||
fd_set fds;
|
||||
int max_fd = -1;
|
||||
get_fd_set(fd_vector, fds, max_fd);
|
||||
timeval timeout = get_timeout(timeout_ms);
|
||||
int result = select(max_fd + 1, nullptr, &fds, nullptr, &timeout);
|
||||
if (result <= 0) {
|
||||
return ret;
|
||||
}
|
||||
for (Socket_FD fd : fd_vector) {
|
||||
if (FD_ISSET(fd, &fds)) {
|
||||
ret.push_back(fd);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Ret<bool> select_write(Socket_FD that, int timeout_ms) {
|
||||
auto r = select_write(std::vector<Socket_FD>{that}, timeout_ms);
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
return !r.value().empty();
|
||||
}
|
||||
Ret<bool> select_read(Socket_FD that, int timeout_ms) {
|
||||
auto r = select_read(std::vector<Socket_FD>{that}, timeout_ms);
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
return !r.value().empty();
|
||||
}
|
||||
|
||||
Ret<Sockaddr_In> get_dest_addr(Socket_FD that) {
|
||||
Sockaddr_In ret;
|
||||
sockaddr_in addr{};
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
if (::getpeername(that, reinterpret_cast<sockaddr*>(&addr), &addrLen) == SOCKET_ERROR) {
|
||||
LOG_FD_Debug(that, "获取目标地址错误!");
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
char ipbuf[INET_ADDRSTRLEN]{};
|
||||
if (!::inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf))) {
|
||||
// inet_ntop 失败:返回标准错误
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
ret.ip = ipbuf;
|
||||
ret.port = ntohs(addr.sin_port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ret<Sockaddr_In> get_addr(Socket_FD that) {
|
||||
Sockaddr_In ret;
|
||||
sockaddr_in localAddr{};
|
||||
socklen_t addrLen = sizeof(localAddr);
|
||||
|
||||
if (::getsockname(that, reinterpret_cast<sockaddr*>(&localAddr), &addrLen) == SOCKET_ERROR) {
|
||||
LOG_FD_Debug(that, "获取socket地址错误!");
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error)); // 或 get_sys_ex()
|
||||
}
|
||||
|
||||
char ipbuf[INET_ADDRSTRLEN]{};
|
||||
if (!::inet_ntop(AF_INET, &localAddr.sin_addr, ipbuf, sizeof(ipbuf))) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
ret.ip = ipbuf;
|
||||
ret.port = ntohs(localAddr.sin_port);
|
||||
return ret;
|
||||
}
|
||||
std::string socket_base_info(Socket_FD that) {
|
||||
auto addr = get_addr(that);
|
||||
auto dest_addr = get_dest_addr(that);
|
||||
auto s1 = addr ? addr->to_string() : Psc::to_string(addr.error());
|
||||
auto s2 = dest_addr ? dest_addr->to_string() : to_string(dest_addr.error());
|
||||
return "[" + s1 + "]=>[" + s2 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ret<void> set_reuse(Socket_FD that, bool enable) {
|
||||
int flag = enable ? 1 : 0;
|
||||
auto ret = setsockopt(that, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<char *>(&flag), sizeof(flag));
|
||||
|
||||
if (ret < 0) {
|
||||
LOG_FD_Debug(that, VAR_STR_2(that, enable) +
|
||||
"无法设置 SO_REUSEADDR!")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument, ret));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
Ret<void> set_debug(Socket_FD that, bool enable) {
|
||||
int flag = enable ? 1 : 0;
|
||||
auto ret = setsockopt(that, SOL_SOCKET, SO_DEBUG,
|
||||
reinterpret_cast<char *>(&flag), sizeof(flag));
|
||||
if (ret < 0) {
|
||||
LOG_FD_Debug(that, VAR_STR_2(that, enable) + "无法设置 SO_DEBUG!")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument, ret));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
|
||||
Ret<int> get_socket_opt(Socket_FD fd, int optname) {
|
||||
int optionValue;
|
||||
socklen_t optionLen = sizeof(optionValue);
|
||||
auto r = getsockopt(fd, SOL_SOCKET, optname,
|
||||
reinterpret_cast<char *>(&optionValue), &optionLen);
|
||||
if (r == SOCKET_ERROR) {
|
||||
LOG_FD_Debug(fd, std::to_string(optname) + "获取套接字错误!")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument, r));
|
||||
}
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ret<void> set_reuse_addr(Socket_FD that, bool enable) {
|
||||
int opt = enable ? 1 : 0;
|
||||
auto r = setsockopt(that, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt,
|
||||
sizeof(opt));
|
||||
if (r == SOCKET_ERROR) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument, r));
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Psc::socket
|
||||
#undef SOCKET_END
|
||||
@@ -0,0 +1,115 @@
|
||||
#ifndef SOCKET_H
|
||||
#define SOCKET_H
|
||||
#include "../Base/expected.h"
|
||||
#include "../Base/export.h"
|
||||
#include "../Base/codec.h"
|
||||
#include "../JSON.h"
|
||||
#include "Core/spdlog/export.h"
|
||||
#include "NetError.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
|
||||
/*
|
||||
套接字连接过程 客户端(发数据端口)/服务端(收数据端口)
|
||||
1. 创建套接字
|
||||
2. 绑定地址和端口 (bind)
|
||||
3. 监听连接 (listen) / 接受连接 (accept)
|
||||
4. send() recv()
|
||||
5. 关闭连接 (close)
|
||||
*/
|
||||
namespace Psc::socket {
|
||||
Ret<Socket_FD> create_socket_fd(Socket_Type type);
|
||||
Ret<void> set_reuse(Socket_FD that, bool enable);
|
||||
// 开启了一个调试模式,允许内核在网络操作中记录详细的信息。可以通过查看内核日志或tcpdump、Wireshark、strace来实际观察这些调试信息。
|
||||
Ret<void> set_debug(Socket_FD that, bool enable);
|
||||
Ret<void> set_block(Socket_FD that, bool blocking);
|
||||
Ret<void> set_reuse_addr(Socket_FD that, bool enable);
|
||||
Ret<void> set_buffer_size(Socket_FD that, size_t buffer_size);
|
||||
Ret<std::vector<Socket_FD>> select_write(std::vector<Socket_FD> fds,
|
||||
int timeout_ms);
|
||||
Ret<std::vector<Socket_FD>> select_read(const std::vector<Socket_FD> &recv_fd_vector, int timeout_ms);
|
||||
Ret<bool> select_write(Socket_FD that, int timeout_ms);
|
||||
Ret<bool> select_read(Socket_FD that, int timeout_ms);
|
||||
Ret<Sockaddr_In> get_addr(Socket_FD that);
|
||||
Ret<Sockaddr_In> get_dest_addr(Socket_FD that);
|
||||
std::string socket_base_info(Socket_FD that);
|
||||
// 对于 TCP 套接字,fd 是一个已经连接的套接字(通过 connect() 或 accept()
|
||||
// 获得的)。 对于 UDP 套接字,你可以使用 connect()
|
||||
// 指定一个默认的目标地址(然后使用 send()),或者直接使用 sendto()
|
||||
// 来指定目标地址。
|
||||
|
||||
// NetError::in_progress
|
||||
// NetError::invalid_argument
|
||||
Ret<bool> connect(Socket_FD that, const Sockaddr_In &addr);
|
||||
|
||||
|
||||
Ret<void> close(Socket_FD that);
|
||||
|
||||
Ret<int> send(Socket_FD connected_fd, const std::string &data);
|
||||
Ret<int> send(Socket_FD connected_fd, void* data, std::size_t size);
|
||||
Ret<std::string> recv_all(Socket_FD connected_fd);
|
||||
Ret<std::string> recv_once(Socket_FD fd, int chunk_size);
|
||||
Ret<void> bind(Socket_FD that, const Sockaddr_In &addr);
|
||||
Ret<std::string> recv_all_until_idle(Socket_FD that, int chunk_size, size_t max_bytes, int idle_timeout_ms, bool eof_is_ok );
|
||||
|
||||
|
||||
namespace TCP {
|
||||
Ret<void> set_no_delay(Socket_FD that, bool no_delay);
|
||||
Ret<void> set_keep_alive(Socket_FD that, bool enable);
|
||||
|
||||
Ret<std::optional<Accept_Info>> accept(Socket_FD that);
|
||||
|
||||
Ret<void> listen(Socket_FD that, int backlog);
|
||||
|
||||
} // namespace TCP
|
||||
namespace UDP {
|
||||
Ret<int> sendto(Socket_FD that, const std::string &client_ip,
|
||||
uint16_t client_port, const std::string &data);
|
||||
Ret<std::string> recvfrom(Socket_FD receive_fd, Sockaddr_In *ret,
|
||||
int chunk_size = 1024 * 1024);
|
||||
} // namespace UDP
|
||||
|
||||
|
||||
|
||||
template <typename T> std::string Ret_To_String(const Ret<T> &r) {
|
||||
auto ec = r.error();
|
||||
std::ostringstream oss;
|
||||
oss << "【" << to_string(ec.nerr) << "】[" << "]";
|
||||
if (ec.native) {
|
||||
auto& nec = ec.native;
|
||||
oss << " native【" << nec.category().name() << ":" << nec.value() << "】[" << Psc::platform_2_utf8(nec.message()) << "]";
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Socket_Base {
|
||||
public:
|
||||
virtual ~Socket_Base() = default;
|
||||
std::optional<Sockaddr_In> address;
|
||||
Socket_FD socket_fd = -1;
|
||||
virtual std::string to_string() { return address->to_string(); }
|
||||
[[nodiscard]] JSON to_Json() const;
|
||||
virtual void close() {
|
||||
auto r = socket::close(socket_fd);
|
||||
if (!r) {
|
||||
socket_logger->c_debug({}, {}, to_string() + "关闭失败!");
|
||||
}
|
||||
socket_fd = -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool is_needed_reconnect_ec(ERROR_CODE_TYPE r);
|
||||
bool is_needed_reconnect(Socket_FD that);
|
||||
|
||||
bool is_client_need_close_ec(ERROR_CODE_TYPE r);
|
||||
bool is_client_need_close(Socket_FD that);
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Psc::socket
|
||||
#endif // SOCKET_H
|
||||
@@ -0,0 +1,436 @@
|
||||
#ifdef __linux__
|
||||
#include "Socket_p.h"
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
namespace Psc::socket {
|
||||
NetError NetErrorCategory::map_system_error(int e) noexcept {
|
||||
|
||||
switch (e) {
|
||||
case EINTR:
|
||||
return NetError::interrupted;
|
||||
case ECANCELED:
|
||||
return NetError::operation_canceled;
|
||||
case EINVAL:
|
||||
return NetError::invalid_argument;
|
||||
case EACCES:
|
||||
return NetError::permission_denied;
|
||||
case ENOMEM:
|
||||
return NetError::out_of_memory;
|
||||
case EMFILE:
|
||||
case ENFILE:
|
||||
case ENOBUFS:
|
||||
return NetError::resource_exhausted;
|
||||
|
||||
case EIO:
|
||||
return NetError::io_error;
|
||||
case EBADF:
|
||||
return NetError::bad_file_descriptor;
|
||||
case ENOTSOCK:
|
||||
return NetError::not_a_socket;
|
||||
|
||||
case EAGAIN:
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
case EWOULDBLOCK:
|
||||
#endif
|
||||
return NetError::would_block;
|
||||
|
||||
case EINPROGRESS:
|
||||
return NetError::in_progress;
|
||||
case EALREADY:
|
||||
return NetError::already_in_progress;
|
||||
|
||||
case EADDRINUSE:
|
||||
return NetError::address_in_use;
|
||||
case EADDRNOTAVAIL:
|
||||
return NetError::address_not_available;
|
||||
case ENETDOWN:
|
||||
return NetError::network_down;
|
||||
case ENETUNREACH:
|
||||
return NetError::network_unreachable;
|
||||
case EHOSTUNREACH:
|
||||
return NetError::host_unreachable;
|
||||
case EPROTO:
|
||||
case EPROTOTYPE:
|
||||
case ENOPROTOOPT:
|
||||
return NetError::protocol_error;
|
||||
|
||||
case ENOTCONN:
|
||||
return NetError::not_connected;
|
||||
case EISCONN:
|
||||
return NetError::already_connected;
|
||||
case ECONNREFUSED:
|
||||
return NetError::connection_refused;
|
||||
case ECONNRESET:
|
||||
return NetError::connection_reset;
|
||||
case ECONNABORTED:
|
||||
return NetError::connection_aborted;
|
||||
case ETIMEDOUT:
|
||||
return NetError::timed_out;
|
||||
case EPIPE:
|
||||
return NetError::broken_pipe;
|
||||
|
||||
default:
|
||||
return NetError::unknown_error;
|
||||
}
|
||||
}
|
||||
namespace TCP {
|
||||
|
||||
Ret<std::optional<Accept_Info>> accept(Socket_FD listen_fd) {
|
||||
sockaddr_in clientAddr{};
|
||||
socklen_t clientAddrSize = sizeof(clientAddr);
|
||||
|
||||
const int client_fd = ::accept(
|
||||
listen_fd, reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize);
|
||||
|
||||
if (client_fd < 0) {
|
||||
const int e = errno;
|
||||
|
||||
// 非阻塞:当前没有新连接(正常情况)
|
||||
// Linux 下通常是 EAGAIN / EWOULDBLOCK;被信号中断是 EINTR
|
||||
if (e == EAGAIN
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
|| e == EWOULDBLOCK
|
||||
#endif
|
||||
|| e == EINTR) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(listen_fd, std::string("accept error: ") +
|
||||
std::system_category().message(e));
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
Accept_Info info{};
|
||||
info.fd = client_fd;
|
||||
|
||||
char ipbuf[INET_ADDRSTRLEN]{};
|
||||
if (::inet_ntop(AF_INET, &clientAddr.sin_addr, ipbuf, sizeof(ipbuf)) ==
|
||||
nullptr) {
|
||||
const int e = errno; // inet_ntop 失败时设置 errno
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(listen_fd, std::string("inet_ntop error: ") +
|
||||
std::system_category().message(e));
|
||||
::close(client_fd); // 纯 Linux 语义:避免泄漏已 accept 的 fd
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
info.sockaddr.ip = ipbuf;
|
||||
info.sockaddr.port = ntohs(clientAddr.sin_port);
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace TCP
|
||||
Ret<bool> connect(Socket_FD that, const Sockaddr_In &addr_in) {
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(addr_in.port);
|
||||
|
||||
if (::inet_pton(AF_INET, addr_in.ip.c_str(), &addr.sin_addr) <= 0) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
if (::connect(that, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {
|
||||
const int e = errno;
|
||||
|
||||
// 非阻塞 connect:EINPROGRESS / EALREADY 表示“连接进行中”,不算失败
|
||||
if (e == EINPROGRESS || e == EALREADY) {
|
||||
// 这里用你的错误体系表达“进行中”(调用方可用 poll/epoll/select
|
||||
// 等等待可写)
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::in_progress, e));
|
||||
// 如果你更希望用返回值表达进行中,也可以改成:return false;
|
||||
}
|
||||
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 纯 Linux 语义:recv_all
|
||||
// - recv 返回 -1 表示失败,错误在 errno
|
||||
// - 非阻塞无数据/被信号打断:返回已收到部分(可能为空)
|
||||
// - recv 返回 0:对端正常关闭(EOF),这里返回 NetError::connection_closed
|
||||
#include <string>
|
||||
#include <cerrno>
|
||||
#include <system_error>
|
||||
|
||||
#ifdef _WIN32
|
||||
// 你这里是 Linux 版,用 errno / ioctl;Windows 需要 ioctlsocket + WSAGetLastError
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
Ret<std::string> recv_all(Socket_FD fd) {
|
||||
// 1) 先拿内核缓冲区里当前可读的字节数
|
||||
#ifdef _WIN32
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::not_supported)); // 如需我给 Windows 版我再补
|
||||
#else
|
||||
int avail = 0;
|
||||
if (::ioctl(fd, FIONREAD, &avail) != 0) {
|
||||
const int e = errno;
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
if (avail <= 0) {
|
||||
// avail==0:要区分“对端关闭” vs “当前没数据(EAGAIN)”
|
||||
// 用 MSG_PEEK 看看是否已经 EOF 或者只是没数据。
|
||||
char ch;
|
||||
for (;;) {
|
||||
const ssize_t n = ::recv(fd, &ch, 1, MSG_PEEK);
|
||||
if (n > 0) {
|
||||
// 有数据但 ioctl 说 0:极少见(竞态),再走一次 ioctl 或直接读 1
|
||||
avail = 1;
|
||||
break;
|
||||
}
|
||||
if (n == 0) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::connection_closed));
|
||||
}
|
||||
// n < 0
|
||||
const int e = errno;
|
||||
if (e == EINTR) continue;
|
||||
if (e == EAGAIN
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
|| e == EWOULDBLOCK
|
||||
#endif
|
||||
) {
|
||||
return std::string{}; // 非阻塞:当前没数据,正常返回空串(或你也可以返回 unexpected<Enum_Err<NetError>>)
|
||||
}
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 一次性读 avail 字节
|
||||
std::string out;
|
||||
out.resize(static_cast<size_t>(avail));
|
||||
|
||||
size_t off = 0;
|
||||
while (off < static_cast<size_t>(avail)) {
|
||||
const ssize_t n = ::recv(fd, out.data() + off,
|
||||
static_cast<size_t>(avail) - off, 0);
|
||||
if (n > 0) {
|
||||
off += static_cast<size_t>(n);
|
||||
continue;
|
||||
}
|
||||
if (n == 0) {
|
||||
// 读到一半对端关闭:保留已读部分还是报错,看你语义
|
||||
out.resize(off);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::connection_closed));
|
||||
}
|
||||
const int e = errno;
|
||||
if (e == EINTR) continue;
|
||||
|
||||
// 这里一般不该出现 EAGAIN(因为我们按 avail 读),但竞态下可能发生
|
||||
if (e == EAGAIN
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
|| e == EWOULDBLOCK
|
||||
#endif
|
||||
) {
|
||||
out.resize(off);
|
||||
return out; // 返回已读部分
|
||||
}
|
||||
|
||||
out.resize(off);
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
// 正常:一次性读完
|
||||
return out;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
namespace UDP {
|
||||
Ret<std::string> recvfrom(Socket_FD receive_fd, Sockaddr_In *ret,
|
||||
int chunk_size) {
|
||||
if (chunk_size <= 0 || ret == nullptr) {
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.resize(static_cast<size_t>(chunk_size));
|
||||
|
||||
sockaddr_in client_addr{};
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
|
||||
const ssize_t n =
|
||||
::recvfrom(receive_fd, buffer.data(), static_cast<size_t>(chunk_size), 0,
|
||||
reinterpret_cast<sockaddr *>(&client_addr), &addr_len);
|
||||
|
||||
if (n < 0) {
|
||||
const int e = errno;
|
||||
|
||||
if (e == EAGAIN
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
|| e == EWOULDBLOCK
|
||||
#endif
|
||||
|| e == EINTR) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(receive_fd, std::string("recvfrom error: ") +
|
||||
std::system_category().message(e));
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
// n == 0:UDP 允许 0 长度 datagram,仍然要填来源地址
|
||||
|
||||
char client_ip[INET_ADDRSTRLEN]{};
|
||||
if (::inet_ntop(AF_INET, &client_addr.sin_addr, client_ip,
|
||||
sizeof(client_ip)) == nullptr) {
|
||||
const int e = errno; // inet_ntop 失败会设置 errno
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(receive_fd, std::string("inet_ntop error: ") +
|
||||
std::system_category().message(e));
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
ret->ip = client_ip;
|
||||
ret->port = ntohs(client_addr.sin_port);
|
||||
|
||||
buffer.resize(static_cast<size_t>(n));
|
||||
return buffer;
|
||||
}
|
||||
} // namespace UDP
|
||||
|
||||
ERROR_CODE_TYPE last_socket_ec() { return errno; }
|
||||
|
||||
using Socket_FD = int;
|
||||
constexpr int kSocketError = -1;
|
||||
Ret<void> set_block(Socket_FD fd, bool blocking) {
|
||||
if (fd == -1)
|
||||
return Ret<void>();
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) {
|
||||
std::cerr << "Failed to get socket flags." << std::endl;
|
||||
}
|
||||
|
||||
if (blocking) {
|
||||
flags &= ~O_NONBLOCK; // 清除 O_NONBLOCK 标志,设置为阻塞
|
||||
} else {
|
||||
flags |= O_NONBLOCK; // 设置 O_NONBLOCK 标志,设置为非阻塞
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags) == -1) {
|
||||
LOG_FD_ERROR(fd, "Failed to set socket to " +
|
||||
std::string(blocking ? "blocking" : "non-blocking") +
|
||||
" mode.")
|
||||
return unexpected<Enum_Err<NetError>>(Enum_Err(NetError::unknown_error));
|
||||
}
|
||||
|
||||
return Ret<void>();
|
||||
}
|
||||
bool is_needed_reconnect_ec(ERROR_CODE_TYPE e) {
|
||||
switch (e) {
|
||||
// 连接已失效/网络不可达:建议重连
|
||||
case ECONNRESET:
|
||||
case ECONNABORTED:
|
||||
case ENOTCONN:
|
||||
case ETIMEDOUT:
|
||||
case ENETDOWN:
|
||||
case ENETUNREACH:
|
||||
case EHOSTUNREACH:
|
||||
case ECONNREFUSED: // 多见于 connect;这里出现也可视为需重连
|
||||
case EPIPE: // 也可视为连接不可用(更常见于 send,但有时可一起处理)
|
||||
return true;
|
||||
|
||||
// 现在没数据/可重试/被打断:不重连
|
||||
case EAGAIN:
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
case EWOULDBLOCK:
|
||||
#endif
|
||||
case EINTR:
|
||||
case EINPROGRESS:
|
||||
case EALREADY:
|
||||
return false;
|
||||
|
||||
// 其他错误:保守处理为不重连(也可改成 true 更激进)
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_needed_reconnect(int fd) {
|
||||
char buf[1];
|
||||
const ssize_t r = ::recv(fd, buf, sizeof(buf), MSG_PEEK);
|
||||
return is_needed_reconnect_ec(r);
|
||||
}
|
||||
|
||||
// 根据“系统错误码/返回码”判断:这个 TCP 连接是否应该被认为已失效并关闭。
|
||||
// 约定:
|
||||
// - r == 0 :通常表示对端正常关闭(EOF/FIN),应关闭本端连接。
|
||||
// - r < 0 :表示发生错误;具体原因需要结合 errno(Linux)分类。
|
||||
// - r > 0 :表示仍可读到数据/连接仍活着,不关闭。
|
||||
bool is_client_need_close_ec(ERROR_CODE_TYPE e) {
|
||||
switch (e) {
|
||||
// ====== 致命错误:连接已不可用,建议关闭 ======
|
||||
|
||||
case ECONNRESET:
|
||||
// 连接被对端复位(RST),对端异常断开:应关闭
|
||||
return true;
|
||||
|
||||
case ECONNABORTED:
|
||||
// 连接被中止(本端/对端导致),不可继续使用:应关闭
|
||||
return true;
|
||||
|
||||
case ENOTCONN:
|
||||
// socket 未处于连接状态(例如已经断开或从未连接):应关闭
|
||||
return true;
|
||||
|
||||
case ETIMEDOUT:
|
||||
// 连接超时(可能是网络断开/对端无响应):通常视为失效,建议关闭
|
||||
return true;
|
||||
|
||||
case EPIPE:
|
||||
// 管道破裂:常见于 send 时对端已关闭;连接不可用:应关闭
|
||||
return true;
|
||||
|
||||
case ECONNREFUSED:
|
||||
// 连接被拒绝:通常发生在 connect 阶段;若出现在此处也视为不可用:应关闭
|
||||
return true;
|
||||
|
||||
// ====== 暂态/可恢复错误:不建议立即关闭 ======
|
||||
|
||||
case EAGAIN:
|
||||
// 非阻塞模式下“暂时无数据/暂不可读写”,属于正常现象:不关闭
|
||||
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
|
||||
case EWOULDBLOCK:
|
||||
// 与 EAGAIN 类似,表示暂时会阻塞:不关闭
|
||||
#endif
|
||||
return false;
|
||||
|
||||
case EINPROGRESS:
|
||||
// 非阻塞 connect 进行中:不关闭
|
||||
return false;
|
||||
|
||||
case EALREADY:
|
||||
// 非阻塞 connect 已在进行:不关闭
|
||||
return false;
|
||||
|
||||
case EINTR:
|
||||
// 系统调用被信号中断:通常应重试,而不是关闭
|
||||
return false;
|
||||
|
||||
default:
|
||||
// 其他错误:保守策略——不立刻关闭(也可按需求改为 true 更激进)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_client_need_close(int fd) {
|
||||
char buf[1];
|
||||
int r = ::recv(fd, buf, sizeof(buf), MSG_PEEK);
|
||||
return is_client_need_close_ec(r);
|
||||
}
|
||||
|
||||
} // namespace Psc::socket
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef SOCKET_P_H
|
||||
#define SOCKET_P_H
|
||||
#include "Socket.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
namespace {
|
||||
struct InitializeWinsock {
|
||||
InitializeWinsock() {
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
std::cerr << "WSAStartup failed." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
~InitializeWinsock() { WSACleanup(); }
|
||||
} Temp;
|
||||
} // namespace
|
||||
|
||||
namespace Psc {}
|
||||
#define socklen_t int
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdexcept>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
|
||||
#define SOCKET_ERROR -1
|
||||
#endif
|
||||
|
||||
namespace Psc::socket {
|
||||
void log_error(BaseLogger *logger, const std::string &POS,
|
||||
Psc::socket::Socket_FD fd, const std::string &MSG);
|
||||
}
|
||||
#define LOG_FD_ERROR(fd, MSG) log_error(socket_logger, LOG_POS, fd, MSG);
|
||||
#define LOG_FD_Debug(fd, MSG) log_error(socket_logger, LOG_POS, fd, MSG);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,397 @@
|
||||
#ifdef WIN32
|
||||
#include "Socket_p.h"
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
Ret<bool> connect(SOCKET s, const Sockaddr_In& addr_in) {
|
||||
SOCKADDR_IN addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<u_short>(addr_in.port));
|
||||
|
||||
if (::inet_pton(AF_INET, addr_in.ip.c_str(), &addr.sin_addr) != 1) {
|
||||
return unexpected(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
if (::connect(s, reinterpret_cast<const SOCKADDR*>(&addr), sizeof(addr)) == 0) {
|
||||
return true; // 立即成功
|
||||
}
|
||||
|
||||
const int e = ::WSAGetLastError();
|
||||
|
||||
// 统一按“非阻塞 connect 语义”:这些不算失败
|
||||
switch (e) {
|
||||
case WSAEWOULDBLOCK:
|
||||
case WSAEINPROGRESS:
|
||||
case WSAEALREADY:
|
||||
return true; // 已发起连接,后续用 SO_ERROR 判定最终结果
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
|
||||
Ret<void> set_block(Socket_FD that, bool blocking) {
|
||||
if (that == -1)
|
||||
return Ret<void>();
|
||||
u_long mode = blocking ? 0 : 1; // 0 为阻塞, 1 为非阻塞
|
||||
if (ioctlsocket(that, FIONBIO, &mode) != 0) {
|
||||
LOG_FD_Debug(that, VAR_STR_2(that, blocking) + "设置socket阻塞属性错误!")
|
||||
}
|
||||
return Ret<void>();
|
||||
}
|
||||
|
||||
|
||||
NetError NetErrorCategory::map_system_error(int e) noexcept {
|
||||
switch (e) {
|
||||
case WSAEINTR: return NetError::interrupted;
|
||||
case WSAECANCELLED: return NetError::operation_canceled;
|
||||
case WSAEINVAL: return NetError::invalid_argument;
|
||||
case WSAEACCES: return NetError::permission_denied;
|
||||
case WSAENOBUFS: return NetError::resource_exhausted;
|
||||
case WSAEBADF: return NetError::bad_file_descriptor;
|
||||
case WSAENOTSOCK: return NetError::not_a_socket;
|
||||
|
||||
case WSAEWOULDBLOCK: return NetError::would_block;
|
||||
case WSAEINPROGRESS: return NetError::in_progress;
|
||||
case WSAEALREADY: return NetError::already_in_progress;
|
||||
|
||||
case WSAEADDRINUSE: return NetError::address_in_use;
|
||||
case WSAEADDRNOTAVAIL: return NetError::address_not_available;
|
||||
case WSAENETDOWN: return NetError::network_down;
|
||||
case WSAENETUNREACH: return NetError::network_unreachable;
|
||||
case WSAEHOSTUNREACH: return NetError::host_unreachable;
|
||||
|
||||
case WSAENOTCONN: return NetError::not_connected;
|
||||
case WSAEISCONN: return NetError::already_connected;
|
||||
case WSAECONNREFUSED: return NetError::connection_refused;
|
||||
case WSAECONNRESET: return NetError::connection_reset;
|
||||
case WSAECONNABORTED: return NetError::connection_aborted;
|
||||
case WSAETIMEDOUT: return NetError::timed_out;
|
||||
|
||||
default: return NetError::unknown_error;
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_CODE_TYPE last_socket_ec() {
|
||||
int wsa = ::WSAGetLastError();
|
||||
return wsa;
|
||||
}
|
||||
|
||||
namespace TCP {
|
||||
Ret<std::optional<Accept_Info>> accept(SOCKET listen_fd) {
|
||||
SOCKADDR_IN clientAddr{};
|
||||
int clientAddrSize = sizeof(clientAddr);
|
||||
|
||||
SOCKET client_fd =
|
||||
::accept(listen_fd, reinterpret_cast<SOCKADDR*>(&clientAddr), &clientAddrSize);
|
||||
|
||||
if (client_fd == INVALID_SOCKET) {
|
||||
int e = ::WSAGetLastError();
|
||||
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(listen_fd, std::string("accept error: ") + std::system_category().message(e));
|
||||
return unexpected(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
Accept_Info info{};
|
||||
info.fd = client_fd;
|
||||
|
||||
char ipbuf[INET_ADDRSTRLEN]{};
|
||||
if (::inet_ntop(AF_INET, &clientAddr.sin_addr, ipbuf, sizeof(ipbuf)) == nullptr) {
|
||||
int e = ::WSAGetLastError();
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(listen_fd, std::string("inet_ntop error: ") + std::system_category().message(e));
|
||||
return unexpected(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
info.sockaddr.ip = ipbuf;
|
||||
info.sockaddr.port = ntohs(clientAddr.sin_port);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ret<std::string> recv_all(SOCKET fd) {
|
||||
u_long avail = 0;
|
||||
if (::ioctlsocket(fd, FIONREAD, &avail) != 0) {
|
||||
int e = ::WSAGetLastError();
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
|
||||
if (avail == 0) {
|
||||
// 无法仅靠 FIONREAD 区分“没数据”还是“已关闭但没数据”
|
||||
// 如果你需要区分:可用 select 读集合 + 再 recv(1字节, MSG_PEEK)
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
size_t to_read = avail;
|
||||
|
||||
std::string buf;
|
||||
buf.resize(to_read);
|
||||
|
||||
size_t total = 0;
|
||||
while (total < to_read) {
|
||||
int n = ::recv(fd, buf.data() + total, (int)(to_read - total), 0);
|
||||
if (n == SOCKET_ERROR) {
|
||||
int e = ::WSAGetLastError();
|
||||
// 理论上 FIONREAD>0 后不该 WSAEWOULDBLOCK,但并发/竞态下可能发生
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR) {
|
||||
break; // 立刻返回已读部分
|
||||
}
|
||||
buf.resize(total);
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
if (n == 0) {
|
||||
// 对端关闭
|
||||
buf.resize(total);
|
||||
if (!buf.empty()) return buf;
|
||||
return unexpected(Enum_Err(NetError::connection_closed));
|
||||
}
|
||||
total += (size_t)n;
|
||||
}
|
||||
|
||||
buf.resize(total);
|
||||
|
||||
//std::cout << "bufsize:" << buf.size() << std::endl;
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ret<std::string> recv_once(Socket_FD fd, int max_bytes) {
|
||||
if (max_bytes <= 0) return unexpected(Enum_Err(NetError::invalid_argument));
|
||||
|
||||
std::string buf;
|
||||
buf.resize((size_t)max_bytes);
|
||||
|
||||
int n = ::recv(fd, buf.data(), max_bytes, 0);
|
||||
|
||||
if (n == SOCKET_ERROR) {
|
||||
int e = ::WSAGetLastError();
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR) return std::string{};
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
if (n == 0) return unexpected(Enum_Err(NetError::connection_closed));
|
||||
|
||||
buf.resize((size_t)n);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ret<std::string> recv_all_until_idle(
|
||||
Socket_FD fd,
|
||||
int chunk_size,
|
||||
size_t max_bytes,
|
||||
int idle_timeout_ms, // 多久没新数据就退出
|
||||
bool eof_is_ok = true // n==0 是否当作正常结束
|
||||
) {
|
||||
if (chunk_size <= 0 || max_bytes == 0) {
|
||||
return unexpected(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.reserve(std::min<size_t>(max_bytes, 64 * 1024));
|
||||
|
||||
size_t total = 0;
|
||||
|
||||
while (total < max_bytes) {
|
||||
// 用 select 等待可读(避免阻塞卡死;也给 idle 超时提供退出点)
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(fd, &rfds);
|
||||
|
||||
TIMEVAL tv;
|
||||
tv.tv_sec = idle_timeout_ms / 1000;
|
||||
tv.tv_usec = (idle_timeout_ms % 1000) * 1000;
|
||||
|
||||
int s = ::select(0, &rfds, nullptr, nullptr, &tv);
|
||||
if (s == 0) {
|
||||
// 超时:认为“暂时读不到了”,退出返回已读
|
||||
return buffer;
|
||||
}
|
||||
if (s == SOCKET_ERROR) {
|
||||
int e = ::WSAGetLastError();
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
|
||||
const int want = (int)std::min<size_t>((size_t)chunk_size, max_bytes - total);
|
||||
buffer.resize(total + (size_t)want);
|
||||
|
||||
int n = ::recv(fd, buffer.data() + total, want, 0);
|
||||
|
||||
if (n == SOCKET_ERROR) {
|
||||
int e = ::WSAGetLastError();
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR) {
|
||||
buffer.resize(total);
|
||||
return buffer;
|
||||
}
|
||||
buffer.resize(total);
|
||||
return unexpected(Enum_Err(NetErrorCategory::map_system_error(e), e));
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
buffer.resize(total);
|
||||
if (eof_is_ok) return buffer;
|
||||
return unexpected(Enum_Err(NetError::connection_closed));
|
||||
}
|
||||
|
||||
total += (size_t)n;
|
||||
buffer.resize(total);
|
||||
}
|
||||
|
||||
// 达到上限:防止对端一直发导致无限读
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
bool is_client_need_close_ec(ERROR_CODE_TYPE e) {
|
||||
switch (e) {
|
||||
// 连接已失效/不再可用:应关闭
|
||||
case WSAECONNRESET: // 对端复位
|
||||
case WSAECONNABORTED: // 连接被中止
|
||||
case WSAENOTCONN: // 未连接
|
||||
case WSAETIMEDOUT: // 超时(可视为应关闭)
|
||||
case WSAESHUTDOWN: // 本端已 shutdown,收发被禁止
|
||||
return true;
|
||||
|
||||
// 连接拒绝通常发生在 connect 阶段;服务端 recv 上很少见。
|
||||
// 这里按你原逻辑,仍视为应关闭(等价于“不可用”)。
|
||||
case WSAECONNREFUSED:
|
||||
return true;
|
||||
|
||||
// 非阻塞无数据/暂态:不关闭
|
||||
case WSAEWOULDBLOCK:
|
||||
case WSAEINPROGRESS:
|
||||
case WSAEALREADY:
|
||||
case WSAEINTR:
|
||||
return false;
|
||||
|
||||
// 其他错误:保守策略,不直接关闭(也可改成 true 更激进)
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool is_client_need_close(SOCKET s) {
|
||||
char buf[1];
|
||||
int r = ::recv(s, buf, sizeof(buf), MSG_PEEK);
|
||||
if (r == 0) {
|
||||
// 对端正常关闭(FIN/EOF)
|
||||
return true;
|
||||
}
|
||||
if (r == SOCKET_ERROR) {
|
||||
const int e = ::WSAGetLastError();
|
||||
return Psc::socket::is_client_need_close_ec(e);
|
||||
}
|
||||
// r > 0:还能读到数据,连接正常
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_needed_reconnect_ec(ERROR_CODE_TYPE e) {
|
||||
switch (e) {
|
||||
// 这些表示连接已失效/网络不可达:建议重连
|
||||
case WSAECONNRESET: // 远端强制关闭
|
||||
case WSAECONNABORTED: // 连接被中止
|
||||
case WSAENOTCONN: // 未连接
|
||||
case WSAETIMEDOUT: // 超时
|
||||
case WSAENETDOWN: // 网络子系统不可用
|
||||
case WSAENETUNREACH: // 网络不可达
|
||||
case WSAEHOSTUNREACH: // 主机不可达
|
||||
case WSAECONNREFUSED: // 连接被拒绝(多见于 connect 阶段;这里出现也可视为需重连)
|
||||
return true;
|
||||
|
||||
// 这些表示“现在没数据/可重试/被打断”:不重连
|
||||
case WSAEWOULDBLOCK: // 非阻塞暂时无数据
|
||||
case WSAEINPROGRESS: // 操作进行中
|
||||
case WSAEALREADY: // 已在进行
|
||||
case WSAEINTR: // 被中断
|
||||
return false;
|
||||
|
||||
// 其他错误:按你的策略决定。这里保守处理为“不重连”
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_needed_reconnect(SOCKET s) {
|
||||
char buf[1];
|
||||
int r = ::recv(s, buf, sizeof(buf), MSG_PEEK);
|
||||
|
||||
if (r == 0) {
|
||||
// 对端正常关闭(FIN/EOF)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (r == SOCKET_ERROR) {
|
||||
const int e = ::WSAGetLastError();
|
||||
return is_needed_reconnect_ec(e);
|
||||
}
|
||||
|
||||
// r > 0:能窥探到数据,连接仍然活着
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace UDP {
|
||||
|
||||
Ret<std::string> recvfrom(SOCKET receive_fd, Sockaddr_In* ret, int chunk_size) {
|
||||
if (chunk_size <= 0 || ret == nullptr) {
|
||||
return unexpected(Enum_Err(NetError::invalid_argument));
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.resize(static_cast<size_t>(chunk_size));
|
||||
|
||||
SOCKADDR_IN client_addr{};
|
||||
int addr_len = sizeof(client_addr);
|
||||
|
||||
int n = ::recvfrom(receive_fd,
|
||||
buffer.data(),
|
||||
chunk_size,
|
||||
0,
|
||||
reinterpret_cast<SOCKADDR*>(&client_addr),
|
||||
&addr_len);
|
||||
|
||||
if (n == SOCKET_ERROR) {
|
||||
const int e = ::WSAGetLastError();
|
||||
|
||||
// 非阻塞:暂时无数据 / 被中断:维持你原来的语义,返回空串
|
||||
if (e == WSAEWOULDBLOCK || e == WSAEINTR) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(receive_fd, std::string("recvfrom error: ") +
|
||||
std::system_category().message(e));
|
||||
return unexpected(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
// n == 0:UDP 允许 0 长度 datagram,依然需要填来源地址
|
||||
|
||||
char client_ip[INET_ADDRSTRLEN]{};
|
||||
if (::inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN) == nullptr) {
|
||||
const int e = ::WSAGetLastError();
|
||||
const auto ne = NetErrorCategory::map_system_error(e);
|
||||
LOG_FD_Debug(receive_fd, std::string("inet_ntop error: ") +
|
||||
std::system_category().message(e));
|
||||
return unexpected(Enum_Err(ne, e));
|
||||
}
|
||||
|
||||
ret->ip = client_ip;
|
||||
ret->port = ntohs(client_addr.sin_port);
|
||||
|
||||
buffer.resize(static_cast<size_t>(n));
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Psc::socket
|
||||
#endif
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "TCP_Client.h"
|
||||
#include "Core/Statistics/Frequency_Limit.h"
|
||||
#include "Socket_p.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
static bool fd_writable_now(int fd) {
|
||||
#ifdef _WIN32
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET((SOCKET)fd, &wfds);
|
||||
timeval tv{};
|
||||
tv.tv_sec = 0; tv.tv_usec = 0;
|
||||
int r = select(0, nullptr, &wfds, nullptr, &tv);
|
||||
return r > 0 && FD_ISSET((SOCKET)fd, &wfds);
|
||||
#else
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(fd, &wfds);
|
||||
timeval tv{};
|
||||
tv.tv_sec = 0; tv.tv_usec = 0;
|
||||
int r = select(fd + 1, nullptr, &wfds, nullptr, &tv);
|
||||
return r > 0 && FD_ISSET(fd, &wfds);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void TCP_Client::create() {
|
||||
send_buffer.init(max_buffer_size);
|
||||
|
||||
auto rsf = socket::create_socket_fd(Socket_Type::TCP);
|
||||
if (!rsf) {
|
||||
last_err = rsf.error();
|
||||
socket_logger->c_debug("Tcp_Client::create", {}, LOG_POS + last_err.to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
socket_fd = rsf.value();
|
||||
set_state(Not_Set_Field);
|
||||
auto r = socket::set_reuse(socket_fd, true)
|
||||
.and_then([this]() { return socket::set_block(socket_fd, false); })
|
||||
.and_then([this]() { return socket::TCP::set_keep_alive(socket_fd, true); })
|
||||
.and_then([this]() { return socket::TCP::set_no_delay(socket_fd, true); })
|
||||
.and_then([this]() { return socket::set_reuse_addr(socket_fd, true); })
|
||||
|
||||
;
|
||||
|
||||
if (!r) {
|
||||
last_err = r.error();
|
||||
socket_logger->error("Tcp_Client::create", {}, Psc::to_string(last_err) + " " + LOG_POS);
|
||||
(void)socket::close(socket_fd);
|
||||
socket_fd = -1;
|
||||
set_state(Not_Created);
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里最好检查 dest_address 是否已设置(你自己加一个标志位也行)
|
||||
|
||||
|
||||
set_state(Reconnecting);
|
||||
next_reconnect_tp = std::chrono::steady_clock::now();
|
||||
tick(); // 立即尝试一次
|
||||
}
|
||||
|
||||
|
||||
void TCP_Client::schedule_recreate(Enum_Err<NetError> e) {
|
||||
last_err = e;
|
||||
(void)socket::close(socket_fd);
|
||||
set_state(Not_Created);
|
||||
next_reconnect_tp = std::chrono::steady_clock::now() + std::chrono::milliseconds(reconnect_backoff_ms);
|
||||
}
|
||||
|
||||
bool TCP_Client::start_connect() {
|
||||
if (socket_fd == -1) return false;
|
||||
|
||||
auto r = socket::connect(socket_fd, dest_address);
|
||||
if (!r) {
|
||||
last_err = r.error();
|
||||
|
||||
|
||||
if (last_err.nerr == NetError::in_progress) {
|
||||
set_state(Connecting);
|
||||
return false; // 还没完成
|
||||
}
|
||||
|
||||
if (last_err.nerr == NetError::already_connected) {
|
||||
set_state(Wait_Check);
|
||||
return true;
|
||||
}
|
||||
|
||||
socket_logger->c_debug({}, {}, "连接失败:"+ VAR_STR_2(last_err, *this) + "\n");
|
||||
|
||||
|
||||
// 其它错误:进入退避重连
|
||||
schedule_recreate(last_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TCP_Client::check_writeable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCP_Client::tick() {
|
||||
if (socket_fd == -1) return;
|
||||
|
||||
auto st = state.load();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (st == Not_Created) {
|
||||
create();
|
||||
return;
|
||||
}
|
||||
|
||||
if (st == Reconnecting) {
|
||||
if (now >= next_reconnect_tp) {
|
||||
(void) start_connect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (st == Connecting || st == Wait_Check) {
|
||||
set_state(Connected);
|
||||
return;
|
||||
}
|
||||
|
||||
if (st == Connected) {
|
||||
flush_send_buffer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string TCP_Client::read() {
|
||||
if (socket_fd == -1) return "";
|
||||
|
||||
|
||||
|
||||
// 如果还没连上,先推进连接状态
|
||||
tick();
|
||||
if (state.load(std::memory_order_relaxed) != Connected) return "";
|
||||
|
||||
|
||||
|
||||
auto ret = socket::recv_all(socket_fd);
|
||||
if (!ret) {
|
||||
Enum_Err e = ret.error();
|
||||
|
||||
// 下面这几个名字按你 NetError 实际枚举改:
|
||||
// - would_block: 非阻塞没数据,不算错
|
||||
// - in_progress/not_connected: 连接没完成
|
||||
if (e.nerr == NetError::would_block) return "";
|
||||
|
||||
static Frequency_Limit drop_log_fl;
|
||||
if (drop_log_fl.test()) {
|
||||
socket_logger->c_debug("Tcp_Client::read recv失败", {},
|
||||
to_string() + " " + Ret_To_String(ret) + " " + LOG_POS);
|
||||
}
|
||||
|
||||
// 断线类错误:进入退避重连(必要时你也可以 close+重建fd)
|
||||
schedule_recreate(e);
|
||||
return "";
|
||||
}
|
||||
|
||||
auto r = ret.value();
|
||||
return r;
|
||||
}
|
||||
|
||||
void TCP_Client::flush_send_buffer() {
|
||||
if (socket_fd == -1) return;
|
||||
if (state.load(std::memory_order_relaxed) != Connected) return;
|
||||
if (send_buffer.empty()) return;
|
||||
|
||||
constexpr uint32_t patch_len = 1500;
|
||||
std::string buf;
|
||||
buf.resize(patch_len);
|
||||
|
||||
while (true) {
|
||||
uint32_t n = send_buffer.peek_best_effort(buf.data(), patch_len);
|
||||
if (n == 0) break;
|
||||
auto r = socket::send(socket_fd, buf.data(), n);
|
||||
if (!r) {
|
||||
last_err = r.error();
|
||||
|
||||
if (last_err.nerr == NetError::would_block) break;
|
||||
if (last_err.nerr == NetError::connection_aborted) {
|
||||
schedule_recreate(last_err);
|
||||
}
|
||||
else if (last_err.nerr == NetError::connection_reset) {
|
||||
schedule_recreate(last_err);
|
||||
} else {
|
||||
socket_logger->c_debug("Tcp_Client::send", {}, "触发重连 " + VAR_STR_2(last_err, *this));
|
||||
schedule_recreate(last_err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
send_buffer.skip_best_effort((uint32_t)r.value());
|
||||
}
|
||||
}
|
||||
|
||||
void TCP_Client::send(const std::string& data) {
|
||||
if (data.empty()) return;
|
||||
(void)send_buffer.write_best_effort((const uint8_t*)data.data(), (uint32_t)data.size());
|
||||
if (state != Connected) return;
|
||||
tick();
|
||||
flush_send_buffer();
|
||||
}
|
||||
|
||||
std::string TCP_Client::to_string() {
|
||||
return VAR_STR_4(socket_fd, address, dest_address, state);
|
||||
}
|
||||
void TCP_Client::close() {
|
||||
if (socket_fd != -1) {
|
||||
auto r = socket::close(socket_fd);
|
||||
(void)r;
|
||||
socket_fd = -1;
|
||||
set_state(Not_Created);
|
||||
}
|
||||
}
|
||||
|
||||
void TCP_Client::set_state(State s) {
|
||||
auto old_state = state.load();
|
||||
std::ostringstream oss;
|
||||
oss << to_string() << "状态变化:" << Psc::to_string(s) << "===>" << Psc::to_string(old_state) << std::endl;
|
||||
socket_logger->debug({}, {}, oss.str());
|
||||
if (on_state_change) on_state_change(state, s);
|
||||
state = s;
|
||||
}
|
||||
|
||||
TCP_Client::~TCP_Client() {
|
||||
TCP_Client::close();
|
||||
}
|
||||
|
||||
} // namespace Psc::socket
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "Core/Base/RingBuffer.hpp"
|
||||
#include "Core/Statistics/Statistics.h"
|
||||
#include "Socket.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
class TCP_Client : public Socket_Base {
|
||||
public:
|
||||
~TCP_Client() override;
|
||||
void set_buffer_size(size_t size) { max_buffer_size = size; }
|
||||
void set_dest_address(const Sockaddr_In& a) { dest_address = a; }
|
||||
void create();
|
||||
// 建议:外部周期性调用一次(比如主循环/线程每 1~10ms)
|
||||
void tick();
|
||||
std::string read();
|
||||
void send(const std::string& data);
|
||||
std::string to_string() override;
|
||||
enum State {
|
||||
Not_Created,
|
||||
Not_Set_Field,
|
||||
Reconnecting,
|
||||
Connecting,
|
||||
Wait_Check,
|
||||
Connected
|
||||
};
|
||||
void close() override;
|
||||
std::atomic<State> state{Not_Created};
|
||||
void set_state(State s);
|
||||
std::function<void(State old_state, State new_state)> on_state_change = nullptr;
|
||||
Enum_Err<NetError> last_err = Enum_Err(NetError::unknown_error);
|
||||
protected:
|
||||
size_t max_buffer_size = 1024 * 1024;
|
||||
StreamRingBuffer_ST send_buffer;
|
||||
Sockaddr_In dest_address{};
|
||||
int reconnect_backoff_ms = 200;
|
||||
std::chrono::steady_clock::time_point next_reconnect_tp{};
|
||||
// 内部流程
|
||||
void schedule_recreate(Enum_Err<NetError> e);
|
||||
bool start_connect(); // 发起 connect(可能 EINPROGRESS)
|
||||
bool check_writeable(); // EINPROGRESS 后检查是否真连上
|
||||
void flush_send_buffer(); // 尝试把 ringbuffer 发出去
|
||||
};
|
||||
} // namespace Psc::socket
|
||||
@@ -0,0 +1,315 @@
|
||||
#include "TCP_Server.h"
|
||||
#include "Core/Statistics/Frequency_Limit.h"
|
||||
#include "Core/Statistics/Statistics.h"
|
||||
#include "Core/transmit_protocol/core/statistics.h"
|
||||
#include "Socket_p.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
TCP_Server::~TCP_Server() {
|
||||
if (socket_fd != -1) {
|
||||
socket_logger->c_debug({},{}, "析构关闭的Server_Socket" + TCP_Server::to_string());
|
||||
TCP_Server::close();
|
||||
}
|
||||
}
|
||||
|
||||
TCP_Server &TCP_Server::set_tcp_no_delay(bool _no_delay) {
|
||||
this->no_delay = _no_delay; return *this;
|
||||
}
|
||||
|
||||
void TCP_Server::close() {
|
||||
if (socket_fd == -1) {
|
||||
socket_logger->c_debug({},{}, "警告 重复关闭的Server_Socket,提前退出!" + LOG_POS);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << to_string() << "开始关闭, 剩余连接数:" << tcp_clients.size() << " ";
|
||||
|
||||
for (auto &kv : tcp_clients) {
|
||||
const Socket_FD fd = kv.first;
|
||||
auto &conn = kv.second;
|
||||
auto &info = conn->info;
|
||||
|
||||
oss << info.to_string();
|
||||
auto r = Psc::socket::close(fd);
|
||||
if (!r) {
|
||||
oss << info.to_string() + "连接关闭异常" + Psc::to_string(r.error())
|
||||
<< " ";
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = Psc::socket::close(socket_fd);
|
||||
if (!ret) {
|
||||
oss << to_string() + "关闭异常" + Psc::to_string(ret.error()) << " ";
|
||||
}
|
||||
|
||||
oss << " 关闭完毕!";
|
||||
socket_fd = -1;
|
||||
tcp_clients.clear();
|
||||
socket_logger->c_debug("Server_Socket", {}, oss.str());
|
||||
state = Not_Created;
|
||||
}
|
||||
|
||||
TCP_Server& TCP_Server::create() {
|
||||
auto ret = socket::create_socket_fd(Socket_Type::TCP);
|
||||
if (!ret) {
|
||||
socket_logger->c_debug("创建socket失败!", {}, to_string() + VAR_STR_1(state));
|
||||
return *this;
|
||||
}
|
||||
state = Not_Set_Field;
|
||||
socket_fd = ret.value();
|
||||
|
||||
auto r2 =
|
||||
socket::set_block(socket_fd, false)
|
||||
.and_then(
|
||||
[this]() { return socket::set_reuse_addr(socket_fd, true); })
|
||||
.and_then([this]() {
|
||||
return socket::TCP::set_no_delay(socket_fd, no_delay);
|
||||
})
|
||||
// .and_then([this]() { return socket::set_debug(socket_fd, true); })
|
||||
.and_then([this]() {
|
||||
return socket::set_buffer_size(socket_fd, recv_system_buffer_size);
|
||||
});
|
||||
|
||||
if (!r2) {
|
||||
socket_logger->c_debug("设置属性失败!", {}, to_string() + VAR_STR_2(socket_fd, state));
|
||||
}
|
||||
state = Not_bind;
|
||||
return *this;
|
||||
}
|
||||
TCP_Server &TCP_Server::set_connect_system_buffer_size(size_t size) {
|
||||
this->connect_system_buffer_size = size;
|
||||
return *this;
|
||||
}
|
||||
TCP_Server &TCP_Server::set_connect_user_buffer_size(size_t size) {
|
||||
this->connect_user_buffer_size = size;
|
||||
return *this;
|
||||
}
|
||||
TCP_Server &TCP_Server::set_recv_system_buffer_size(size_t size) {
|
||||
this->recv_system_buffer_size = size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool TCP_Server::listen(const std::string &ip, std::uint32_t port) {
|
||||
return listen(Psc::socket::Sockaddr_In(ip, port));
|
||||
}
|
||||
|
||||
std::string TCP_Server::to_string() {
|
||||
return "Server_Socket:[" + address->to_string() + "]";
|
||||
}
|
||||
|
||||
void TCP_Server::flush_clients() {
|
||||
if (state != Working) {
|
||||
return;
|
||||
}
|
||||
if (socket_fd == -1)
|
||||
return;
|
||||
|
||||
auto cur = accept();
|
||||
while (cur) {
|
||||
const Socket_FD fd = cur->info.fd;
|
||||
|
||||
// insert_or_assign 避免 operator[] 的二次构造/二次查找
|
||||
auto [it, inserted] = tcp_clients.insert_or_assign(fd, cur);
|
||||
auto &t = it->second;
|
||||
|
||||
socket_logger->c_debug(
|
||||
"", {}, to_string() + " 添加了连接 [" + t->to_string() + "]");
|
||||
cur = accept();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<TCP_Connect> TCP_Server::accept() const {
|
||||
|
||||
if (socket_fd == -1)
|
||||
return nullptr;
|
||||
|
||||
auto rc = socket::TCP::accept(socket_fd);
|
||||
if (!rc) {
|
||||
socket_logger->error("accept", {}, Ret_To_String(rc) + " " + LOG_POS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto cur = rc.value(); // std::optional<Accept_Info>
|
||||
if (!cur)
|
||||
return nullptr;
|
||||
|
||||
auto cfg =
|
||||
socket::set_block(cur->fd, false)
|
||||
.and_then([&]() { return socket::set_reuse_addr(cur->fd, true); })
|
||||
.and_then(
|
||||
[&]() { return socket::set_buffer_size(cur->fd, connect_system_buffer_size); });
|
||||
|
||||
if (!cfg) {
|
||||
// 先记录“配置失败原因”
|
||||
socket_logger->error("accept post-config failed", {},
|
||||
Ret_To_String(cfg) + " " + LOG_POS);
|
||||
|
||||
// 再尝试关闭,并记录“关闭是否成功”
|
||||
auto cr = socket::close(cur->fd);
|
||||
if (!cr) {
|
||||
socket_logger->error("accept post-config close failed", {},
|
||||
Ret_To_String(cr) + " " + LOG_POS);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ret = std::make_shared<TCP_Connect>();
|
||||
ret->info = *cur;
|
||||
ret->send_buffer.init(connect_user_buffer_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TCP_Server::listen(const Sockaddr_In &address) {
|
||||
this->address = address;
|
||||
auto ret = socket::bind(socket_fd, address);
|
||||
if (!ret) {
|
||||
state = Not_bind;
|
||||
return false;
|
||||
}
|
||||
auto r = socket::TCP::listen(socket_fd, 100);
|
||||
if (!r) {
|
||||
state = Not_Listen;
|
||||
return false;
|
||||
}
|
||||
state = Working;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCP_Server::write_to_all_clients(const std::string &data) {
|
||||
if (socket_fd == -1)
|
||||
return;
|
||||
// 去重,避免同一 fd 被多次 close/erase
|
||||
std::unordered_set<Socket_FD> need_close_fds;
|
||||
for (auto &kv : tcp_clients) {
|
||||
const Socket_FD fd = kv.first;
|
||||
auto &conn = kv.second;
|
||||
Accept_Info &info = conn->info;
|
||||
|
||||
bool ok = conn->send_buffer.write(data.c_str(), data.size());
|
||||
if (!ok) {
|
||||
conn->lose_speed.update(data.size());
|
||||
static Frequency_Limit fl;
|
||||
if (fl.test()) {
|
||||
socket_logger->c_debug(
|
||||
"lose", {},
|
||||
"警告:" + info.sockaddr.to_string() +
|
||||
" 发送缓冲已满,已丢弃本次数据。 pending=" +
|
||||
std::to_string(data.size()) +
|
||||
" add=" + std::to_string(data.size()) + " max_pending=" +
|
||||
std::to_string(max_pending_send_bytes) + get_error_message() + " " + LOG_POS);
|
||||
}
|
||||
// 不return 仍然尝试发送
|
||||
} else {
|
||||
conn->push_speed.update(data.size());
|
||||
}
|
||||
|
||||
std::string buf;
|
||||
buf.resize(2000);
|
||||
|
||||
while (true) {
|
||||
auto peek = conn->send_buffer.peek_best_effort(buf.data(), buf.size());
|
||||
//if (peek < 500) break;
|
||||
|
||||
if (peek == 0)
|
||||
break;
|
||||
auto r_send = socket::send(fd, buf.data(), peek);
|
||||
|
||||
if (r_send.has_value()) {
|
||||
const size_t n = static_cast<size_t>(r_send.value());
|
||||
conn->send_num.update(n);
|
||||
conn->send_buffer.skip_best_effort(n);
|
||||
if (n != peek) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const int sys_ec = r_send.error().native.value();
|
||||
|
||||
if (socket::is_client_need_close_ec(sys_ec)) {
|
||||
need_close_fds.insert(fd);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 统一关闭(不在遍历 map 时 erase)
|
||||
for (auto fd : need_close_fds) {
|
||||
auto it = tcp_clients.find(fd);
|
||||
if (it == tcp_clients.end())
|
||||
continue;
|
||||
|
||||
auto info = it->second->info;
|
||||
std::string log = to_string() + " 清除了连接 [" + info.to_string() + "]";
|
||||
|
||||
auto r = socket::close(fd);
|
||||
log += r ? " 成功!" : (" 失败!" + Psc::to_string(r.error()));
|
||||
|
||||
tcp_clients.erase(it);
|
||||
socket_logger->c_debug("", {}, log);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TCP_Server::Read_Info> TCP_Server::read_from_all_clients() {
|
||||
if (socket_fd == -1)
|
||||
return {};
|
||||
|
||||
std::vector<Read_Info> ret;
|
||||
|
||||
auto r_readable = socket::select_read(client_fds(), 0);
|
||||
if (!r_readable)
|
||||
return {};
|
||||
|
||||
auto readable = r_readable.value();
|
||||
|
||||
for (auto fd : readable) {
|
||||
auto it = tcp_clients.find(fd);
|
||||
if (it == tcp_clients.end())
|
||||
continue;
|
||||
|
||||
auto &conn = it->second;
|
||||
auto &info = conn->info;
|
||||
|
||||
auto r = socket::recv_all(fd);
|
||||
if (r.has_value()) {
|
||||
ret.push_back(Read_Info{info, r.value()});
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ec = r.error().native.value();
|
||||
if (is_client_need_close_ec(ec)) {
|
||||
auto r2 = socket::close(fd);
|
||||
if (!r2) {
|
||||
socket_logger->c_debug("tcp服务端", {},
|
||||
to_string() + VAR_STR_1(fd) + info.to_string() +
|
||||
"关闭连接失败!!!!" + LOG_POS);
|
||||
}
|
||||
tcp_clients.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Socket_FD> TCP_Server::client_fds() {
|
||||
std::vector<Socket_FD> ret;
|
||||
ret.reserve(tcp_clients.size());
|
||||
for (auto &c : tcp_clients)
|
||||
ret.push_back(c.first);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<TCP_Connect>> TCP_Server::get_all_clients() {
|
||||
std::vector<std::shared_ptr<TCP_Connect>> ret;
|
||||
ret.reserve(tcp_clients.size());
|
||||
for (auto &c : tcp_clients)
|
||||
ret.push_back(c.second);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Psc::socket
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include "Core/Base/RingBuffer.hpp"
|
||||
#include "Core/Statistics/Statistics.h"
|
||||
#include "Socket.h"
|
||||
namespace Psc::socket {
|
||||
|
||||
|
||||
|
||||
|
||||
class TCP_Connect {
|
||||
public:
|
||||
Accept_Info info;
|
||||
[[nodiscard]] std::string to_string() const {
|
||||
return info.to_string();
|
||||
}
|
||||
StreamRingBuffer_ST send_buffer;
|
||||
Speed_Statistics push_speed;
|
||||
Speed_Statistics lose_speed;
|
||||
Value_Statistics send_num;
|
||||
};
|
||||
|
||||
class TCP_Server : public Socket_Base {
|
||||
public:
|
||||
~TCP_Server() override;
|
||||
TCP_Server& set_tcp_no_delay(bool no_delay);
|
||||
void close() override;
|
||||
TCP_Server& create();
|
||||
TCP_Server& set_connect_system_buffer_size(size_t size);
|
||||
TCP_Server& set_connect_user_buffer_size(size_t size);
|
||||
TCP_Server& set_recv_system_buffer_size(size_t size);
|
||||
bool listen(const std::string &ip, std::uint32_t port);
|
||||
std::string to_string() override;
|
||||
void write_to_all_clients(const std::string &data);
|
||||
struct Read_Info {
|
||||
Accept_Info info;
|
||||
std::string data;
|
||||
};
|
||||
enum State {
|
||||
Not_Created,
|
||||
Not_Set_Field,
|
||||
Not_bind,
|
||||
Not_Listen,
|
||||
Working
|
||||
};
|
||||
State state = Not_Created;
|
||||
std::vector<Read_Info> read_from_all_clients();
|
||||
std::vector<Socket_FD> client_fds();
|
||||
std::vector<std::shared_ptr<TCP_Connect>> get_all_clients();
|
||||
void flush_clients();
|
||||
[[nodiscard]] std::shared_ptr<TCP_Connect> accept() const;
|
||||
bool listen(const Sockaddr_In &address);
|
||||
protected:
|
||||
std::map<Socket_FD, std::shared_ptr<TCP_Connect>> tcp_clients;
|
||||
size_t connect_user_buffer_size = 1024 * 1024;
|
||||
size_t connect_system_buffer_size = 4096 * 10;
|
||||
size_t max_pending_send_bytes = 1024 * 1024;
|
||||
size_t recv_system_buffer_size = 1024 * 1024;
|
||||
bool no_delay = true;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// UDP_Client.cpp
|
||||
#include "UDP_Client.h"
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
void UDP_Client::create() {
|
||||
// 关闭旧 fd
|
||||
if (socket_fd != -1) {
|
||||
(void)socket::close(socket_fd);
|
||||
socket_fd = -1;
|
||||
}
|
||||
connected = false;
|
||||
|
||||
auto rsf = socket::create_socket_fd(Socket_Type::UDP);
|
||||
if (!rsf) {
|
||||
// 你工程里有 logger 的话在这里打日志;否则静默失败
|
||||
return;
|
||||
}
|
||||
socket_fd = rsf.value();
|
||||
|
||||
// 常见选项(失败也不致命:按你的风格你可以选择 fail_fast 或记录日志后 return)
|
||||
auto r = socket::set_reuse(socket_fd, true).and_then([this]() {
|
||||
return socket::set_block(socket_fd, false);
|
||||
});
|
||||
if (!r) {
|
||||
socket_logger->c_debug({}, {}, to_string() + "设置属性失败,强制退出!");
|
||||
}
|
||||
}
|
||||
|
||||
bool UDP_Client::connect() {
|
||||
if (socket_fd == -1) {
|
||||
create();
|
||||
if (socket_fd == -1) return false;
|
||||
}
|
||||
|
||||
auto r = socket::connect(socket_fd, dest_address);
|
||||
if (!r) {
|
||||
connected = false;
|
||||
return false;
|
||||
}
|
||||
connected = r.value(); // 通常为 true
|
||||
return connected;
|
||||
}
|
||||
|
||||
std::string UDP_Client::read() {
|
||||
if (socket_fd == -1) return {};
|
||||
|
||||
Sockaddr_In peer{};
|
||||
auto r = socket::UDP::recvfrom(socket_fd, &peer, read_chunk_size);
|
||||
if (!r) {
|
||||
// 这里可以按需:返回空串 / 抛异常 / 记录日志
|
||||
return {};
|
||||
}
|
||||
return r.value();
|
||||
}
|
||||
|
||||
void UDP_Client::send(const std::string& data) {
|
||||
if (socket_fd == -1) {
|
||||
create();
|
||||
if (socket_fd == -1) return;
|
||||
}
|
||||
|
||||
// 为了不依赖 Sockaddr_In 的 ip/port 字段,这里走 “UDP connect + send()”
|
||||
if (!connected) {
|
||||
if (!connect()) return;
|
||||
}
|
||||
|
||||
(void)socket::send(socket_fd, data);
|
||||
}
|
||||
void UDP_Client::close() {
|
||||
if (socket_fd != -1) {
|
||||
(void)socket::close(socket_fd);
|
||||
socket_fd = -1;
|
||||
}
|
||||
connected = false;
|
||||
state = Not_Created;
|
||||
}
|
||||
std::string UDP_Client::to_string() {
|
||||
return VAR_STR_5(dest_address.to_string(), socket_fd, connected, blocking, read_chunk_size);
|
||||
}
|
||||
|
||||
} // namespace Psc::socket
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "Socket.h"
|
||||
namespace Psc::socket {
|
||||
class UDP_Client {
|
||||
public:
|
||||
void set_dest_address(const Sockaddr_In& _dest_address) { dest_address = _dest_address; }
|
||||
void set_dest_address(std::string ip, std::uint32_t port) { dest_address = Sockaddr_In(ip, port); }
|
||||
void create();
|
||||
bool connect();
|
||||
std::string read();
|
||||
void send(const std::string& data);
|
||||
Sockaddr_In dest_address;
|
||||
void close();
|
||||
std::string to_string();
|
||||
enum State {
|
||||
Not_Created,
|
||||
Not_Set_Field,
|
||||
Working,
|
||||
};
|
||||
State state = Not_Created;
|
||||
private:
|
||||
Socket_FD socket_fd = -1;
|
||||
bool connected = false;
|
||||
bool blocking = true; // 如需非阻塞,可改成参数/成员 setter
|
||||
int read_chunk_size = 1024 * 1024;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 重启的观测
|
||||
@@ -0,0 +1,149 @@
|
||||
// UDP_Server.cpp
|
||||
#include "UDP_Server.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Psc::socket {
|
||||
|
||||
static bool same_endpoint(const Sockaddr_In& a, const Sockaddr_In& b) {
|
||||
return a.ip == b.ip && a.port == b.port;
|
||||
}
|
||||
|
||||
void UDP_Server::create() {
|
||||
|
||||
bound = false;
|
||||
has_last_peer = false;
|
||||
clients.clear();
|
||||
|
||||
auto rsf = socket::create_socket_fd(Socket_Type::UDP);
|
||||
if (!rsf) {
|
||||
return;
|
||||
}
|
||||
socket_fd = rsf.value();
|
||||
state = Not_Set_Field;
|
||||
// 只能非阻塞
|
||||
auto r2 = socket::set_block(socket_fd, false)
|
||||
.and_then([this]() { return socket::set_reuse_addr(socket_fd, true); })
|
||||
.and_then([this]() { return socket::set_debug(socket_fd, true); })
|
||||
.and_then([this]() { return socket::set_buffer_size(socket_fd, buffer_size); });
|
||||
|
||||
if (!r2) {
|
||||
state = Not_Bind;
|
||||
}
|
||||
socket_fd = rsf.value();
|
||||
}
|
||||
|
||||
bool UDP_Server::bind() {
|
||||
if (socket_fd == -1) {
|
||||
create();
|
||||
if (socket_fd == -1) return false;
|
||||
}
|
||||
auto r = socket::bind(socket_fd, bind_address);
|
||||
if (!r) {
|
||||
bound = false;
|
||||
return false;
|
||||
}
|
||||
state = Working;
|
||||
bound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// std::string UDP_Server::read() {
|
||||
// if (!bound) {
|
||||
// if (!bind()) return {};
|
||||
// }
|
||||
//
|
||||
// Sockaddr_In peer{};
|
||||
// auto r = socket::UDP::recvfrom(socket_fd, &peer, read_chunk_size);
|
||||
// if (!r) {
|
||||
// return {};
|
||||
// }
|
||||
//
|
||||
// last_peer = peer;
|
||||
// has_last_peer = true;
|
||||
//
|
||||
// // 收集 client:去重加入
|
||||
// const bool exists =
|
||||
// std::any_of(clients.begin(), clients.end(),
|
||||
// [&](const Sockaddr_In& c) { return same_endpoint(c, peer); });
|
||||
// if (!exists) {
|
||||
// clients.push_back(peer);
|
||||
// }
|
||||
//
|
||||
// return r.value();
|
||||
// }
|
||||
|
||||
void UDP_Server::tick() {
|
||||
if (!bound) {
|
||||
if (!bind()) return;
|
||||
}
|
||||
|
||||
Sockaddr_In peer{};
|
||||
auto r = socket::UDP::recvfrom(socket_fd, &peer, read_chunk_size);
|
||||
if (!r) return;
|
||||
if (peer.ip == "" && peer.port == 0) return; // 不知道为什么会有这个
|
||||
|
||||
|
||||
last_peer = peer;
|
||||
has_last_peer = true;
|
||||
|
||||
// 连接/断开切换:存在 => 断开(移除),不存在 => 连接(加入)
|
||||
auto it = std::find_if(clients.begin(), clients.end(),
|
||||
[&](const Sockaddr_In& c) { return same_endpoint(c, peer); });
|
||||
|
||||
if (it == clients.end()) {
|
||||
// 第一次:连接
|
||||
clients.push_back(peer);
|
||||
// 可选:你可以在这里记录事件类型
|
||||
// last_event = PeerConnected;
|
||||
} else {
|
||||
// 第二次:断开
|
||||
clients.erase(it);
|
||||
// last_event = PeerDisconnected;
|
||||
}
|
||||
|
||||
// 如果“第一次/第二次包”只是控制含义,直接不向上层返回数据:
|
||||
return;
|
||||
|
||||
// 如果你仍想把该包内容交给上层处理,改成:
|
||||
// return r.value();
|
||||
}
|
||||
|
||||
void UDP_Server::close() {
|
||||
if (socket_fd != -1) {
|
||||
(void)socket::close(socket_fd);
|
||||
socket_fd = -1;
|
||||
state = Not_Created;
|
||||
}
|
||||
bound = false;
|
||||
has_last_peer = false;
|
||||
clients.clear();
|
||||
}
|
||||
|
||||
void UDP_Server::reply_last_peer(const std::string& data) {
|
||||
if (socket_fd == -1 || !bound || !has_last_peer) return;
|
||||
(void)socket::UDP::sendto(socket_fd, last_peer.ip, last_peer.port, data);
|
||||
}
|
||||
|
||||
void UDP_Server::send_to(const std::string& ip, uint16_t port, const std::string& data) {
|
||||
if (socket_fd == -1) {
|
||||
create();
|
||||
if (socket_fd == -1) return;
|
||||
}
|
||||
if (!bound) {
|
||||
if (!bind()) return;
|
||||
}
|
||||
(void)socket::UDP::sendto(socket_fd, ip, port, data);
|
||||
}
|
||||
|
||||
void UDP_Server::write_to_all_clients(const std::string& msg) {
|
||||
if (socket_fd == -1) return;
|
||||
if (!bound) {
|
||||
if (!bind()) return;
|
||||
}
|
||||
|
||||
for (const auto& c : clients) {
|
||||
(void)socket::UDP::sendto(socket_fd, c.ip, c.port, msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// UDP_Server.h
|
||||
#pragma once
|
||||
#include "Socket.h"
|
||||
namespace Psc::socket {
|
||||
class UDP_Server {
|
||||
public:
|
||||
void set_bind_address(const Sockaddr_In& addr) { bind_address = addr; }
|
||||
void set_bind_address(const std::string& ip, std::uint32_t port) {
|
||||
bind_address = Sockaddr_In(ip, port);
|
||||
}
|
||||
[[nodiscard]] std::string to_string() const {
|
||||
return bind_address.to_string();
|
||||
}
|
||||
void create();
|
||||
bool bind();
|
||||
|
||||
enum State {
|
||||
Not_Created,
|
||||
Not_Set_Field,
|
||||
Not_Bind,
|
||||
Working,
|
||||
};
|
||||
State state = Not_Created;
|
||||
|
||||
void tick();
|
||||
void close();
|
||||
// 回复到最近一次 read() 的对端(如果你想做 echo/server 常用)
|
||||
void reply_last_peer(const std::string& data);
|
||||
|
||||
// 直接发给指定地址(需要你自己提供 ip/port)
|
||||
void send_to(const std::string& ip, uint16_t port, const std::string& data);
|
||||
void write_to_all_clients(const std::string & msg);
|
||||
Sockaddr_In bind_address;
|
||||
std::vector<Sockaddr_In> clients;
|
||||
Socket_FD socket_fd = -1;
|
||||
bool bound = false;
|
||||
int read_chunk_size = 1024 * 1024;
|
||||
Sockaddr_In last_peer{};
|
||||
bool has_last_peer = false;
|
||||
size_t buffer_size = 4096 * 10;
|
||||
};
|
||||
|
||||
} // namespace Psc::socket
|
||||
Reference in New Issue
Block a user