- 重构Client类,使用事件队列管理连接和消息事件 - 增加消息队列大小限制和错误处理 - 优化数据包处理流程,减少内存拷贝 - 添加nullptr检查防止空指针访问 - 更新版本号至1.4 - 新增TimerQueue_GetTimerMess函数声明和hook
341 lines
10 KiB
C++
341 lines
10 KiB
C++
#pragma once
|
|
#include "Singleton.h"
|
|
#include "l_squirrel.h"
|
|
#include <array>
|
|
#include <asio.hpp>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <spdlog/sinks/basic_file_sink.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
extern HSQUIRRELVM v;
|
|
extern std::recursive_mutex SqMtx;
|
|
|
|
using namespace asio;
|
|
using asio::ip::tcp;
|
|
|
|
class Client
|
|
{
|
|
private:
|
|
struct PendingEvent
|
|
{
|
|
enum class Type
|
|
{
|
|
Connect,
|
|
Message
|
|
};
|
|
|
|
Type type = Type::Message;
|
|
std::string payload;
|
|
};
|
|
|
|
private:
|
|
tcp::resolver resolver;
|
|
tcp::socket socket;
|
|
tcp::resolver::results_type endpoint;
|
|
std::array<char, 8192> buffer;
|
|
std::array<char, 8192000> PackData;
|
|
std::size_t bufferedDataSize = 0;
|
|
std::deque<PendingEvent> pendingEvents;
|
|
std::size_t queuedMessageBytes = 0;
|
|
std::mutex pendingEventsMutex;
|
|
std::string Ip = "192.168.200.27";
|
|
// std::string Ip = "127.0.0.1";
|
|
std::string Port = "65109";
|
|
|
|
public:
|
|
bool ConnectState = false;
|
|
|
|
std::fstream file;
|
|
|
|
public:
|
|
Client(asio::io_context &io_context, std::string Ip, std::string Port)
|
|
: resolver(io_context),
|
|
socket(io_context)
|
|
{
|
|
endpoint = resolver.resolve(Ip, Port);
|
|
this->Ip = Ip;
|
|
this->Port = Port;
|
|
PackData.fill(0);
|
|
}
|
|
|
|
private:
|
|
enum : std::size_t
|
|
{
|
|
HeaderSize = 4,
|
|
MaxQueuedMessages = 10000,
|
|
MaxQueuedMessageBytes = 8 * 1024 * 1024
|
|
};
|
|
|
|
void ResetPacketBuffer()
|
|
{
|
|
bufferedDataSize = 0;
|
|
}
|
|
|
|
void InvokeGatewayConnectCallback()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(SqMtx);
|
|
SQInteger top = sq_gettop(v); // saves the stack size before the call
|
|
sq_pushroottable(v); // pushes the global table
|
|
sq_pushstring(v, _SC("OnGatewaySocketConnect"), -1);
|
|
if (SQ_SUCCEEDED(sq_get(v, -2)))
|
|
{
|
|
sq_pushroottable(v); // push the global table as this
|
|
sq_call(v, 1, SQFalse, SQTrue); // calls the function
|
|
}
|
|
sq_settop(v, top); // restores the original stack size
|
|
}
|
|
|
|
void InvokeGatewaySocketMsg(const std::string &message)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(SqMtx);
|
|
SQInteger top = sq_gettop(v); // saves the stack size before the call
|
|
sq_pushroottable(v); // pushes the global table
|
|
sq_pushstring(v, _SC("OnGatewaySocketMsg"), -1);
|
|
if (SQ_SUCCEEDED(sq_get(v, -2)))
|
|
{
|
|
sq_pushroottable(v);
|
|
sq_pushstring(v, message.c_str(), static_cast<SQInteger>(message.size()));
|
|
sq_call(v, 2, SQFalse, SQTrue);
|
|
}
|
|
sq_settop(v, top);
|
|
}
|
|
|
|
bool EnqueueConnectEvent()
|
|
{
|
|
std::lock_guard<std::mutex> lock(pendingEventsMutex);
|
|
PendingEvent event;
|
|
event.type = PendingEvent::Type::Connect;
|
|
pendingEvents.push_back(std::move(event));
|
|
return true;
|
|
}
|
|
|
|
bool EnqueueMessageEvent(const char *message, std::size_t length)
|
|
{
|
|
std::lock_guard<std::mutex> lock(pendingEventsMutex);
|
|
if (pendingEvents.size() >= MaxQueuedMessages)
|
|
{
|
|
spdlog::error("Gateway pending message queue is full. queued_events={}, limit={}",
|
|
static_cast<unsigned long long>(pendingEvents.size()),
|
|
static_cast<unsigned long long>(MaxQueuedMessages));
|
|
return false;
|
|
}
|
|
|
|
if ((queuedMessageBytes + length) > MaxQueuedMessageBytes)
|
|
{
|
|
spdlog::error("Gateway pending message bytes exceeded. queued_bytes={}, incoming={}, limit={}",
|
|
static_cast<unsigned long long>(queuedMessageBytes),
|
|
static_cast<unsigned long long>(length),
|
|
static_cast<unsigned long long>(MaxQueuedMessageBytes));
|
|
return false;
|
|
}
|
|
|
|
PendingEvent event;
|
|
event.type = PendingEvent::Type::Message;
|
|
event.payload.assign(message, length);
|
|
queuedMessageBytes += length;
|
|
pendingEvents.push_back(std::move(event));
|
|
return true;
|
|
}
|
|
|
|
void DrainPendingEvents(std::deque<PendingEvent> &events)
|
|
{
|
|
std::lock_guard<std::mutex> lock(pendingEventsMutex);
|
|
if (pendingEvents.empty())
|
|
return;
|
|
events.swap(pendingEvents);
|
|
queuedMessageBytes = 0;
|
|
}
|
|
|
|
bool AppendReceivedData(std::size_t bytes_transferred)
|
|
{
|
|
if (bytes_transferred == 0)
|
|
return true;
|
|
|
|
if ((bufferedDataSize + bytes_transferred) > PackData.size())
|
|
{
|
|
spdlog::error("Gateway receive buffer overflow. buffered={}, incoming={}, capacity={}",
|
|
static_cast<unsigned long long>(bufferedDataSize),
|
|
static_cast<unsigned long long>(bytes_transferred),
|
|
static_cast<unsigned long long>(PackData.size()));
|
|
return false;
|
|
}
|
|
|
|
std::memcpy(PackData.data() + bufferedDataSize, buffer.data(), bytes_transferred);
|
|
bufferedDataSize += bytes_transferred;
|
|
return true;
|
|
}
|
|
|
|
bool ProcessBufferedPackets()
|
|
{
|
|
std::size_t offset = 0;
|
|
while ((bufferedDataSize - offset) >= HeaderSize)
|
|
{
|
|
const int packSize = ByteLittleToInt(reinterpret_cast<unsigned char *>(PackData.data() + offset));
|
|
if (packSize <= 0)
|
|
{
|
|
spdlog::error("Gateway packet length is invalid: {}", packSize);
|
|
return false;
|
|
}
|
|
|
|
const std::size_t payloadSize = static_cast<std::size_t>(packSize);
|
|
if (payloadSize > (PackData.size() - HeaderSize))
|
|
{
|
|
spdlog::error("Gateway packet length exceeds buffer. payload={}, capacity={}",
|
|
static_cast<unsigned long long>(payloadSize),
|
|
static_cast<unsigned long long>(PackData.size() - HeaderSize));
|
|
return false;
|
|
}
|
|
|
|
if ((bufferedDataSize - offset - HeaderSize) < payloadSize)
|
|
break;
|
|
|
|
if (!EnqueueMessageEvent(PackData.data() + offset + HeaderSize, payloadSize))
|
|
return false;
|
|
offset += HeaderSize + payloadSize;
|
|
}
|
|
|
|
if (offset == 0)
|
|
return true;
|
|
|
|
const std::size_t remaining = bufferedDataSize - offset;
|
|
if (remaining > 0)
|
|
std::memmove(PackData.data(), PackData.data() + offset, remaining);
|
|
bufferedDataSize = remaining;
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
void start()
|
|
{
|
|
async_connect(socket, endpoint, [this](const asio::error_code &error, const tcp::endpoint &connected_endpoint)
|
|
{
|
|
if (!error) {
|
|
ConnectState = true;
|
|
ResetPacketBuffer();
|
|
EnqueueConnectEvent();
|
|
read();
|
|
} else {
|
|
// std::cerr << "Error connecting to server: " << error.message() << std::endl;
|
|
start();
|
|
} });
|
|
}
|
|
|
|
void read()
|
|
{
|
|
socket.async_read_some(asio::buffer(buffer), [this](const asio::error_code &error, std::size_t bytes_transferred)
|
|
{
|
|
if (!error) {
|
|
if (!AppendReceivedData(bytes_transferred) || !ProcessBufferedPackets()) {
|
|
reconnect();
|
|
return;
|
|
}
|
|
read();
|
|
} else {
|
|
// std::cerr << "Error reading data: " << error.message() << std::endl;
|
|
usleep(10);
|
|
reconnect();
|
|
} });
|
|
}
|
|
|
|
void reconnect()
|
|
{
|
|
ConnectState = false;
|
|
ResetPacketBuffer();
|
|
std::cout << "Gateway socket disconnected, reconnecting." << std::endl;
|
|
asio::error_code close_error;
|
|
socket.close(close_error);
|
|
endpoint = resolver.resolve(Ip, Port);
|
|
async_connect(socket, endpoint, [this](const asio::error_code &error, const tcp::endpoint &connected_endpoint)
|
|
{
|
|
if (!error) {
|
|
ConnectState = true;
|
|
ResetPacketBuffer();
|
|
EnqueueConnectEvent();
|
|
read();
|
|
} else {
|
|
reconnect();
|
|
} });
|
|
}
|
|
|
|
void send(unsigned char *message, int Length)
|
|
{
|
|
std::shared_ptr<std::vector<unsigned char> > payload(new std::vector<unsigned char>(message, message + Length));
|
|
async_write(socket, asio::buffer(*payload), [payload](const asio::error_code &error, std::size_t bytes_transferred)
|
|
{
|
|
if (!error) {
|
|
// std::cout << "Message sent" << std::endl;
|
|
} else {
|
|
std::cerr << "Error sending message: " << error.message() << std::endl;
|
|
} });
|
|
}
|
|
|
|
void ClearPack()
|
|
{
|
|
ResetPacketBuffer();
|
|
}
|
|
|
|
void PackLogic()
|
|
{
|
|
std::deque<PendingEvent> events;
|
|
DrainPendingEvents(events);
|
|
while (!events.empty())
|
|
{
|
|
PendingEvent event = std::move(events.front());
|
|
events.pop_front();
|
|
if (event.type == PendingEvent::Type::Connect)
|
|
{
|
|
InvokeGatewayConnectCallback();
|
|
continue;
|
|
}
|
|
|
|
InvokeGatewaySocketMsg(event.payload);
|
|
}
|
|
}
|
|
|
|
static int ByteLittleToInt(unsigned char *Count)
|
|
{
|
|
int int1 = Count[0] & 0xff;
|
|
int int2 = (Count[1] & 0xff) << 8;
|
|
int int3 = (Count[2] & 0xff) << 16;
|
|
int int4 = (Count[3] & 0xff) << 24;
|
|
|
|
return int1 | int2 | int3 | int4;
|
|
}
|
|
};
|
|
|
|
class l_socket
|
|
{
|
|
public:
|
|
private:
|
|
l_socket() {} // private constructor to prevent instantiation
|
|
l_socket(const l_socket &) = delete; // disable copy constructor
|
|
l_socket &operator=(const l_socket &) = delete; // disable assignment operator
|
|
Client *ClientObj = nullptr;
|
|
|
|
public:
|
|
bool InitState = false;
|
|
static l_socket &getInstance()
|
|
{
|
|
static l_socket instance;
|
|
return instance;
|
|
}
|
|
|
|
void InitSqr();
|
|
void InitPackLogic();
|
|
void Init(std::string Ip, std::string Port);
|
|
void Send(const SQChar *Pck);
|
|
void Logic();
|
|
void IntToByteLittle(unsigned char *b, int Count);
|
|
|
|
public:
|
|
};
|