349 lines
10 KiB
C++
349 lines
10 KiB
C++
#include "socketactions.h"
|
|
#include "Logger.h"
|
|
#include <QCoreApplication>
|
|
#include <QNetworkInterface>
|
|
#include <QEventLoop>
|
|
#include <QMutexLocker>
|
|
#include <QAbstractSocket>
|
|
#include <QDateTime>
|
|
#include <string>
|
|
|
|
namespace sockAct {
|
|
|
|
struct SocketContext {
|
|
std::unique_ptr<QTcpSocket> socket;
|
|
QMutex sendMutex;
|
|
QMutex connectMutex;
|
|
bool isInitialized = false;
|
|
std::string lastError;
|
|
QHostAddress lastConnectedHost;
|
|
quint16 lastConnectedPort = 0;
|
|
qint64 lastSendTime = 0;
|
|
qint64 lastReceiveTime = 0;
|
|
int sendCount = 0;
|
|
int receiveCount = 0;
|
|
};
|
|
|
|
static SocketContext& context() { // Static context
|
|
static SocketContext ctx;
|
|
return ctx;
|
|
}
|
|
|
|
namespace {
|
|
bool ensureInitialized();
|
|
bool internalConnectToHost(const std::string& host, quint16 port, int timeoutMs);
|
|
std::string waitForResponse(int timeoutMs);
|
|
void logNetworkEvent(const std::string& message, LogLevel level);
|
|
void handleSocketError(QAbstractSocket::SocketError error);
|
|
void resetSocket();
|
|
QString toQString(const std::string& str);
|
|
std::string fromQString(const QString& str);
|
|
// Add UTF-8 encoding helper functions
|
|
QByteArray stringToUtf8Bytes(const std::string& str);
|
|
std::string bytesToUtf8String(const QByteArray& bytes);
|
|
}
|
|
|
|
bool sockInit() {
|
|
QMutexLocker locker(&context().connectMutex);
|
|
|
|
if (context().isInitialized) {
|
|
logNetworkEvent("[Socket] Initialization completed.", INFO);
|
|
return true;
|
|
}
|
|
|
|
if (!QNetworkInterface::allInterfaces().isEmpty()) { // Check Qt Network Module
|
|
logNetworkEvent("[Socket] Qt Networking module initializing completed.", INFO);
|
|
} else {
|
|
logNetworkEvent("[Socket] No avaliable network interfaces", WARNING);
|
|
}
|
|
|
|
context().isInitialized = true;
|
|
logNetworkEvent("[Socket] Initialization completed.", INFO);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool connectToHost(const std::string& host, quint16 port, int timeoutMs) {
|
|
if (!ensureInitialized()) {
|
|
return false;
|
|
}
|
|
|
|
QMutexLocker locker(&context().connectMutex);
|
|
|
|
QString qhost = toQString(host);
|
|
|
|
if (context().socket &&
|
|
context().lastConnectedHost.toString() == qhost &&
|
|
context().lastConnectedPort == port) {
|
|
if (context().socket->state() == QAbstractSocket::ConnectedState) {
|
|
logNetworkEvent(std::string("[Socket] Connected to server: ") + host + ":" + std::to_string(port), INFO);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return internalConnectToHost(host, port, timeoutMs);
|
|
}
|
|
|
|
std::string tcpSend(const std::string& data, int timeoutMs) {
|
|
if (!ensureInitialized()) {
|
|
return std::string();
|
|
}
|
|
|
|
QMutexLocker locker(&context().sendMutex);
|
|
|
|
if (!context().socket || context().socket->state() != QAbstractSocket::ConnectedState) {
|
|
logNetworkEvent("[Socket] No connection avaliable, cannot send any data.", ERROR);
|
|
return std::string();
|
|
}
|
|
|
|
// Convert string to UTF-8 bytes for transmission
|
|
QByteArray sendData = stringToUtf8Bytes(data);
|
|
|
|
qint64 bytesWritten = context().socket->write(sendData); // Send data
|
|
context().lastSendTime = QDateTime::currentMSecsSinceEpoch();
|
|
context().sendCount++;
|
|
|
|
if (bytesWritten == -1) {
|
|
QString errorStr = context().socket->errorString();
|
|
logNetworkEvent("[Socket] Failed to send data: " + fromQString(errorStr), ERROR);
|
|
return std::string();
|
|
}
|
|
|
|
if (bytesWritten != sendData.size()) {
|
|
logNetworkEvent("[Socket] Data send not complete, expected to send " + std::to_string(sendData.size()) +
|
|
" bytes, " + std::to_string(bytesWritten) + " bytes sent", WARNING);
|
|
}
|
|
|
|
logNetworkEvent("[Socket] " + std::to_string(bytesWritten) + " bytes has been sent to sevrer", DEBUG);
|
|
|
|
std::string response = waitForResponse(timeoutMs);
|
|
|
|
return response;
|
|
}
|
|
|
|
void clearSocket() {
|
|
QMutexLocker locker1(&context().connectMutex);
|
|
QMutexLocker locker2(&context().sendMutex);
|
|
|
|
if (context().socket) {
|
|
std::string serverInfo;
|
|
if (!context().lastConnectedHost.isNull()) {
|
|
serverInfo = fromQString(context().lastConnectedHost.toString()) + ":" +
|
|
std::to_string(context().lastConnectedPort);
|
|
}
|
|
|
|
logNetworkEvent("[Socket] Disconnecting from server " + serverInfo + "...", INFO);
|
|
|
|
if (context().socket->state() != QAbstractSocket::UnconnectedState) {
|
|
context().socket->abort();
|
|
}
|
|
|
|
context().socket->disconnect();
|
|
|
|
// Reset connection info
|
|
context().lastConnectedHost.clear();
|
|
context().lastConnectedPort = 0;
|
|
context().lastSendTime = 0;
|
|
context().lastReceiveTime = 0;
|
|
context().sendCount = 0;
|
|
context().receiveCount = 0;
|
|
|
|
logNetworkEvent("[Socket] Disconnected.", INFO);
|
|
}
|
|
}
|
|
|
|
bool isConnected() {
|
|
if (!context().socket) {
|
|
return false;
|
|
}
|
|
return context().socket->state() == QAbstractSocket::ConnectedState;
|
|
}
|
|
|
|
std::string getLastError() {
|
|
return context().lastError;
|
|
}
|
|
|
|
// ==================== Internal methods ====================
|
|
|
|
namespace {
|
|
|
|
QString toQString(const std::string& str) {
|
|
return QString::fromStdString(str);
|
|
}
|
|
|
|
std::string fromQString(const QString& str) {
|
|
return str.toStdString();
|
|
}
|
|
|
|
// Convert string to UTF-8 bytes
|
|
QByteArray stringToUtf8Bytes(const std::string& str) {
|
|
// Use QString to convert to UTF-8
|
|
QString qstr = QString::fromStdString(str);
|
|
return qstr.toUtf8();
|
|
}
|
|
|
|
// Convert UTF-8 bytes to string
|
|
std::string bytesToUtf8String(const QByteArray& bytes) {
|
|
// Convert from UTF-8 bytes to QString, then to std::string
|
|
QString qstr = QString::fromUtf8(bytes);
|
|
return qstr.toStdString();
|
|
}
|
|
|
|
bool ensureInitialized() {
|
|
if (!context().isInitialized) {
|
|
logNetworkEvent("[Socket] Not initialized.", ERROR);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool internalConnectToHost(const std::string& host, quint16 port, int timeoutMs) {
|
|
if (context().socket) {
|
|
context().socket->abort();
|
|
context().socket.reset();
|
|
}
|
|
|
|
|
|
context().socket.reset(new QTcpSocket());
|
|
|
|
QString qhost = toQString(host);
|
|
|
|
QObject::connect(context().socket.get(), &QTcpSocket::connected, [host, port]() {
|
|
logNetworkEvent("[Socket] Connected to server " + host + ":" + std::to_string(port), INFO);
|
|
});
|
|
|
|
QObject::connect(context().socket.get(), QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
|
|
[](QAbstractSocket::SocketError error) {
|
|
handleSocketError(error);
|
|
});
|
|
|
|
logNetworkEvent("[Socket] Connecting to server " + host + ":" + std::to_string(port) +
|
|
" (Timeout: " + std::to_string(timeoutMs) + "ms)...", INFO);
|
|
context().socket->connectToHost(qhost, port);
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
while (context().socket->state() == QAbstractSocket::ConnectingState) {
|
|
if (timer.elapsed() > timeoutMs) {
|
|
logNetworkEvent("[Socket] Connection to server " + host + ":" + std::to_string(port) + " timeout", WARNING);
|
|
context().socket->abort();
|
|
return false;
|
|
}
|
|
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
QThread::msleep(10);
|
|
}
|
|
|
|
if (context().socket->state() != QAbstractSocket::ConnectedState) {
|
|
QString errorStr = context().socket->errorString();
|
|
logNetworkEvent("[Sockct] Failed to connect to server " + host + ":" + std::to_string(port) +
|
|
" Reason: " + fromQString(errorStr), ERROR);
|
|
return false;
|
|
}
|
|
|
|
context().lastConnectedHost = context().socket->peerAddress();
|
|
context().lastConnectedPort = context().socket->peerPort();
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string waitForResponse(int timeoutMs) {
|
|
if (!context().socket) {
|
|
return std::string();
|
|
}
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
while (context().socket->bytesAvailable() == 0) {
|
|
if (timer.elapsed() > timeoutMs) {
|
|
logNetworkEvent("[Socket] Timeout when waiting for reply (" + std::to_string(timeoutMs) + "ms)", WARNING);
|
|
return std::string();
|
|
}
|
|
|
|
if (!context().socket->waitForReadyRead(100)) {
|
|
if (context().socket->state() != QAbstractSocket::ConnectedState) {
|
|
logNetworkEvent("[Socket] Connection lost.", ERROR);
|
|
return std::string();
|
|
}
|
|
}
|
|
}
|
|
|
|
QByteArray responseData = context().socket->readAll();
|
|
context().lastReceiveTime = QDateTime::currentMSecsSinceEpoch();
|
|
context().receiveCount++;
|
|
|
|
// Convert UTF-8 bytes to string
|
|
std::string response = bytesToUtf8String(responseData);
|
|
|
|
int waitTime = timer.elapsed();
|
|
logNetworkEvent("[Socket] Reply from server " + std::to_string(responseData.size()) +
|
|
" bytes, Time: " + std::to_string(waitTime) + "ms", DEBUG);
|
|
|
|
return response;
|
|
}
|
|
|
|
void logNetworkEvent(const std::string& message, LogLevel level) {
|
|
postLog(message, static_cast<int>(level));
|
|
}
|
|
|
|
void handleSocketError(QAbstractSocket::SocketError error) {
|
|
std::string errorMsg;
|
|
LogLevel level = ERROR;
|
|
|
|
switch (error) {
|
|
case QAbstractSocket::ConnectionRefusedError:
|
|
errorMsg = "[Socket] Connection refused.";
|
|
break;
|
|
case QAbstractSocket::RemoteHostClosedError:
|
|
errorMsg = "[Socket] Remote server closed connection.";
|
|
level = WARNING;
|
|
break;
|
|
case QAbstractSocket::HostNotFoundError:
|
|
errorMsg = "[Socket] No host found.";
|
|
break;
|
|
case QAbstractSocket::SocketAccessError:
|
|
errorMsg = "[Socket] Access error.";
|
|
break;
|
|
case QAbstractSocket::SocketResourceError:
|
|
errorMsg = "[Socket] Resource error.";
|
|
level = WARNING;
|
|
break;
|
|
case QAbstractSocket::SocketTimeoutError:
|
|
errorMsg = "[Socket] Timeout.";
|
|
level = WARNING;
|
|
break;
|
|
case QAbstractSocket::NetworkError:
|
|
errorMsg = "[Socket] Network fatal.";
|
|
level = FATAL;
|
|
break;
|
|
case QAbstractSocket::SslHandshakeFailedError:
|
|
errorMsg = "[Socket] TLS handshake error.";
|
|
break;
|
|
default:
|
|
errorMsg = "[Socket] Unknown error.";
|
|
break;
|
|
}
|
|
|
|
if (context().socket) {
|
|
QString errorStr = context().socket->errorString();
|
|
errorMsg += ": " + fromQString(errorStr);
|
|
}
|
|
|
|
logNetworkEvent(errorMsg, level);
|
|
context().lastError = errorMsg;
|
|
}
|
|
|
|
void resetSocket() {
|
|
if (context().socket) {
|
|
context().socket->disconnect();
|
|
context().socket.reset();
|
|
}
|
|
context().lastError.clear();
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace sockAct
|