codex-5.2: новая модель объектов взаимодействия с сетью
This commit is contained in:
483
Src/Common/Net2.cpp
Normal file
483
Src/Common/Net2.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
#include "Net2.hpp"
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
namespace LV::Net2 {
|
||||
|
||||
using namespace TOS;
|
||||
|
||||
namespace {
|
||||
|
||||
struct HeaderFields {
|
||||
uint32_t size = 0;
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
FrameFlags flags = FrameFlags::None;
|
||||
uint32_t streamId = 0;
|
||||
};
|
||||
|
||||
std::array<std::byte, AsyncSocket::kHeaderSize> encodeHeader(const HeaderFields &h) {
|
||||
std::array<std::byte, AsyncSocket::kHeaderSize> out{};
|
||||
uint32_t sizeNet = detail::toNetwork(h.size);
|
||||
uint16_t typeNet = detail::toNetwork(h.type);
|
||||
uint32_t streamNet = detail::toNetwork(h.streamId);
|
||||
|
||||
std::memcpy(out.data(), &sizeNet, sizeof(sizeNet));
|
||||
std::memcpy(out.data() + 4, &typeNet, sizeof(typeNet));
|
||||
out[6] = std::byte(static_cast<uint8_t>(h.priority));
|
||||
out[7] = std::byte(static_cast<uint8_t>(h.flags));
|
||||
std::memcpy(out.data() + 8, &streamNet, sizeof(streamNet));
|
||||
return out;
|
||||
}
|
||||
|
||||
HeaderFields decodeHeader(const std::array<std::byte, AsyncSocket::kHeaderSize> &in) {
|
||||
HeaderFields h{};
|
||||
std::memcpy(&h.size, in.data(), sizeof(h.size));
|
||||
std::memcpy(&h.type, in.data() + 4, sizeof(h.type));
|
||||
h.priority = static_cast<Priority>(std::to_integer<uint8_t>(in[6]));
|
||||
h.flags = static_cast<FrameFlags>(std::to_integer<uint8_t>(in[7]));
|
||||
std::memcpy(&h.streamId, in.data() + 8, sizeof(h.streamId));
|
||||
|
||||
h.size = detail::fromNetwork(h.size);
|
||||
h.type = detail::fromNetwork(h.type);
|
||||
h.streamId = detail::fromNetwork(h.streamId);
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PacketWriter& PacketWriter::writeBytes(std::span<const std::byte> data) {
|
||||
Buffer.insert(Buffer.end(), data.begin(), data.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
PacketWriter& PacketWriter::writeString(std::string_view str) {
|
||||
write<uint32_t>(static_cast<uint32_t>(str.size()));
|
||||
auto bytes = std::as_bytes(std::span<const char>(str.data(), str.size()));
|
||||
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<std::byte> PacketWriter::release() {
|
||||
std::vector<std::byte> out = std::move(Buffer);
|
||||
Buffer.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
void PacketWriter::clear() {
|
||||
Buffer.clear();
|
||||
}
|
||||
|
||||
PacketReader::PacketReader(std::span<const std::byte> data)
|
||||
: Data(data)
|
||||
{
|
||||
}
|
||||
|
||||
void PacketReader::readBytes(std::span<std::byte> out) {
|
||||
require(out.size());
|
||||
std::memcpy(out.data(), Data.data() + Pos, out.size());
|
||||
Pos += out.size();
|
||||
}
|
||||
|
||||
std::string PacketReader::readString() {
|
||||
uint32_t size = read<uint32_t>();
|
||||
require(size);
|
||||
std::string out(size, '\0');
|
||||
std::memcpy(out.data(), Data.data() + Pos, size);
|
||||
Pos += size;
|
||||
return out;
|
||||
}
|
||||
|
||||
void PacketReader::require(size_t size) {
|
||||
if(Data.size() - Pos < size)
|
||||
MAKE_ERROR("Net2::PacketReader: not enough data");
|
||||
}
|
||||
|
||||
SocketServer::SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port)
|
||||
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
|
||||
{
|
||||
assert(onConnect);
|
||||
co_spawn(run(std::move(onConnect)));
|
||||
}
|
||||
|
||||
bool SocketServer::isStopped() const {
|
||||
return !Acceptor.is_open();
|
||||
}
|
||||
|
||||
uint16_t SocketServer::getPort() const {
|
||||
return Acceptor.local_endpoint().port();
|
||||
}
|
||||
|
||||
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
|
||||
while(true) {
|
||||
try {
|
||||
co_spawn(onConnect(co_await Acceptor.async_accept()));
|
||||
} catch(const std::exception &exc) {
|
||||
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
|
||||
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncSocket::SendQueue::SendQueue(asio::io_context &ioc)
|
||||
: semaphore(ioc)
|
||||
{
|
||||
semaphore.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
bool AsyncSocket::SendQueue::empty() const {
|
||||
for(const auto &queue : queues) {
|
||||
if(!queue.empty())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncSocket::AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits)
|
||||
: AsyncObject(ioc), LimitsCfg(limits), Socket(std::move(socket)), Outgoing(ioc)
|
||||
{
|
||||
Context = std::make_shared<AsyncContext>();
|
||||
|
||||
boost::asio::socket_base::linger optionLinger(true, 4);
|
||||
Socket.set_option(optionLinger);
|
||||
boost::asio::ip::tcp::no_delay optionNoDelay(true);
|
||||
Socket.set_option(optionNoDelay);
|
||||
|
||||
co_spawn(sendLoop());
|
||||
}
|
||||
|
||||
AsyncSocket::~AsyncSocket() {
|
||||
if(Context)
|
||||
Context->needShutdown.store(true);
|
||||
|
||||
{
|
||||
boost::lock_guard lock(Outgoing.mtx);
|
||||
Outgoing.semaphore.cancel();
|
||||
WorkDeadline.cancel();
|
||||
}
|
||||
|
||||
if(Socket.is_open())
|
||||
try { Socket.close(); } catch(...) {}
|
||||
}
|
||||
|
||||
void AsyncSocket::enqueue(OutgoingMessage &&msg) {
|
||||
if(msg.payload.size() > LimitsCfg.maxMessageSize) {
|
||||
setError("Net2::AsyncSocket: message too large");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
boost::unique_lock lock(Outgoing.mtx);
|
||||
const size_t msgSize = msg.payload.size();
|
||||
const size_t lowIndex = static_cast<size_t>(Priority::Low);
|
||||
|
||||
if(msg.priority == Priority::Low) {
|
||||
while(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes && !Outgoing.queues[lowIndex].empty()) {
|
||||
Outgoing.bytesInQueue -= Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.bytesInLow -= Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.queues[lowIndex].pop_front();
|
||||
}
|
||||
if(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
|
||||
dropLow(msgSize);
|
||||
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
|
||||
if(msg.dropIfOverloaded)
|
||||
return;
|
||||
setError("Net2::AsyncSocket: send queue overflow");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t idx = static_cast<size_t>(msg.priority);
|
||||
Outgoing.bytesInQueue += msgSize;
|
||||
if(msg.priority == Priority::Low)
|
||||
Outgoing.bytesInLow += msgSize;
|
||||
Outgoing.queues[idx].push_back(std::move(msg));
|
||||
|
||||
if(Outgoing.waiting) {
|
||||
Outgoing.waiting = false;
|
||||
Outgoing.semaphore.cancel();
|
||||
Outgoing.semaphore.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
}
|
||||
|
||||
coro<IncomingMessage> AsyncSocket::readMessage() {
|
||||
while(true) {
|
||||
std::array<std::byte, kHeaderSize> headerBytes{};
|
||||
co_await readExact(headerBytes.data(), headerBytes.size());
|
||||
HeaderFields header = decodeHeader(headerBytes);
|
||||
|
||||
if(header.size > LimitsCfg.maxFrameSize)
|
||||
MAKE_ERROR("Net2::AsyncSocket: frame too large");
|
||||
|
||||
std::vector<std::byte> chunk(header.size);
|
||||
if(header.size)
|
||||
co_await readExact(chunk.data(), chunk.size());
|
||||
|
||||
if(header.streamId != 0) {
|
||||
if(Fragments.size() >= LimitsCfg.maxOpenStreams && !Fragments.contains(header.streamId))
|
||||
MAKE_ERROR("Net2::AsyncSocket: too many open streams");
|
||||
|
||||
FragmentState &state = Fragments[header.streamId];
|
||||
if(state.data.empty()) {
|
||||
state.type = header.type;
|
||||
state.priority = header.priority;
|
||||
}
|
||||
|
||||
if(state.data.size() + chunk.size() > LimitsCfg.maxMessageSize)
|
||||
MAKE_ERROR("Net2::AsyncSocket: reassembled message too large");
|
||||
|
||||
state.data.insert(state.data.end(), chunk.begin(), chunk.end());
|
||||
|
||||
if(!hasFlag(header.flags, FrameFlags::HasMore)) {
|
||||
IncomingMessage msg{state.type, state.priority, std::move(state.data)};
|
||||
Fragments.erase(header.streamId);
|
||||
co_return msg;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hasFlag(header.flags, FrameFlags::HasMore))
|
||||
MAKE_ERROR("Net2::AsyncSocket: stream id missing for fragmented frame");
|
||||
|
||||
IncomingMessage msg{header.type, header.priority, std::move(chunk)};
|
||||
co_return msg;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::readLoop(std::function<coro<>(IncomingMessage&&)> onMessage) {
|
||||
while(isAlive()) {
|
||||
IncomingMessage msg = co_await readMessage();
|
||||
co_await onMessage(std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::closeRead() {
|
||||
if(Socket.is_open() && !Context->readClosed.exchange(true)) {
|
||||
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::close() {
|
||||
if(Context)
|
||||
Context->needShutdown.store(true);
|
||||
if(Socket.is_open())
|
||||
try { Socket.close(); } catch(...) {}
|
||||
}
|
||||
|
||||
bool AsyncSocket::isAlive() const {
|
||||
return Context && !Context->needShutdown.load() && !Context->senderStopped.load() && Socket.is_open();
|
||||
}
|
||||
|
||||
std::string AsyncSocket::getError() const {
|
||||
boost::lock_guard lock(Context->errorMtx);
|
||||
return Context->error;
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendLoop() {
|
||||
try {
|
||||
while(!Context->needShutdown.load()) {
|
||||
OutgoingMessage msg;
|
||||
{
|
||||
boost::unique_lock lock(Outgoing.mtx);
|
||||
if(Outgoing.empty()) {
|
||||
Outgoing.waiting = true;
|
||||
auto coroutine = Outgoing.semaphore.async_wait();
|
||||
lock.unlock();
|
||||
try { co_await std::move(coroutine); } catch(...) {}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!popNext(msg))
|
||||
continue;
|
||||
}
|
||||
|
||||
co_await sendMessage(std::move(msg));
|
||||
}
|
||||
} catch(const std::exception &exc) {
|
||||
setError(exc.what());
|
||||
} catch(...) {
|
||||
setError("Net2::AsyncSocket: send loop stopped");
|
||||
}
|
||||
|
||||
Context->senderStopped.store(true);
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendMessage(OutgoingMessage &&msg) {
|
||||
const size_t total = msg.payload.size();
|
||||
if(total <= LimitsCfg.maxFrameSize) {
|
||||
co_await sendFrame(msg.type, msg.priority, FrameFlags::None, 0, msg.payload);
|
||||
co_return;
|
||||
}
|
||||
|
||||
if(!msg.allowFragment) {
|
||||
setError("Net2::AsyncSocket: message requires fragmentation");
|
||||
close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
uint32_t streamId = NextStreamId++;
|
||||
if(streamId == 0)
|
||||
streamId = NextStreamId++;
|
||||
|
||||
size_t offset = 0;
|
||||
while(offset < total) {
|
||||
const size_t chunk = std::min(LimitsCfg.maxFrameSize, total - offset);
|
||||
const bool more = (offset + chunk) < total;
|
||||
FrameFlags flags = more ? FrameFlags::HasMore : FrameFlags::None;
|
||||
std::span<const std::byte> view(msg.payload.data() + offset, chunk);
|
||||
co_await sendFrame(msg.type, msg.priority, flags, streamId, view);
|
||||
offset += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
|
||||
std::span<const std::byte> payload) {
|
||||
HeaderFields header{
|
||||
.size = static_cast<uint32_t>(payload.size()),
|
||||
.type = type,
|
||||
.priority = priority,
|
||||
.flags = flags,
|
||||
.streamId = streamId
|
||||
};
|
||||
auto headerBytes = encodeHeader(header);
|
||||
std::array<asio::const_buffer, 2> buffers{
|
||||
asio::buffer(headerBytes),
|
||||
asio::buffer(payload.data(), payload.size())
|
||||
};
|
||||
if(payload.empty())
|
||||
co_await asio::async_write(Socket, asio::buffer(headerBytes));
|
||||
else
|
||||
co_await asio::async_write(Socket, buffers);
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::readExact(std::byte *data, size_t size) {
|
||||
if(size == 0)
|
||||
co_return;
|
||||
co_await asio::async_read(Socket, asio::buffer(data, size));
|
||||
}
|
||||
|
||||
bool AsyncSocket::popNext(OutgoingMessage &out) {
|
||||
static constexpr int kWeights[4] = {8, 4, 2, 1};
|
||||
|
||||
for(int attempt = 0; attempt < 4; ++attempt) {
|
||||
const uint8_t idx = static_cast<uint8_t>((Outgoing.nextIndex + attempt) % 4);
|
||||
auto &queue = Outgoing.queues[idx];
|
||||
if(queue.empty())
|
||||
continue;
|
||||
|
||||
if(Outgoing.credits[idx] <= 0)
|
||||
Outgoing.credits[idx] = kWeights[idx];
|
||||
|
||||
if(Outgoing.credits[idx] <= 0)
|
||||
continue;
|
||||
|
||||
out = std::move(queue.front());
|
||||
queue.pop_front();
|
||||
Outgoing.credits[idx]--;
|
||||
Outgoing.nextIndex = idx;
|
||||
|
||||
const size_t msgSize = out.payload.size();
|
||||
Outgoing.bytesInQueue -= msgSize;
|
||||
if(idx == static_cast<uint8_t>(Priority::Low))
|
||||
Outgoing.bytesInLow -= msgSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; ++i)
|
||||
Outgoing.credits[i] = kWeights[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsyncSocket::dropLow(size_t needBytes) {
|
||||
const size_t lowIndex = static_cast<size_t>(Priority::Low);
|
||||
while(Outgoing.bytesInQueue + needBytes > LimitsCfg.maxQueueBytes && !Outgoing.queues[lowIndex].empty()) {
|
||||
const size_t size = Outgoing.queues[lowIndex].front().payload.size();
|
||||
Outgoing.bytesInQueue -= size;
|
||||
Outgoing.bytesInLow -= size;
|
||||
Outgoing.queues[lowIndex].pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncSocket::setError(const std::string &msg) {
|
||||
if(!Context)
|
||||
return;
|
||||
boost::lock_guard lock(Context->errorMtx);
|
||||
Context->error = msg;
|
||||
}
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string &address,
|
||||
std::function<void(const std::string&)> onProgress) {
|
||||
std::string progress;
|
||||
auto addLog = [&](const std::string &msg) {
|
||||
progress += '\n';
|
||||
progress += msg;
|
||||
if(onProgress)
|
||||
onProgress('\n' + msg);
|
||||
};
|
||||
|
||||
auto ioc = co_await asio::this_coro::executor;
|
||||
|
||||
addLog("Parsing address " + address);
|
||||
auto re = Str::match(address, "((?:\\[[\\d\\w:]+\\])|(?:[\\d\\.]+))(?:\\:(\\d+))?");
|
||||
|
||||
std::vector<std::tuple<tcp::endpoint, std::string>> eps;
|
||||
|
||||
if(!re) {
|
||||
re = Str::match(address, "([-_\\.\\w\\d]+)(?:\\:(\\d+))?");
|
||||
if(!re)
|
||||
MAKE_ERROR("Failed to parse address");
|
||||
|
||||
tcp::resolver resv{ioc};
|
||||
tcp::resolver::results_type result;
|
||||
|
||||
addLog("Resolving name...");
|
||||
result = co_await resv.async_resolve(*re->at(1), re->at(2) ? *re->at(2) : "7890");
|
||||
|
||||
addLog("Got " + std::to_string(result.size()) + " endpoints");
|
||||
for(auto iter : result) {
|
||||
std::string addr = iter.endpoint().address().to_string() + ':' + std::to_string(iter.endpoint().port());
|
||||
std::string hostname = iter.host_name();
|
||||
if(hostname == addr)
|
||||
addLog("ep: " + addr);
|
||||
else
|
||||
addLog("ep: " + hostname + " (" + addr + ')');
|
||||
|
||||
eps.emplace_back(iter.endpoint(), iter.host_name());
|
||||
}
|
||||
} else {
|
||||
eps.emplace_back(tcp::endpoint{asio::ip::make_address(*re->at(1)),
|
||||
static_cast<uint16_t>(re->at(2) ? Str::toVal<int>(*re->at(2)) : 7890)},
|
||||
*re->at(1));
|
||||
}
|
||||
|
||||
for(auto [ep, hostname] : eps) {
|
||||
addLog("Connecting to " + hostname + " (" + ep.address().to_string() + ':'
|
||||
+ std::to_string(ep.port()) + ")");
|
||||
try {
|
||||
tcp::socket sock{ioc};
|
||||
co_await sock.async_connect(ep);
|
||||
addLog("Connected");
|
||||
co_return sock;
|
||||
} catch(const std::exception &exc) {
|
||||
addLog(std::string("Connect failed: ") + exc.what());
|
||||
}
|
||||
}
|
||||
|
||||
MAKE_ERROR("Unable to connect to server");
|
||||
}
|
||||
|
||||
} // namespace LV::Net2
|
||||
227
Src/Common/Net2.hpp
Normal file
227
Src/Common/Net2.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#pragma once
|
||||
|
||||
#include "Async.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace LV::Net2 {
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr bool kLittleEndian = (std::endian::native == std::endian::little);
|
||||
|
||||
template<typename T>
|
||||
requires std::is_integral_v<T>
|
||||
inline T toNetwork(T value) {
|
||||
if constexpr (kLittleEndian && sizeof(T) > 1)
|
||||
return std::byteswap(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::is_floating_point_v<T>
|
||||
inline T toNetwork(T value) {
|
||||
using U = std::conditional_t<sizeof(T) == 4, uint32_t, uint64_t>;
|
||||
U u = std::bit_cast<U>(value);
|
||||
u = toNetwork(u);
|
||||
return std::bit_cast<T>(u);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T fromNetwork(T value) {
|
||||
return toNetwork(value);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum class Priority : uint8_t {
|
||||
Realtime = 0,
|
||||
High = 1,
|
||||
Normal = 2,
|
||||
Low = 3
|
||||
};
|
||||
|
||||
enum class FrameFlags : uint8_t {
|
||||
None = 0,
|
||||
HasMore = 1
|
||||
};
|
||||
|
||||
inline FrameFlags operator|(FrameFlags a, FrameFlags b) {
|
||||
return static_cast<FrameFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
}
|
||||
|
||||
inline bool hasFlag(FrameFlags value, FrameFlags flag) {
|
||||
return (static_cast<uint8_t>(value) & static_cast<uint8_t>(flag)) != 0;
|
||||
}
|
||||
|
||||
struct Limits {
|
||||
size_t maxFrameSize = 1 << 24;
|
||||
size_t maxMessageSize = 1 << 26;
|
||||
size_t maxQueueBytes = 1 << 27;
|
||||
size_t maxLowPriorityBytes = 1 << 26;
|
||||
size_t maxOpenStreams = 64;
|
||||
};
|
||||
|
||||
struct OutgoingMessage {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
bool dropIfOverloaded = false;
|
||||
bool allowFragment = true;
|
||||
std::vector<std::byte> payload;
|
||||
};
|
||||
|
||||
struct IncomingMessage {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
std::vector<std::byte> payload;
|
||||
};
|
||||
|
||||
class PacketWriter {
|
||||
public:
|
||||
PacketWriter& writeBytes(std::span<const std::byte> data);
|
||||
|
||||
template<typename T>
|
||||
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
|
||||
PacketWriter& write(T value) {
|
||||
T net = detail::toNetwork(value);
|
||||
std::array<std::byte, sizeof(T)> bytes{};
|
||||
std::memcpy(bytes.data(), &net, sizeof(T));
|
||||
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
PacketWriter& writeString(std::string_view str);
|
||||
|
||||
const std::vector<std::byte>& data() const { return Buffer; }
|
||||
std::vector<std::byte> release();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::vector<std::byte> Buffer;
|
||||
};
|
||||
|
||||
class PacketReader {
|
||||
public:
|
||||
explicit PacketReader(std::span<const std::byte> data);
|
||||
|
||||
template<typename T>
|
||||
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
|
||||
T read() {
|
||||
require(sizeof(T));
|
||||
T net{};
|
||||
std::memcpy(&net, Data.data() + Pos, sizeof(T));
|
||||
Pos += sizeof(T);
|
||||
return detail::fromNetwork(net);
|
||||
}
|
||||
|
||||
void readBytes(std::span<std::byte> out);
|
||||
std::string readString();
|
||||
bool empty() const { return Pos >= Data.size(); }
|
||||
size_t remaining() const { return Data.size() - Pos; }
|
||||
|
||||
private:
|
||||
void require(size_t size);
|
||||
|
||||
size_t Pos = 0;
|
||||
std::span<const std::byte> Data;
|
||||
};
|
||||
|
||||
class SocketServer : public AsyncObject {
|
||||
public:
|
||||
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0);
|
||||
bool isStopped() const;
|
||||
uint16_t getPort() const;
|
||||
|
||||
private:
|
||||
coro<void> run(std::function<coro<>(tcp::socket)> onConnect);
|
||||
|
||||
tcp::acceptor Acceptor;
|
||||
};
|
||||
|
||||
class AsyncSocket : public AsyncObject {
|
||||
public:
|
||||
static constexpr size_t kHeaderSize = 12;
|
||||
|
||||
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits = {});
|
||||
~AsyncSocket();
|
||||
|
||||
void enqueue(OutgoingMessage &&msg);
|
||||
coro<IncomingMessage> readMessage();
|
||||
coro<> readLoop(std::function<coro<>(IncomingMessage&&)> onMessage);
|
||||
|
||||
void closeRead();
|
||||
void close();
|
||||
bool isAlive() const;
|
||||
std::string getError() const;
|
||||
|
||||
private:
|
||||
struct FragmentState {
|
||||
uint16_t type = 0;
|
||||
Priority priority = Priority::Normal;
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
|
||||
struct AsyncContext {
|
||||
std::atomic_bool needShutdown{false};
|
||||
std::atomic_bool senderStopped{false};
|
||||
std::atomic_bool readClosed{false};
|
||||
boost::mutex errorMtx;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct SendQueue {
|
||||
boost::mutex mtx;
|
||||
bool waiting = false;
|
||||
asio::steady_timer semaphore;
|
||||
std::deque<OutgoingMessage> queues[4];
|
||||
size_t bytesInQueue = 0;
|
||||
size_t bytesInLow = 0;
|
||||
uint8_t nextIndex = 0;
|
||||
int credits[4] = {8, 4, 2, 1};
|
||||
|
||||
explicit SendQueue(asio::io_context &ioc);
|
||||
bool empty() const;
|
||||
};
|
||||
|
||||
coro<> sendLoop();
|
||||
coro<> sendMessage(OutgoingMessage &&msg);
|
||||
coro<> sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
|
||||
std::span<const std::byte> payload);
|
||||
|
||||
coro<> readExact(std::byte *data, size_t size);
|
||||
|
||||
bool popNext(OutgoingMessage &out);
|
||||
void dropLow(size_t needBytes);
|
||||
void setError(const std::string &msg);
|
||||
|
||||
Limits LimitsCfg;
|
||||
tcp::socket Socket;
|
||||
SendQueue Outgoing;
|
||||
std::shared_ptr<AsyncContext> Context;
|
||||
std::unordered_map<uint32_t, FragmentState> Fragments;
|
||||
uint32_t NextStreamId = 1;
|
||||
};
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string &address,
|
||||
std::function<void(const std::string&)> onProgress = nullptr);
|
||||
|
||||
} // namespace LV::Net2
|
||||
Reference in New Issue
Block a user