1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Switch client-server communication to new API

This commit is contained in:
Ivan Savenko 2023-11-18 16:34:18 +02:00
parent de5227142b
commit 0a1153e1c6
34 changed files with 623 additions and 694 deletions

View File

@ -45,17 +45,17 @@
#include "../lib/mapping/CMapInfo.h"
#include "../lib/mapObjects/MiscObjects.h"
#include "../lib/modding/ModIncompatibility.h"
#include "../lib/network/NetworkClient.h"
#include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/Connection.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/CMemorySerializer.h"
#include "../lib/UnlockGuard.h"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/asio.hpp>
#include "../lib/serializer/Cast.h"
#include "LobbyClientNetPackVisitors.h"
@ -131,8 +131,16 @@ public:
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::~CServerHandler() = default;
CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
: state(EClientState::NONE)
, mx(std::make_shared<boost::recursive_mutex>())
, networkClient(std::make_unique<NetworkClient>(*this))
, client(nullptr)
, loadMode(0)
, campaignStateToSend(nullptr)
, campaignServerRestartLock(false)
{
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
//read from file to restore last session
@ -161,7 +169,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
myNames.push_back(settings["general"]["playerName"].String());
}
void CServerHandler::startLocalServerAndConnect()
void CServerHandler::startLocalServerAndConnect(const std::function<void()> & onConnected)
{
if(threadRunLocalServer)
threadRunLocalServer->join();
@ -169,17 +177,13 @@ void CServerHandler::startLocalServerAndConnect()
th->update();
auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
try
if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort()))
{
CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
logNetwork->error("Port is busy, check if another instance of vcmiserver is working");
CInfoWindow::showInfoDialog(errorMsg, {});
return;
}
catch(std::runtime_error & error)
{
//no connection means that port is not busy and we can start local server
}
#if defined(SINGLE_PROCESS_APP)
boost::condition_variable cond;
@ -243,38 +247,15 @@ void CServerHandler::startLocalServerAndConnect()
th->update(); //put breakpoint here to attach to server before it does something stupid
justConnectToServer(localhostAddress, 0);
justConnectToServer(localhostAddress, 0, onConnected);
logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
}
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port)
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port, const std::function<void()> & onConnected)
{
logNetwork->info("Establishing connection...");
state = EClientState::CONNECTING;
while(!c && state != EClientState::CONNECTION_CANCELLED)
{
try
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : getHostAddress(),
port ? port : getHostPort(),
NAME, uuid);
}
catch(std::runtime_error & error)
{
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what());
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
}
if(state == EClientState::CONNECTION_CANCELLED)
{
logNetwork->info("Connection aborted by player!");
return;
}
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
if(!addr.empty() && addr != getHostAddress())
{
@ -286,13 +267,39 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
Settings serverPort = settings.write["server"]["port"];
serverPort->Integer() = port;
}
networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort());
}
void CServerHandler::onConnectionFailed(const std::string & errorMessage)
{
if(state == EClientState::CONNECTION_CANCELLED)
{
logNetwork->info("Connection aborted by player!");
return;
}
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", errorMessage);
//FIXME: replace with asio timer
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
//FIXME: pass parameters from initial attempt
networkClient->start(getHostAddress(), getHostPort());
}
void CServerHandler::onConnectionEstablished()
{
c->enterLobbyConnectionMode();
sendClientConnecting();
//FIXME: call functor provided in CServerHandler::justConnectToServer
assert(0);
//onConnected();
}
void CServerHandler::applyPacksOnLobbyScreen()
{
if(!c || !c->handler)
return;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
while(!packsForLobbyScreen.empty())
{
@ -306,16 +313,6 @@ void CServerHandler::applyPacksOnLobbyScreen()
}
}
void CServerHandler::stopServerConnection()
{
if(c->handler)
{
while(!c->handler->timed_join(boost::chrono::milliseconds(50)))
applyPacksOnLobbyScreen();
c->handler->join();
}
}
std::set<PlayerColor> CServerHandler::getHumanColors()
{
return clientHumanColors(c->connectionID);
@ -421,7 +418,6 @@ void CServerHandler::sendClientDisconnecting()
{
// Network thread might be applying network pack at this moment
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
c->close();
c.reset();
}
}
@ -814,7 +810,7 @@ void CServerHandler::restoreLastSession()
myNames.push_back(name.String());
resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
screenType = ESelectionScreen::loadGame;
justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer());
justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer(), {});
};
auto cleanUpSession = []()
@ -844,9 +840,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
screenType = ESelectionScreen::newGame;
}
if(settings["session"]["donotstartserver"].Bool())
justConnectToServer(localhostAddress, 3030);
justConnectToServer(localhostAddress, 3030, {});
else
startLocalServerAndConnect();
startLocalServerAndConnect({});
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
@ -902,65 +898,49 @@ public:
}
};
void CServerHandler::threadHandleConnection()
void CServerHandler::onPacketReceived(const std::vector<uint8_t> & message)
{
setThreadName("handleConnection");
c->enterLobbyConnectionMode();
try
CPack * pack = c->retrievePack(message);
if(state == EClientState::DISCONNECTING)
{
sendClientConnecting();
while(c && c->connected)
{
while(state == EClientState::STARTING)
boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
CPack * pack = c->retrievePack();
if(state == EClientState::DISCONNECTING)
{
// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
// Though currently they'll be delivered and might cause crash.
vstd::clear_pointer(pack);
}
else
{
ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
}
// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
// Though currently they'll be delivered and might cause crash.
vstd::clear_pointer(pack);
}
//catch only asio exceptions
catch(const boost::system::system_error & e)
else
{
if(state == EClientState::DISCONNECTING)
ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
}
void CServerHandler::onDisconnected()
{
if(state == EClientState::DISCONNECTING)
{
logNetwork->info("Successfully closed connection to server, ending listening thread!");
}
else
{
logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
if(client)
{
logNetwork->info("Successfully closed connection to server, ending listening thread!");
state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
});
}
else
{
if (e.code() == boost::asio::error::eof)
logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
else
logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what());
if(client)
{
state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
});
}
else
{
auto lcd = new LobbyClientDisconnected();
lcd->clientId = c->connectionID;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lcd);
}
auto lcd = new LobbyClientDisconnected();
lcd->clientId = c->connectionID;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lcd);
}
}
}

View File

