diff --git a/.vscode/settings.json b/.vscode/settings.json index 6cd35a4..ebb2564 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -155,6 +155,8 @@ "qtablewidget": "cpp", "qapplication": "cpp", "text_encoding": "cpp", - "hash_map": "cpp" + "hash_map": "cpp", + "qreadwritelock": "cpp", + "qdatastream": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 742bdf2..ff07d17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,4 +26,5 @@ set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) add_subdirectory(Protocol) +add_subdirectory(Server) add_subdirectory(Test) diff --git a/Protocol/CMakeLists.txt b/Protocol/CMakeLists.txt index 797d0f4..20266dd 100644 --- a/Protocol/CMakeLists.txt +++ b/Protocol/CMakeLists.txt @@ -15,6 +15,9 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) set(SOURCES Protocol.cxx Protocol.h +InfoClient.h +InfoClient.cpp +InfoPack.hpp ) add_library(Protocol STATIC ${SOURCES}) diff --git a/Protocol/InfoClient.cpp b/Protocol/InfoClient.cpp new file mode 100644 index 0000000..8e5496a --- /dev/null +++ b/Protocol/InfoClient.cpp @@ -0,0 +1,13 @@ +#include "InfoClient.h" + +QDataStream& operator<<(QDataStream& data, const InfoClient& info) +{ + info.serialize(data); + return data; +} + +QDataStream& operator>>(QDataStream& data, InfoClient& info) +{ + info.deserialize(data); + return data; +} diff --git a/Protocol/InfoClient.h b/Protocol/InfoClient.h new file mode 100644 index 0000000..0072c65 --- /dev/null +++ b/Protocol/InfoClient.h @@ -0,0 +1,49 @@ +#ifndef COMSTRUCT_H +#define COMSTRUCT_H + +#include +#include +#include +#include +#include + +struct InfoClient { + QString id; + QString name; + + void serialize(QDataStream& data) const + { + data << id << name; + } + + void deserialize(QDataStream& data) + { + data >> id >> name; + } +}; + +QDataStream& operator<<(QDataStream& data, const InfoClient& info); +QDataStream& operator>>(QDataStream& data, InfoClient& info); + +struct InfoClientVec { + QVector vec; + + void serialize(QDataStream& data) const + { + data << vec.size(); + for (const auto& info : vec) { + data << info; + } + } + void deserialize(QDataStream& data) + { + qsizetype size; + data >> size; + vec.resize(size); + for (quint32 i = 0; i < size; ++i) { + data >> vec[i]; + } + } +}; + +#endif // COMSTRUCT_H \ No newline at end of file diff --git a/Protocol/InfoPack.hpp b/Protocol/InfoPack.hpp new file mode 100644 index 0000000..92b53dc --- /dev/null +++ b/Protocol/InfoPack.hpp @@ -0,0 +1,25 @@ +#ifndef INFO_PACK_HPP +#define INFO_PACK_HPP + +#include +#include +#include + +template QByteArray infoPack(const T& obj) +{ + QByteArray byteArray; + QDataStream stream(&byteArray, QIODevice::ReadWrite); + obj.serialize(stream); + stream.device()->seek(0); + return byteArray; +} + +template T infoUnpack(const QByteArray& byteArray) +{ + T obj; + QDataStream stream(byteArray); + obj.deserialize(stream); + return obj; +} + +#endif // INFO_PACK_HPP \ No newline at end of file diff --git a/Server/CMakeLists.txt b/Server/CMakeLists.txt new file mode 100644 index 0000000..e546675 --- /dev/null +++ b/Server/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +project(frelayServer LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network) + +add_executable(frelayServer Server.h Server.cpp main.cpp) +target_link_libraries(frelayServer PRIVATE Protocol) +target_link_libraries(frelayServer PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) \ No newline at end of file diff --git a/Server/Server.cpp b/Server/Server.cpp new file mode 100644 index 0000000..9692142 --- /dev/null +++ b/Server/Server.cpp @@ -0,0 +1,221 @@ +#include "Server.h" + +#include +#include + +#include "InfoClient.h" +#include "InfoPack.hpp" + +Server::Server(QObject* parent) : QTcpServer(parent) +{ + monitorTimer_ = new QTimer(this); + connect(monitorTimer_, &QTimer::timeout, this, &Server::monitorClients); +} + +Server::~Server() +{ + stopServer(); +} + +bool Server::startServer(quint16 port) +{ + if (!listen(QHostAddress::Any, port)) { + qWarning() << "Server start failed:" << errorString(); + return false; + } + + qDebug() << "Server started on port" << serverPort(); + monitorTimer_->start(30000); + return true; +} + +void Server::stopServer() +{ + monitorTimer_->stop(); + close(); + + QWriteLocker locker(&rwLock_); + for (auto& client : clients_) { + client->socket->disconnectFromHost(); + client->socket->deleteLater(); + } + clients_.clear(); +} + +void Server::onNewConnection() +{ + QTcpSocket* clientSocket = nextPendingConnection(); + QString clientId = QString("%1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort()); + + if (clients_.size() >= 100) { + qWarning() << "Client connection refused (max limit reached):" << clientId; + clientSocket->disconnectFromHost(); + return; + } + + auto client = QSharedPointer::create(); + client->socket = clientSocket; + client->id = clientId; + client->connectTime = QDateTime::currentSecsSinceEpoch(); + + connect(clientSocket, &QTcpSocket::readyRead, this, &Server::onReadyRead); + connect(clientSocket, &QTcpSocket::disconnected, this, &Server::onClientDisconnected); + + { + QWriteLocker locker(&rwLock_); + clients_.insert(clientId, client); + } + + qDebug() << "Client connected:" << clientId; + auto frame = QSharedPointer::create(); + frame->type = FBT_SER_MSG_YOURID; + frame->fid = "server"; + frame->tid = clientId; + frame->data = QString("Welcome client %1").arg(clientId).toUtf8(); + sendData(clientSocket, frame); +} + +void Server::onClientDisconnected() +{ + QTcpSocket* socket = qobject_cast(sender()); + if (!socket) { + return; + } + + QString clientId = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()); + + { + QWriteLocker locker(&rwLock_); + clients_.remove(clientId); + } + + qDebug() << "Client disconnected:" << clientId; + socket->deleteLater(); +} + +void Server::onReadyRead() +{ + QTcpSocket* socket = qobject_cast(sender()); + if (!socket) { + return; + } + + QString clientId = QString("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort()); + + QSharedPointer client; + { + QReadLocker locker(&rwLock_); + client = clients_.value(clientId); + } + + if (client) { + client->buffer.append(socket->readAll()); + processClientData(client); + } +} + +void Server::processClientData(QSharedPointer client) +{ + while (true) { + auto frame = Protocol::ParseBuffer(client->buffer); + if (frame.isNull()) { + break; + } + frame->fid = client->id; + + if (frame->type <= 30) { + frame->tid = "server"; + replyRequest(client, frame); + } else { + if (!forwardData(client, frame)) { + qWarning() << "Failed to forward data from" << client->id << "to" << frame->tid; + } + } + } +} + +bool Server::forwardData(QSharedPointer client, QSharedPointer frame) +{ + QSharedPointer targetClient; + { + QReadLocker locker(&rwLock_); + targetClient = clients_.value(frame->tid); + } + + if (targetClient) { + return sendData(targetClient->socket, frame); + } else { + auto errorFrame = QSharedPointer::create(); + errorFrame->type = FBT_SER_MSG_FORWARD_FAILED; + errorFrame->fid = "server"; + errorFrame->tid = client->id; + errorFrame->data = QString("Target client %1 not found").arg(frame->tid).toUtf8(); + return sendData(client->socket, errorFrame); + } +} + +void Server::replyRequest(QSharedPointer client, QSharedPointer frame) +{ + switch (frame->type) { + case FBT_SER_MSG_ASKCLIENTS: { + QByteArray clientList = getClients(); + auto replyFrame = QSharedPointer::create(); + replyFrame->type = FBT_SER_MSG_RESPONSE; + replyFrame->fid = "server"; + replyFrame->tid = client->id; + replyFrame->data = clientList; + sendData(client->socket, replyFrame); + break; + } + default: + qWarning() << "Unknown request type:" << frame->type; + break; + } +} + +bool Server::sendData(QTcpSocket* socket, QSharedPointer frame) +{ + if (!socket || !socket->isOpen() || frame.isNull()) { + return false; + } + + QByteArray data = Protocol::PackBuffer(frame); + if (data.isEmpty()) { + return false; + } + + return socket->write(data) == data.size(); +} + +void Server::monitorClients() +{ + qint64 now = QDateTime::currentSecsSinceEpoch(); + QWriteLocker locker(&rwLock_); + + for (auto it = clients_.begin(); it != clients_.end();) { + if (now - it.value()->connectTime > 300) { + qDebug() << "Disconnecting inactive client:" << it.value()->id; + it.value()->socket->disconnectFromHost(); + it = clients_.erase(it); + } else { + ++it; + } + } +} + +QByteArray Server::getClients() +{ + InfoClientVec infoClients; + + { + QReadLocker locker(&rwLock_); + for (auto& c : clients_) { + InfoClient infoClient; + infoClient.id = c->id; + infoClients.vec.append(infoClient); + } + } + + auto ret = infoPack(infoClients); + return ret; +} diff --git a/Server/Server.h b/Server/Server.h new file mode 100644 index 0000000..304b9cf --- /dev/null +++ b/Server/Server.h @@ -0,0 +1,50 @@ +// Server.h +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include +#include +#include + +#include "Protocol.h" + +class Server : public QTcpServer +{ + Q_OBJECT +public: + explicit Server(QObject* parent = nullptr); + ~Server(); + + bool startServer(quint16 port); + void stopServer(); + +private slots: + void onNewConnection(); + void onClientDisconnected(); + void onReadyRead(); + void monitorClients(); + +private: + QByteArray getClients(); + +private: + struct ClientInfo { + QTcpSocket* socket; + QString id; + qint64 connectTime; + QByteArray buffer; + }; + + void processClientData(QSharedPointer client); + bool forwardData(QSharedPointer client, QSharedPointer frame); + void replyRequest(QSharedPointer client, QSharedPointer frame); + bool sendData(QTcpSocket* socket, QSharedPointer frame); + + QMap> clients_; + QReadWriteLock rwLock_; + QTimer* monitorTimer_; +}; + +#endif // SERVER_H \ No newline at end of file diff --git a/Server/main.cpp b/Server/main.cpp new file mode 100644 index 0000000..b508d0c --- /dev/null +++ b/Server/main.cpp @@ -0,0 +1,17 @@ +#include + +#include "Server.h" + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + + Server server; + if (!server.startServer(9009)) { + return 1; + } + + qDebug() << "TCP server is running. Press Ctrl+C to exit..."; + + return app.exec(); +} \ No newline at end of file diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 9b3ce8e..375a863 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -4,5 +4,5 @@ project(frelayTest LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_executable(frelayTest protocolTest.cpp) +add_executable(frelayTest protocolTest.cpp infoTest.h infoTest.cpp) target_link_libraries(frelayTest PRIVATE Protocol) \ No newline at end of file diff --git a/Test/infoTest.cpp b/Test/infoTest.cpp new file mode 100644 index 0000000..b646938 --- /dev/null +++ b/Test/infoTest.cpp @@ -0,0 +1,83 @@ +#include "infoTest.h" + +#include +#include + +int infoTest1() +{ + InfoClient ic; + ic.id = "1234567890"; + ic.name = "Test"; + + InfoClientVec vec; + vec.vec.append(ic); + + QByteArray qa; + QBuffer buf(&qa); + buf.open(QIODevice::ReadWrite); + + QDataStream qs(&buf); + vec.serialize(qs); + buf.close(); + + qDebug() << "Serialized data size:" << qa.size(); + qDebug() << "Serialized data:" << qa.toHex(); + + QBuffer buf2(&qa); + buf2.open(QIODevice::ReadWrite); + QDataStream qs2(&buf2); + InfoClientVec vec2; + vec2.deserialize(qs2); + buf2.close(); + + return 0; +} + +int infoTest2() +{ + InfoClient ic; + ic.id = "1234567890"; + ic.name = "Test"; + + InfoClientVec vec; + vec.vec.append(ic); + + QByteArray qa; + QDataStream qs(&qa, QIODevice::ReadWrite); + vec.serialize(qs); + + qDebug() << "Serialized data size:" << qa.size(); + qDebug() << "Serialized data:" << qa.toHex(); + + qs.device()->seek(0); + InfoClientVec vec2; + vec2.deserialize(qs); + + return 0; +} + +void performanceTest() +{ + QByteArray data; + QElapsedTimer timer; + + timer.start(); + QDataStream stream(&data, QIODevice::ReadWrite); + for (int i = 0; i < 10000; ++i) { + stream << i; + stream.device()->seek(0); + int val; + stream >> val; + } + qDebug() << "Reuse stream:" << timer.elapsed() << "ms"; + + timer.start(); + for (int i = 0; i < 10000; ++i) { + QDataStream out(&data, QIODevice::WriteOnly); + out << i; + QDataStream in(&data, QIODevice::ReadOnly); + int val; + in >> val; + } + qDebug() << "New streams:" << timer.elapsed() << "ms"; +} \ No newline at end of file diff --git a/Test/infoTest.h b/Test/infoTest.h new file mode 100644 index 0000000..dac3ad4 --- /dev/null +++ b/Test/infoTest.h @@ -0,0 +1,10 @@ +#ifndef INFO_TEST_H_ +#define INFO_TEST_H_ + +#include + +int infoTest1(); +int infoTest2(); +void performanceTest(); + +#endif \ No newline at end of file diff --git a/Test/protocolTest.cpp b/Test/protocolTest.cpp index 4d6c740..265cd2c 100644 --- a/Test/protocolTest.cpp +++ b/Test/protocolTest.cpp @@ -1,7 +1,8 @@ #include #include +#include "infoTest.h" -int main() +int test1() { auto frame = QSharedPointer::create(); frame->type = FBT_CLI_BIN_FILEDATA; @@ -14,6 +15,11 @@ int main() qDebug() << "Packed data hex:" << packet.toHex(); auto ret = Protocol::ParseBuffer(packet); + return 0; +} +int main() +{ + performanceTest(); return 0; } \ No newline at end of file