Initial commit
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user