@ -11,6 +11,7 @@
#include "../lib/CStopWatch.h"
#include "../lib/network/NetworkListener.h"
#include "../lib/StartInfo.h"
#include "../lib/CondSh.h"
@ -80,8 +81,10 @@ public:
};
/// structure to handle running server and connecting to it
class CServerHandler : public IServerAPI, public LobbyInfo
class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, boost::noncopyable
{
std::unique_ptr<NetworkClient> networkClient;
friend class ApplyOnLobbyHandlerNetPackVisitor;
std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
@ -95,12 +98,18 @@ class CServerHandler : public IServerAPI, public LobbyInfo
std::shared_ptr<HighScoreCalculation> highScoreCalc;
void threadHandleConnection();
void threadRunServer();
void onServerFinished();
void sendLobbyPack(const CPackForLobby & pack) const override;
void onPacketReceived(const std::vector<uint8_t> & message) override;
void onConnectionFailed(const std::string & errorMessage) override;
void onConnectionEstablished() override;
void onDisconnected() override;
public:
std::shared_ptr<CConnection> c;
std::atomic<EClientState> state;
////////////////////
// FIXME: Bunch of crutches to glue it all together
@ -115,7 +124,6 @@ public:
std::unique_ptr<CStopWatch> th;
std::shared_ptr<boost::thread> threadRunLocalServer;
std::shared_ptr<CConnection> c;
CClient * client;
CondSh<bool> campaignServerRestartLock;
@ -123,15 +131,15 @@ public:
static const std::string localhostAddress;
CServerHandler();
~CServerHandler();
std::string getHostAddress() const;
ui16 getHostPort() const;
void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
void startLocalServerAndConnect();
void justConnectToServer(const std::string & addr, const ui16 port);
void startLocalServerAndConnect(const std::function<void()> & onConnected);
void justConnectToServer(const std::string & addr, const ui16 port, const std::function<void()> & onConnected);
void applyPacksOnLobbyScreen();
void stopServerConnection();
// Helpers for lobby state access
std::set<PlayerColor> getHumanColors();

View File

@ -30,11 +30,12 @@
#include "../lib/UnlockGuard.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesClientPacks.h"
#include "../lib/serializer/Connection.h"
#include <memory>
#include <vcmi/events/EventBus.h>

View File

@ -27,8 +27,8 @@
#include "../CCallback.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/VCMI_Lib.h"

View File

@ -54,8 +54,6 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient
result = false;
return;
}
handler.stopServerConnection();
}
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)

View File

@ -47,7 +47,7 @@
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/CRandomGenerator.h"
ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
: screenType(ScreenType)

View File

@ -43,7 +43,6 @@
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/mapping/MapFormat.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/serializer/Connection.h"
bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
{

View File

@ -45,7 +45,6 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/JsonNode.h"
#include "../../lib/campaign/CampaignHandler.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/filesystem/CCompressedStream.h"
@ -559,7 +558,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
{
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting"));
buttonOk->block(true);
startConnectThread();
startConnection();
}
else
{
@ -582,7 +581,7 @@ void CSimpleJoinScreen::connectToServer()
buttonOk->block(true);
GH.stopTextInput();
startConnectThread(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
startConnection(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
}
void CSimpleJoinScreen::leaveScreen()
@ -603,7 +602,7 @@ void CSimpleJoinScreen::onChange(const std::string & newText)
buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty());
}
void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port)
void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port)
{
#if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID)
// in single process build server must use same JNIEnv as client
@ -611,35 +610,31 @@ void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port)
// https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md#threads-and-the-java-vm
CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv());
#endif
boost::thread connector(&CSimpleJoinScreen::connectThread, this, addr, port);
connector.detach();
}
auto const & onConnected = [this]()
{
// async call to prevent thread race
GH.dispatchMainThread([this](){
if(CSH->state == EClientState::CONNECTION_FAILED)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
{
setThreadName("connectThread");
if(!addr.length())
CSH->startLocalServerAndConnect();
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
GH.startTextInput(inputAddress->pos);
buttonOk->block(false);
}
if(GH.windows().isTopWindow(this))
{
close();
}
});
};
if(addr.empty())
CSH->startLocalServerAndConnect(onConnected);
else
CSH->justConnectToServer(addr, port);
// async call to prevent thread race
GH.dispatchMainThread([this](){
if(CSH->state == EClientState::CONNECTION_FAILED)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
GH.startTextInput(inputAddress->pos);
buttonOk->block(false);
}
if(GH.windows().isTopWindow(this))
{
close();
}
});
CSH->justConnectToServer(addr, port, onConnected);
}
CLoadingScreen::CLoadingScreen()

View File

@ -177,8 +177,7 @@ class CSimpleJoinScreen : public WindowBase
void connectToServer();
void leaveScreen();
void onChange(const std::string & newText);
void startConnectThread(const std::string & addr = {}, ui16 port = 0);
void connectThread(const std::string & addr, ui16 port);
void startConnection(const std::string & addr = {}, ui16 port = 0);
public:
CSimpleJoinScreen(bool host = true);

View File

@ -18,12 +18,12 @@
#include "../../lib/MetaString.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/network/NetworkClient.h"
LobbyClient::LobbyClient(LobbyWindow * window)
: window(window)
{
}
: networkClient(std::make_unique<NetworkClient>(*this))
, window(window)
{}
static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
{
@ -100,7 +100,22 @@ void LobbyClient::sendMessage(const JsonNode & data)
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
sendPacket(payloadBuffer);
networkClient->sendPacket(payloadBuffer);
}
void LobbyClient::start(const std::string & host, uint16_t port)
{
networkClient->start(host, port);
}
void LobbyClient::run()
{
networkClient->run();
}
void LobbyClient::poll()
{
networkClient->poll();
}
LobbyWidget::LobbyWidget(LobbyWindow * window)

View File

@ -12,7 +12,7 @@
#include "../gui/InterfaceObjectConfigurable.h"
#include "../windows/CWindowObject.h"
#include "../../lib/network/NetworkClient.h"
#include "../../lib/network/NetworkListener.h"
class LobbyWindow;
@ -27,8 +27,9 @@ public:
std::shared_ptr<CTextBox> getGameChat();
};
class LobbyClient : public NetworkClient
class LobbyClient : public INetworkClientListener
{
std::unique_ptr<NetworkClient> networkClient;
LobbyWindow * window;
void onPacketReceived(const std::vector<uint8_t> & message) override;
@ -40,6 +41,10 @@ public:
explicit LobbyClient(LobbyWindow * window);
void sendMessage(const JsonNode & data);
void start(const std::string & host, uint16_t port);
void run();
void poll();
};
class LobbyWindow : public CWindowObject

View File

