Files
CPP_Core/Socket_old/Socket_linux.cpp
T
2026-06-16 10:56:40 +08:00

436 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
// 非阻塞 connectEINPROGRESS / 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 / ioctlWindows 需要 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 == 0UDP 允许 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