@ -478,6 +478,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/network/NetworkClient.h
${MAIN_LIB_DIR}/network/NetworkConnection.h
${MAIN_LIB_DIR}/network/NetworkDefines.h
${MAIN_LIB_DIR}/network/NetworkListener.h
${MAIN_LIB_DIR}/network/NetworkServer.h
${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h

View File

@ -47,8 +47,6 @@
#include "RiverHandler.h"
#include "TerrainHandler.h"
#include "serializer/Connection.h"
VCMI_LIB_NAMESPACE_BEGIN
void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const

View File

@ -123,7 +123,12 @@ bool LobbyInfo::isClientHost(int clientId) const
return clientId == hostClientId;
}
std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId)
bool LobbyInfo::isPlayerHost(const PlayerColor & color) const
{
return vstd::contains(getAllClientPlayers(hostClientId), color);
}
std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) const
{
std::set<PlayerColor> players;
for(auto & elem : si->playerInfos)

View File

@ -197,7 +197,6 @@ struct DLL_LINKAGE LobbyState
struct DLL_LINKAGE LobbyInfo : public LobbyState
{
boost::mutex stateMutex;
std::string uuid;
LobbyInfo() {}
@ -205,7 +204,8 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState
void verifyStateBeforeStart(bool ignoreNoHuman = false) const;
bool isClientHost(int clientId) const;
std::set<PlayerColor> getAllClientPlayers(int clientId);
bool isPlayerHost(const PlayerColor & color) const;
std::set<PlayerColor> getAllClientPlayers(int clientId) const;
std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const;
// Helpers for lobby state access

View File

@ -13,10 +13,27 @@
VCMI_LIB_NAMESPACE_BEGIN
NetworkClient::NetworkClient()
DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port)
{
boost::asio::io_service io;
NetworkAcceptor acceptor(io);
boost::system::error_code ec;
acceptor.open(boost::asio::ip::tcp::v4(), ec);
if (ec)
return false;
acceptor.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), ec);
if (ec)
return false;
return true;
}
NetworkClient::NetworkClient(INetworkClientListener & listener)
: io(new NetworkService)
, socket(new NetworkSocket(*io))
// , timer(new NetworkTimer(*io))
, listener(listener)
{
}
@ -32,14 +49,14 @@ void NetworkClient::onConnected(const boost::system::error_code & ec)
{
if (ec)
{
onConnectionFailed(ec.message());
listener.onConnectionFailed(ec.message());
return;
}
connection = std::make_shared<NetworkConnection>(socket, *this);
connection->start();
onConnectionEstablished();
listener.onConnectionEstablished();
}
void NetworkClient::run()
@ -59,12 +76,12 @@ void NetworkClient::sendPacket(const std::vector<uint8_t> & message)
void NetworkClient::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{
onDisconnected();
listener.onDisconnected();
}
void NetworkClient::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{
onPacketReceived(message);
listener.onPacketReceived(message);
}

View File

@ -10,9 +10,14 @@
#pragma once
#include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN
/// Function that attempts to open specified port on local system to determine whether port is in use
/// Returns: true if port is free and can be used to receive connections
DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port);
class NetworkConnection;
class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener
@ -20,23 +25,19 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL
std::shared_ptr<NetworkService> io;
std::shared_ptr<NetworkSocket> socket;
std::shared_ptr<NetworkConnection> connection;
std::shared_ptr<NetworkTimer> timer;
INetworkClientListener & listener;
void onConnected(const boost::system::error_code & ec);
void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
protected:
virtual void onPacketReceived(const std::vector<uint8_t> & message) = 0;
virtual void onConnectionFailed(const std::string & errorMessage) = 0;
virtual void onConnectionEstablished() = 0;
virtual void onDisconnected() = 0;
public:
NetworkClient(INetworkClientListener & listener);
virtual ~NetworkClient() = default;
void sendPacket(const std::vector<uint8_t> & message);
public:
NetworkClient();
virtual ~NetworkClient() = default;
void start(const std::string & host, uint16_t port);
void run();

View File

@ -10,6 +10,7 @@
#pragma once
#include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN

View File

@ -17,17 +17,5 @@ using NetworkService = boost::asio::io_service;
using NetworkSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
using NetworkAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
using NetworkBuffer = boost::asio::streambuf;
using NetworkTimer = boost::asio::steady_timer;
class NetworkConnection;
class DLL_LINKAGE INetworkConnectionListener
{
friend class NetworkConnection;
protected:
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
};
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,50 @@
/*
* NetworkListener.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class NetworkConnection;
class NetworkServer;
class NetworkClient;
class DLL_LINKAGE INetworkConnectionListener
{
friend class NetworkConnection;
protected:
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
~INetworkConnectionListener() = default;
};
class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener
{
friend class NetworkServer;
protected:
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0;
~INetworkServerListener() = default;
};
class DLL_LINKAGE INetworkClientListener
{
friend class NetworkClient;
protected:
virtual void onPacketReceived(const std::vector<uint8_t> & message) = 0;
virtual void onConnectionFailed(const std::string & errorMessage) = 0;
virtual void onConnectionEstablished() = 0;
virtual void onDisconnected() = 0;
~INetworkClientListener() = default;
};
VCMI_LIB_NAMESPACE_END

View File

@ -13,6 +13,12 @@
VCMI_LIB_NAMESPACE_BEGIN
NetworkServer::NetworkServer(INetworkServerListener & listener)
:listener(listener)
{
}
void NetworkServer::start(uint16_t port)
{
io = std::make_shared<boost::asio::io_service>();
@ -32,6 +38,11 @@ void NetworkServer::run()
io->run();
}
void NetworkServer::run(std::chrono::milliseconds duration)
{
io->run_for(duration);
}
void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingConnection, const boost::system::error_code & ec)
{
if(ec)
@ -43,7 +54,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
auto connection = std::make_shared<NetworkConnection>(upcomingConnection, *this);
connections.insert(connection);
connection->start();
onNewConnection(connection);
listener.onNewConnection(connection);
startAsyncAccept();
}
@ -56,7 +67,12 @@ void NetworkServer::onDisconnected(const std::shared_ptr<NetworkConnection> & co
{
assert(connections.count(connection));
connections.erase(connection);
onConnectionLost(connection);
listener.onDisconnected(connection);
}
void NetworkServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{
listener.onPacketReceived(connection, message);
}
VCMI_LIB_NAMESPACE_END

View File

@ -10,6 +10,7 @@
#pragma once
#include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -21,20 +22,20 @@ class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionL
std::shared_ptr<NetworkAcceptor> acceptor;
std::set<std::shared_ptr<NetworkConnection>> connections;
INetworkServerListener & listener;
void connectionAccepted(std::shared_ptr<NetworkSocket>, const boost::system::error_code & ec);
void startAsyncAccept();
void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
protected:
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0;
virtual void onConnectionLost(const std::shared_ptr<NetworkConnection> &) = 0;
void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
public:
explicit NetworkServer(INetworkServerListener & listener);
void sendPacket(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message);
public:
virtual ~NetworkServer() = default;
void start(uint16_t port);
void run(std::chrono::milliseconds duration);
void run();
};

View File

@ -51,7 +51,7 @@ struct VectorizedObjectInfo
};
/// Base class for serializers capable of reading or writing data
class DLL_LINKAGE CSerializer
class DLL_LINKAGE CSerializer : boost::noncopyable
{
template<typename Numeric, std::enable_if_t<std::is_arithmetic_v<Numeric>, bool> = true>
static int32_t idToNumber(const Numeric &t)

View File

@ -10,9 +10,52 @@
#include "StdInc.h"
#include "Connection.h"
#include "../networkPacks/NetPacksBase.h"
#include "BinaryDeserializer.h"
#include "BinarySerializer.h"
#include <boost/asio.hpp>
//#include "../networkPacks/NetPacksBase.h"
CConnection::CConnection(std::weak_ptr<NetworkConnection> networkConnection)
{
}
void CConnection::sendPack(const CPack * pack)
{
}
CPack * CConnection::retrievePack(const std::vector<uint8_t> & data)
{
return nullptr;
}
void CConnection::disableStackSendingByID()
{
}
void CConnection::enterLobbyConnectionMode()
{
}
void CConnection::enterGameplayConnectionMode(CGameState * gs)
{
}
int CConnection::write(const void * data, unsigned size)
{
return 0;
}
int CConnection::read(void * data, unsigned size)
{
return 0;
}
#if 0
VCMI_LIB_NAMESPACE_BEGIN
@ -362,3 +405,5 @@ std::string CConnection::toString() const
}
VCMI_LIB_NAMESPACE_END
#endif

View File

@ -9,123 +9,84 @@
*/
#pragma once
#include "BinaryDeserializer.h"
#include "BinarySerializer.h"
#if BOOST_VERSION >= 107000 // Boost version >= 1.70
#include <boost/asio.hpp>
using TSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
using TAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
#else
namespace boost
{
namespace asio
{
namespace ip
{
class tcp;
}
#if BOOST_VERSION >= 106600 // Boost version >= 1.66
class io_context;
typedef io_context io_service;
#else
class io_service;
#endif
template <typename Protocol> class stream_socket_service;
template <typename Protocol,typename StreamSocketService>
class basic_stream_socket;
template <typename Protocol> class socket_acceptor_service;
template <typename Protocol,typename SocketAcceptorService>
class basic_socket_acceptor;
}
class mutex;
}
typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service<boost::asio::ip::tcp> > TSocket;
typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::socket_acceptor_service<boost::asio::ip::tcp> > TAcceptor;
#endif
#include "CSerializer.h"
VCMI_LIB_NAMESPACE_BEGIN
class BinaryDeserializer;
class BinarySerializer;
struct CPack;
struct ConnectionBuffers;
class NetworkConnection;
/// Main class for network communication
/// Allows establishing connection and bidirectional read-write
class DLL_LINKAGE CConnection
: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
class DLL_LINKAGE CConnection : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
{
void init();
void reportState(vstd::CLoggerBase * out) override;
/// Non-owning pointer to underlying connection
std::weak_ptr<NetworkConnection> networkConnection;
// void init();
// void reportState(vstd::CLoggerBase * out) override;
//
int write(const void * data, unsigned size) override;
int read(void * data, unsigned size) override;
void flushBuffers();
std::shared_ptr<boost::asio::io_service> io_service; //can be empty if connection made from socket
bool enableBufferedWrite;
bool enableBufferedRead;
std::unique_ptr<ConnectionBuffers> connectionBuffers;
// void flushBuffers();
//
// bool enableBufferedWrite;
// bool enableBufferedRead;
// std::unique_ptr<ConnectionBuffers> connectionBuffers;
//
std::unique_ptr<BinaryDeserializer> iser;
std::unique_ptr<BinarySerializer> oser;
//
// std::string contactUuid;
// std::string name; //who uses this connection
public:
BinaryDeserializer iser;
BinarySerializer oser;
std::shared_ptr<boost::mutex> mutexRead;
std::shared_ptr<boost::mutex> mutexWrite;
std::shared_ptr<TSocket> socket;
bool connected;
bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars
std::string contactUuid;
std::string name; //who uses this connection
std::string uuid;
int connectionID;
std::shared_ptr<boost::thread> handler;
CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID);
CConnection(const std::shared_ptr<TAcceptor> & acceptor, const std::shared_ptr<boost::asio::io_service> & Io_service, std::string Name, std::string UUID);
CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket
CConnection(std::weak_ptr<NetworkConnection> networkConnection);
// CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID);
// CConnection(const std::shared_ptr<TAcceptor> & acceptor, const std::shared_ptr<boost::asio::io_service> & Io_service, std::string Name, std::string UUID);
// CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket
// virtual ~CConnection();
void close();
bool isOpen() const;
template<class T>
CConnection &operator&(const T&);
virtual ~CConnection();
CPack * retrievePack();
// void close();
// bool isOpen() const;
//
// CPack * retrievePack();
void sendPack(const CPack * pack);
CPack * retrievePack(const std::vector<uint8_t> & data);
// std::vector<uint8_t> serializePack(const CPack * pack);
//
void disableStackSendingByID();
void enableStackSendingByID();
void disableSmartPointerSerialization();
void enableSmartPointerSerialization();
void disableSmartVectorMemberSerialization();
void enableSmartVectorMemberSerializatoin();
// void enableStackSendingByID();
// void disableSmartPointerSerialization();
// void enableSmartPointerSerialization();
// void disableSmartVectorMemberSerialization();
// void enableSmartVectorMemberSerializatoin();
//
void enterLobbyConnectionMode();
void enterGameplayConnectionMode(CGameState * gs);
std::string toString() const;
template<class T>
CConnection & operator>>(T &t)
{
iser & t;
return * this;
}
template<class T>
CConnection & operator<<(const T &t)
{
oser & t;
return * this;
}
//
// std::string toString() const;
//
// template<class T>
// CConnection & operator>>(T &t)
// {
// iser & t;
// return * this;
// }
//
// template<class T>
// CConnection & operator<<(const T &t)
// {
// oser & t;
// return * this;
// }
};
VCMI_LIB_NAMESPACE_END

View File

@ -104,14 +104,14 @@ void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target,
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
sendPacket(target, payloadBuffer);
networkServer->sendPacket(target, payloadBuffer);
}
void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
{
}
void LobbyServer::onConnectionLost(const std::shared_ptr<NetworkConnection> & connection)
void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{
activeAccounts.erase(connection);
}
@ -169,9 +169,20 @@ void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & co
LobbyServer::LobbyServer()
: database(new LobbyDatabase())
, networkServer(new NetworkServer(*this))
{
}
void LobbyServer::start(uint16_t port)
{
networkServer->start(port);
}
void LobbyServer::run()
{
networkServer->run();
}
int main(int argc, const char * argv[])
{
LobbyServer server;

View File

@ -41,7 +41,7 @@ public:
std::vector<ChatMessage> getRecentMessageHistory();
};
class LobbyServer : public NetworkServer
class LobbyServer : public INetworkServerListener
{
struct AccountState
{
@ -51,12 +51,16 @@ class LobbyServer : public NetworkServer
std::map<std::shared_ptr<NetworkConnection>, AccountState> activeAccounts;
std::unique_ptr<LobbyDatabase> database;
std::unique_ptr<NetworkServer> networkServer;
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
void onConnectionLost(const std::shared_ptr<NetworkConnection> &) override;
void onDisconnected(const std::shared_ptr<NetworkConnection> &) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message) override;
void sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json);
public:
LobbyServer();
void start(uint16_t port);
void run();
};

View File

@ -61,6 +61,7 @@
#include "../lib/serializer/CSaveFile.h"
#include "../lib/serializer/CLoadFile.h"
#include "../lib/serializer/Connection.h"
#include "../lib/spells/CSpellHandler.h"
@ -969,11 +970,11 @@ void CGameHandler::onNewTurn()
synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
}
void CGameHandler::run(bool resume)
void CGameHandler::start(bool resume)
{
LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
for (auto cc : lobby->connections)
for (auto cc : lobby->activeConnections)
{
auto players = lobby->getAllClientPlayers(cc->connectionID);
std::stringstream sbuffer;
@ -1004,18 +1005,11 @@ void CGameHandler::run(bool resume)
events::GameResumed::defaultExecute(serverEventBus.get());
turnOrder->onGameStarted();
}
//wait till game is done
auto clockLast = std::chrono::steady_clock::now();
while(lobby->getState() == EServerState::GAMEPLAY)
{
const auto clockDuration = std::chrono::steady_clock::now() - clockLast;
const int timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(clockDuration).count();
clockLast += clockDuration;
turnTimerHandler.update(timePassed);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
void CGameHandler::tick(int millisecondsPassed)
{
turnTimerHandler.update(millisecondsPassed);
}
void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
@ -1677,13 +1671,8 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
void CGameHandler::sendToAllClients(CPackForClient * pack)
{
logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name());
for (auto c : lobby->connections)
{
if(!c->isOpen())
continue;
for (auto c : lobby->activeConnections)
c->sendPack(pack);
}
}
void CGameHandler::sendAndApply(CPackForClient * pack)

View File

@ -261,7 +261,8 @@ public:
bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id);
void run(bool resume);
void start(bool resume);
void tick(int millisecondsPassed);
bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
void spawnWanderingMonsters(CreatureID creatureID);

View File

@ -8,12 +8,11 @@
*
*/
#include "StdInc.h"
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include "../lib/filesystem/Filesystem.h"
#include "../lib/campaign/CampaignState.h"
#include "../lib/CThreadHelper.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CArtHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
@ -37,12 +36,15 @@
#include "CGameHandler.h"
#include "processors/PlayerMessageProcessor.h"
#include "../lib/mapping/CMapInfo.h"
#include "../lib/network/NetworkServer.h"
#include "../lib/network/NetworkClient.h"
#include "../lib/GameConstants.h"
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CConfigHandler.h"
#include "../lib/ScopeGuard.h"
#include "../lib/serializer/CMemorySerializer.h"
#include "../lib/serializer/Cast.h"
#include "../lib/serializer/Connection.h"
#include "../lib/UnlockGuard.h"
@ -81,11 +83,8 @@ public:
if(checker.getResult())
{
boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
ApplyOnServerNetPackVisitor applier(*srv);
ptr->visit(applier);
return applier.getResult();
}
else
@ -117,48 +116,101 @@ public:
}
};
class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
{
private:
CVCMIServer & handler;
std::shared_ptr<CGameHandler> gh;
public:
CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr<CGameHandler> gh)
:handler(handler), gh(gh)
{
}
virtual bool callTyped() override { return false; }
virtual void visitForLobby(CPackForLobby & packForLobby) override
{
handler.handleReceivedPack(std::unique_ptr<CPackForLobby>(&packForLobby));
}
virtual void visitForServer(CPackForServer & serverPack) override
{
if (gh)
gh->handleReceivedPack(&serverPack);
else
logNetwork->error("Received pack for game server while in lobby!");
}
virtual void visitForClient(CPackForClient & clientPack) override
{
}
};
std::string SERVER_NAME_AFFIX = "server";
std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')';
CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
: port(3030), io(std::make_shared<boost::asio::io_service>()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
: state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
{
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
applier = std::make_shared<CApplier<CBaseForServerApply>>();
registerTypesLobbyPacks(*applier);
uint16_t port = 3030;
if(cmdLineOptions.count("port"))
port = cmdLineOptions["port"].as<ui16>();
port = cmdLineOptions["port"].as<uint16_t>();
logNetwork->info("Port %d will be used", port);
try
{
acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
}
catch(...)
{
logNetwork->info("Port %d is busy, trying to use random port instead", port);
if(cmdLineOptions.count("run-by-client"))
{
logNetwork->error("Port must be specified when run-by-client is used!!");
#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE)
::exit(0);
#else
std::quick_exit(0);
#endif
}
acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
port = acceptor->local_endpoint().port();
}
networkServer = std::make_unique<NetworkServer>(*this);
networkServer->start(port);
logNetwork->info("Listening for connections at port %d", port);
}
CVCMIServer::~CVCMIServer()
{
announceQueue.clear();
CVCMIServer::~CVCMIServer() = default;
if(announceLobbyThread)
announceLobbyThread->join();
void CVCMIServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
{
if (activeConnections.empty())
establishOutgoingConnection();
if(state == EServerState::LOBBY)
activeConnections.push_back(std::make_shared<CConnection>(connection));//, SERVER_NAME, uuid);)
// TODO: else: deny connection
// TODO: else: try to reconnect / send state to reconnected client
}
void CVCMIServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{
std::shared_ptr<CConnection> c = findConnection(connection);
CPack * pack = c->retrievePack(message);
pack->c = c;
CVCMIServerPackVisitor visitor(*this, this->gh);
pack->visit(visitor);
//FIXME: delete pack?
}
void CVCMIServer::onPacketReceived(const std::vector<uint8_t> & message)
{
//TODO: handle pack received from lobby
}
void CVCMIServer::onConnectionFailed(const std::string & errorMessage)
{
//TODO: handle failure to connect to lobby
}
void CVCMIServer::onConnectionEstablished()
{
//TODO: handle connection to lobby - login?
}
void CVCMIServer::onDisconnected()
{
//TODO: handle disconnection from lobby
}
void CVCMIServer::setState(EServerState value)
@ -171,101 +223,60 @@ EServerState CVCMIServer::getState() const
return state.load();
}
std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<NetworkConnection> & netConnection)
{
//TODO
assert(0);
return nullptr;
}
void CVCMIServer::run()
{
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
if(!restartGameplay)
{
this->announceLobbyThread = std::make_unique<boost::thread>(&CVCMIServer::threadAnnounceLobby, this);
startAsyncAccept();
if(!remoteConnectionsThread && cmdLineOptions.count("lobby"))
{
remoteConnectionsThread = std::make_unique<boost::thread>(&CVCMIServer::establishRemoteConnections, this);
}
#if defined(VCMI_ANDROID)
#ifndef SINGLE_PROCESS_APP
CAndroidVMHelper vmHelper;
vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
}
#endif
#endif
}
while(state == EServerState::LOBBY || state == EServerState::GAMEPLAY_STARTING)
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
static const int serverUpdateIntervalMilliseconds = 50;
auto clockInitial = std::chrono::steady_clock::now();
int64_t msPassedLast = 0;
logNetwork->info("Thread handling connections ended");
if(state == EServerState::GAMEPLAY)
{
gh->run(si->mode == StartInfo::LOAD_GAME);
}
while(state == EServerState::GAMEPLAY_ENDED)
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
}
void CVCMIServer::establishRemoteConnections()
{
setThreadName("establishConnection");
//wait for host connection
while(connections.empty())
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
uuid = cmdLineOptions["lobby-uuid"].as<std::string>();
int numOfConnections = cmdLineOptions["connections"].as<ui16>();
for(int i = 0; i < numOfConnections; ++i)
connectToRemote();
}
void CVCMIServer::connectToRemote()
{
std::shared_ptr<CConnection> c;
try
{
auto address = cmdLineOptions["lobby"].as<std::string>();
int port = cmdLineOptions["lobby-port"].as<ui16>();
logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid);
c = std::make_shared<CConnection>(address, port, SERVER_NAME, uuid);
}
catch(...)
{
logNetwork->error("\nCannot establish remote connection!");
}
if(c)
{
connections.insert(c);
remoteConnections.insert(c);
c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
}
}
void CVCMIServer::threadAnnounceLobby()
{
setThreadName("announceLobby");
while(state != EServerState::SHUTDOWN)
{
{
boost::unique_lock<boost::recursive_mutex> myLock(mx);
while(!announceQueue.empty())
{
announcePack(std::move(announceQueue.front()));
announceQueue.pop_front();
}
networkServer->run(std::chrono::milliseconds(serverUpdateIntervalMilliseconds));
if(acceptor)
{
io->reset();
io->poll();
}
}
const auto clockNow = std::chrono::steady_clock::now();
const auto clockPassed = clockNow - clockInitial;
const int64_t msPassedNow = std::chrono::duration_cast<std::chrono::milliseconds>(clockPassed).count();
const int64_t msDelta = msPassedNow - msPassedLast;
msPassedLast = msPassedNow;
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
if (state == EServerState::GAMEPLAY)
gh->tick(msDelta);
}
}
void CVCMIServer::establishOutgoingConnection()
{
if(!cmdLineOptions.count("lobby"))
return;
uuid = cmdLineOptions["lobby-uuid"].as<std::string>();
auto address = cmdLineOptions["lobby"].as<std::string>();
int port = cmdLineOptions["lobby-port"].as<ui16>();
logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid);
outgoingConnection = std::make_unique<NetworkClient>(*this);
outgoingConnection->start(address, port);//, SERVER_NAME, uuid);
// connections.insert(c);
// remoteConnections.insert(c);
}
void CVCMIServer::prepareToRestart()
{
if(state == EServerState::GAMEPLAY)
@ -280,11 +291,9 @@ void CVCMIServer::prepareToRestart()
campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0));
campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1);
}
// FIXME: dirry hack to make sure old CGameHandler::run is finished
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
for(auto c : connections)
for(auto c : activeConnections)
{
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
@ -306,10 +315,11 @@ bool CVCMIServer::prepareToStartGame()
{
if(progressTracking.get() != currentProgress)
{
//FIXME: UNGUARDED MULTITHREADED ACCESS!!!
currentProgress = progressTracking.get();
std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress);
loadProgress->progress = currentProgress;
addToAnnounceQueue(std::move(loadProgress));
announcePack(std::move(loadProgress));
}
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
@ -355,149 +365,55 @@ bool CVCMIServer::prepareToStartGame()
return true;
}
void CVCMIServer::startGameImmidiately()
void CVCMIServer::startGameImmediately()
{
for(auto c : connections)
for(auto c : activeConnections)
c->enterGameplayConnectionMode(gh->gs);
gh->start(si->mode == StartInfo::LOAD_GAME);
state = EServerState::GAMEPLAY;
}
void CVCMIServer::startAsyncAccept()
void CVCMIServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{
assert(!upcomingConnection);
assert(acceptor);
logNetwork->error("Network error receiving a pack. Connection has been closed");
#if BOOST_VERSION >= 107000 // Boost version >= 1.70
upcomingConnection = std::make_shared<TSocket>(acceptor->get_executor());
#else
upcomingConnection = std::make_shared<TSocket>(acceptor->get_io_service());
#endif
acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1));
}
std::shared_ptr<CConnection> c = findConnection(connection);
void CVCMIServer::connectionAccepted(const boost::system::error_code & ec)
{
if(ec)
inactiveConnections.push_back(c);
vstd::erase(activeConnections, c);
if(activeConnections.empty() || hostClientId == c->connectionID)
state = EServerState::SHUTDOWN;
if(gh && state == EServerState::GAMEPLAY)
{
if(state != EServerState::SHUTDOWN)
logNetwork->info("Something wrong during accepting: %s", ec.message());
return;
}
try
{
if(state == EServerState::LOBBY || !hangingConnections.empty())
{
logNetwork->info("We got a new connection! :)");
auto c = std::make_shared<CConnection>(upcomingConnection, SERVER_NAME, uuid);
upcomingConnection.reset();
connections.insert(c);
c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
}
}
catch(std::exception & e)
{
logNetwork->error("Failure processing new connection! %s", e.what());
upcomingConnection.reset();
}
startAsyncAccept();
}
class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
{
private:
CVCMIServer & handler;
std::shared_ptr<CGameHandler> gh;
public:
CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr<CGameHandler> gh)
:handler(handler), gh(gh)
{
}
virtual bool callTyped() override { return false; }
virtual void visitForLobby(CPackForLobby & packForLobby) override
{
handler.handleReceivedPack(std::unique_ptr<CPackForLobby>(&packForLobby));
}
virtual void visitForServer(CPackForServer & serverPack) override
{
if (gh)
gh->handleReceivedPack(&serverPack);
else
logNetwork->error("Received pack for game server while in lobby!");
}
virtual void visitForClient(CPackForClient & clientPack) override
{
}
};
void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
{
setThreadName("handleClient");
c->enterLobbyConnectionMode();
while(c->connected)
{
CPack * pack;
try
{
pack = c->retrievePack();
pack->c = c;
}
catch(boost::system::system_error & e)
{
if (e.code() == boost::asio::error::eof)
logNetwork->error("Network error receiving a pack. Connection has been closed");
else
logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
hangingConnections.insert(c);
connections.erase(c);
if(connections.empty() || hostClient == c)
state = EServerState::SHUTDOWN;
if(gh && state == EServerState::GAMEPLAY)
{
gh->handleClientDisconnection(c);
}
break;
}
CVCMIServerPackVisitor visitor(*this, this->gh);
pack->visit(visitor);
gh->handleClientDisconnection(c);
}
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
if(c->connected)
{
auto lcd = std::make_unique<LobbyClientDisconnected>();
lcd->c = c;
lcd->clientId = c->connectionID;
handleReceivedPack(std::move(lcd));
}
logNetwork->info("Thread listening for %s ended", c->toString());
c->handler.reset();
// if(c->connected)
// {
// auto lcd = std::make_unique<LobbyClientDisconnected>();
// lcd->c = c;
// lcd->clientId = c->connectionID;
// handleReceivedPack(std::move(lcd));
// }
//
// logNetwork->info("Thread listening for %s ended", c->toString());
}
void CVCMIServer::handleReceivedPack(std::unique_ptr<CPackForLobby> pack)
{
CBaseForServerApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack.get()));
if(apply->applyOnServerBefore(this, pack.get()))
addToAnnounceQueue(std::move(pack));
announcePack(std::move(pack));
}
void CVCMIServer::announcePack(std::unique_ptr<CPackForLobby> pack)
{
for(auto c : connections)
for(auto c : activeConnections)
{
// FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected
// Until UUID set we only pass LobbyClientConnected to this client
@ -515,7 +431,7 @@ void CVCMIServer::announceMessage(const std::string & txt)
logNetwork->info("Show message: %s", txt);
auto cm = std::make_unique<LobbyShowMessage>();
cm->message = txt;
addToAnnounceQueue(std::move(cm));
announcePack(std::move(cm));
}
void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
@ -524,25 +440,18 @@ void CVCMIServer::announceTxt(const std::string & txt, const std::string & playe
auto cm = std::make_unique<LobbyChatMessage>();
cm->playerName = playerName;
cm->message = txt;
addToAnnounceQueue(std::move(cm));
}
void CVCMIServer::addToAnnounceQueue(std::unique_ptr<CPackForLobby> pack)
{
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
announceQueue.push_back(std::move(pack));
announcePack(std::move(cm));
}
bool CVCMIServer::passHost(int toConnectionId)
{
for(auto c : connections)
for(auto c : activeConnections)
{
if(isClientHost(c->connectionID))
continue;
if(c->connectionID != toConnectionId)
continue;
hostClient = c;
hostClientId = c->connectionID;
announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
return true;
@ -555,9 +464,8 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
if(state == EServerState::LOBBY)
c->connectionID = currentClientId++;
if(!hostClient)
if(hostClientId == -1)
{
hostClient = c;
hostClientId = c->connectionID;
si->mode = mode;
}
@ -592,8 +500,9 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
{
connections -= c;
if(connections.empty() || hostClient == c)
vstd::erase(activeConnections, c);
if(activeConnections.empty() || hostClientId == c->connectionID)
{
state = EServerState::SHUTDOWN;
return;
@ -626,7 +535,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
if(gh && si && state == EServerState::GAMEPLAY)
{
gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
gh->connections[playerSettings->color].insert(hostClient);
// gh->connections[playerSettings->color].insert(hostClient);
startAiPack.players.push_back(playerSettings->color);
}
}
@ -753,7 +662,6 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
void CVCMIServer::updateAndPropagateLobbyState()
{
boost::unique_lock<boost::mutex> stateLock(stateMutex);
// Update player settings for RMG
// TODO: find appropriate location for this code
if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
@ -772,7 +680,7 @@ void CVCMIServer::updateAndPropagateLobbyState()
auto lus = std::make_unique<LobbyUpdateState>();
lus->state = *this;
addToAnnounceQueue(std::move(lus));
announcePack(std::move(lus));
}
void CVCMIServer::setPlayer(PlayerColor clickedColor)
@ -1188,32 +1096,9 @@ int main(int argc, const char * argv[])
cond->notify_one();
#endif
try
{
boost::asio::io_service io_service;
CVCMIServer server(opts);
CVCMIServer server(opts);
server.run();
try
{
while(server.getState() != EServerState::SHUTDOWN)
{
server.run();
}
io_service.run();
}
catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection
{
logNetwork->error(e.what());
server.setState(EServerState::SHUTDOWN);
}
}
catch(boost::system::system_error & e)
{
logNetwork->error(e.what());
//catch any startup errors (e.g. can't access port) errors
//and return non-zero status so client can detect error
throw;
}
#if VCMI_ANDROID_DUAL_PROCESS
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");

View File

@ -9,10 +9,10 @@
*/
#pragma once
#include "../lib/serializer/Connection.h"
#include "../lib/network/NetworkListener.h"
#include "../lib/StartInfo.h"
#include <boost/program_options.hpp>
#include <boost/program_options/variables_map.hpp>
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
#define VCMI_ANDROID_DUAL_PROCESS 1
@ -24,6 +24,7 @@ class CMapInfo;
struct CPackForLobby;
class CConnection;
struct StartInfo;
struct LobbyInfo;
struct PlayerSettings;
@ -46,53 +47,66 @@ enum class EServerState : ui8
SHUTDOWN
};
class CVCMIServer : public LobbyInfo
class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener
{
/// Network server instance that receives and processes incoming connections on active socket
std::unique_ptr<NetworkServer> networkServer;
/// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game)
std::unique_ptr<NetworkClient> outgoingConnection;
public:
/// List of all active connections
std::vector<std::shared_ptr<CConnection>> activeConnections;
private:
/// List of all connections that were closed (but can still reconnect later)
std::vector<std::shared_ptr<CConnection>> inactiveConnections;
std::atomic<bool> restartGameplay; // FIXME: this is just a hack
std::shared_ptr<boost::asio::io_service> io;
std::shared_ptr<TAcceptor> acceptor;
std::shared_ptr<TSocket> upcomingConnection;
std::list<std::unique_ptr<CPackForLobby>> announceQueue;
boost::recursive_mutex mx;
std::shared_ptr<CApplier<CBaseForServerApply>> applier;
std::unique_ptr<boost::thread> announceLobbyThread;
std::unique_ptr<boost::thread> remoteConnectionsThread;
std::atomic<EServerState> state;
public:
std::shared_ptr<CGameHandler> gh;
ui16 port;
// INetworkServerListener impl
void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
// INetworkClientListener impl
void onPacketReceived(const std::vector<uint8_t> & message) override;
void onConnectionFailed(const std::string & errorMessage) override;
void onConnectionEstablished() override;
void onDisconnected() override;
void establishOutgoingConnection();
std::shared_ptr<CConnection> findConnection(const std::shared_ptr<NetworkConnection> &);
boost::program_options::variables_map cmdLineOptions;
std::set<std::shared_ptr<CConnection>> connections;
std::set<std::shared_ptr<CConnection>> remoteConnections;
std::set<std::shared_ptr<CConnection>> hangingConnections; //keep connections of players disconnected during the game
std::atomic<int> currentClientId;
std::atomic<ui8> currentPlayerId;
std::shared_ptr<CConnection> hostClient;
public:
std::shared_ptr<CGameHandler> gh;
boost::program_options::variables_map cmdLineOptions;
CVCMIServer(boost::program_options::variables_map & opts);
~CVCMIServer();
void run();
bool prepareToStartGame();
void prepareToRestart();
void startGameImmidiately();
void startGameImmediately();
void establishRemoteConnections();
void connectToRemote();
void startAsyncAccept();
void connectionAccepted(const boost::system::error_code & ec);
void threadHandleClient(std::shared_ptr<CConnection> c);
void threadAnnounceLobby();
void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
void announcePack(std::unique_ptr<CPackForLobby> pack);
bool passHost(int toConnectionId);
void announceTxt(const std::string & txt, const std::string & playerName = "system");
void announceMessage(const std::string & txt);
void addToAnnounceQueue(std::unique_ptr<CPackForLobby> pack);
void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
@ -101,6 +115,11 @@ public:
void clientDisconnected(std::shared_ptr<CConnection> c);
void reconnectPlayer(int connId);
public:
void announceMessage(const std::string & txt);
void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
void updateAndPropagateLobbyState();
void setState(EServerState value);

View File

@ -13,11 +13,9 @@
#include "CVCMIServer.h"
#include "CGameHandler.h"
#include "../lib/serializer/Connection.h"
#include "../lib/StartInfo.h"
// Campaigns
#include "../lib/campaign/CampaignState.h"
#include "../lib/serializer/Connection.h"
void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack)
{
@ -38,67 +36,18 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitForLobby(CPackForLobby & pac
void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
{
if(srv.gh)
{
for(auto & connection : srv.hangingConnections)
{
if(connection->uuid == pack.uuid)
{
{
result = true;
return;
}
}
}
}
if(srv.getState() == EServerState::LOBBY)
{
result = true;
return;
}
//disconnect immediately and ignore this client
srv.connections.erase(pack.c);
if(pack.c && pack.c->isOpen())
{
pack.c->close();
pack.c->connected = false;
}
{
result = false;
return;
}
}
void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
{
if(srv.gh)
{
for(auto & connection : srv.hangingConnections)
{
if(connection->uuid == pack.uuid)
{
logNetwork->info("Reconnection player");
pack.c->connectionID = connection->connectionID;
for(auto & playerConnection : srv.gh->connections)
{
for(auto & existingConnection : playerConnection.second)
{
if(existingConnection == connection)
{
playerConnection.second.erase(existingConnection);
playerConnection.second.insert(pack.c);
break;
}
}
}
srv.hangingConnections.erase(connection);
break;
}
}
}
srv.clientConnected(pack.c, pack.names, pack.uuid, pack.mode);
// Server need to pass some data to newly connected client
pack.clientId = pack.c->connectionID;
@ -121,7 +70,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyCl
startGameForReconnectedPlayer->initializedStartInfo = srv.si;
startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState();
startGameForReconnectedPlayer->clientId = pack.c->connectionID;
srv.addToAnnounceQueue(std::move(startGameForReconnectedPlayer));
srv.announcePack(std::move(startGameForReconnectedPlayer));
}
}
@ -154,38 +103,28 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC
void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{
srv.clientDisconnected(pack.c);
pack.c->close();
pack.c->connected = false;
result = true;
}
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{
if(pack.c && pack.c->isOpen())
{
boost::unique_lock<boost::mutex> lock(*pack.c->mutexWrite);
pack.c->close();
pack.c->connected = false;
}
if(pack.shutdownServer)
{
logNetwork->info("Client requested shutdown, server will close itself...");
srv.setState(EServerState::SHUTDOWN);
return;
}
else if(srv.connections.empty())
else if(srv.activeConnections.empty())
{
logNetwork->error("Last connection lost, server will close itself...");
srv.setState(EServerState::SHUTDOWN);
}
else if(pack.c == srv.hostClient)
else if(pack.c->connectionID == srv.hostClientId)
{
auto ph = std::make_unique<LobbyChangeHost>();
auto newHost = *RandomGeneratorUtil::nextItem(srv.connections, CRandomGenerator::getDefault());
auto newHost = srv.activeConnections.front();
ph->newHostConnectionId = newHost->connectionID;
srv.addToAnnounceQueue(std::move(ph));
srv.announcePack(std::move(ph));
}
srv.updateAndPropagateLobbyState();
@ -270,8 +209,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack)
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack)
{
boost::unique_lock<boost::mutex> stateLock(srv.stateMutex);
for(auto & c : srv.connections)
for(auto & c : srv.activeConnections)
{
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
@ -312,10 +250,10 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
{
if(pack.clientId == -1) //do not restart game for single client only
srv.startGameImmidiately();
srv.startGameImmediately();
else
{
for(auto & c : srv.connections)
for(auto & c : srv.activeConnections)
{
if(c->connectionID == pack.clientId)
{

View File

@ -20,11 +20,11 @@
#include "../lib/IGameCallback.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/battle/IBattleState.h"
#include "../lib/battle/BattleAction.h"
#include "../lib/battle/Unit.h"
#include "../lib/serializer/Connection.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/spells/ISpellMechanics.h"
#include "../lib/serializer/Cast.h"

View File

@ -13,7 +13,6 @@
#include "../CGameHandler.h"
#include "../CVCMIServer.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/modding/IdentifierStorage.h"
@ -22,11 +21,13 @@
#include "../../lib/StartInfo.h"
#include "../../lib/gameState/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/modding/IdentifierStorage.h"
#include "../../lib/modding/ModScope.h"
#include "../../lib/mapping/CMap.h"
#include "../../lib/networkPacks/PacksForClient.h"
#include "../../lib/networkPacks/StackLocation.h"
#include "../../lib/serializer/Connection.h"
PlayerMessageProcessor::PlayerMessageProcessor()
:gameHandler(nullptr)
@ -62,10 +63,7 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st
std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" "));
bool isHost = false;
for(auto & c : gameHandler->connections[player])
if(gameHandler->gameLobby()->isClientHost(c->connectionID))
isHost = true;
bool isHost = gameHandler->gameLobby()->isPlayerHost(player);
if(!isHost || words.size() < 2 || words[0] != "game")
return false;