moved server to seperate object and thread, eventloop now runs in main thread
Dieser Commit ist enthalten in:
Ursprung
95b4d88693
Commit
cdc5dab129
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -34,47 +34,34 @@
|
|||||||
class EventLoop
|
class EventLoop
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
inline static EventLoop* s_instance;
|
inline static std::queue<std::function<void()>> s_queue;
|
||||||
std::queue<std::function<void()>> m_queue;
|
inline static std::mutex s_queueMutex;
|
||||||
std::mutex m_queueMutex;
|
inline static std::condition_variable s_condition;
|
||||||
std::condition_variable m_condition;
|
inline static std::atomic<bool> s_run;
|
||||||
bool m_run;
|
|
||||||
std::thread m_thread;
|
|
||||||
|
|
||||||
EventLoop() :
|
EventLoop() = default;
|
||||||
m_run{true},
|
~EventLoop() = default;
|
||||||
m_thread(&EventLoop::run, this)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
EventLoop(const EventLoop&) = delete;
|
EventLoop(const EventLoop&) = delete;
|
||||||
|
EventLoop& operator =(const EventLoop&) = delete;
|
||||||
|
|
||||||
~EventLoop()
|
public:
|
||||||
|
inline static const std::thread::id threadId = std::this_thread::get_id();
|
||||||
|
|
||||||
|
static void exec()
|
||||||
{
|
{
|
||||||
}
|
std::unique_lock<std::mutex> lock(s_queueMutex);
|
||||||
|
|
||||||
void add(std::function<void()>&& f)
|
s_run = true;
|
||||||
{
|
while(s_run)
|
||||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
|
||||||
m_queue.emplace(f);
|
|
||||||
m_condition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
setThreadName("eventloop");
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(m_queueMutex);
|
|
||||||
|
|
||||||
while(m_run)
|
|
||||||
{
|
{
|
||||||
if(m_queue.empty())
|
if(s_queue.empty())
|
||||||
m_condition.wait(lock, [this]{ return !m_queue.empty(); });
|
s_condition.wait(lock, []{ return !s_queue.empty(); });
|
||||||
|
|
||||||
if(m_queue.empty())
|
if(s_queue.empty())
|
||||||
continue; // a suspisius wakeup may occur
|
continue; // a suspisius wakeup may occur
|
||||||
|
|
||||||
std::function<void()>& f{m_queue.front()};
|
std::function<void()>& f{s_queue.front()};
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
@ -89,34 +76,27 @@ class EventLoop
|
|||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
|
|
||||||
m_queue.pop();
|
s_queue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void exit()
|
|
||||||
{
|
|
||||||
add([this](){ m_run = false; });
|
|
||||||
m_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static void start()
|
|
||||||
{
|
|
||||||
s_instance = new EventLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stop()
|
static void stop()
|
||||||
{
|
{
|
||||||
s_instance->exit();
|
s_run = false;
|
||||||
delete s_instance;
|
|
||||||
s_instance = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename _Callable, typename... _Args>
|
template<typename _Callable, typename... _Args>
|
||||||
inline static void call(_Callable&& __f, _Args&&... __args)
|
inline static void call(_Callable&& __f, _Args&&... __args)
|
||||||
{
|
{
|
||||||
s_instance->add(std::bind(__f, __args...));
|
std::lock_guard<std::mutex> lock(s_queueMutex);
|
||||||
|
s_queue.emplace(std::bind(__f, __args...));
|
||||||
|
s_condition.notify_one();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool isEventLoopThread()
|
||||||
|
{
|
||||||
|
return std::this_thread::get_id() == EventLoop::threadId;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -25,7 +25,6 @@
|
|||||||
#include <csignal>
|
#include <csignal>
|
||||||
#endif
|
#endif
|
||||||
#include "options.hpp"
|
#include "options.hpp"
|
||||||
#include "core/eventloop.hpp"
|
|
||||||
#include "traintastic/traintastic.hpp"
|
#include "traintastic/traintastic.hpp"
|
||||||
#include "log/log.hpp"
|
#include "log/log.hpp"
|
||||||
#include <traintastic/locale/locale.hpp>
|
#include <traintastic/locale/locale.hpp>
|
||||||
@ -46,12 +45,8 @@ void signalHandler(int signum)
|
|||||||
signal(SIGINT, SIG_DFL);
|
signal(SIGINT, SIG_DFL);
|
||||||
signal(SIGQUIT, SIG_DFL);
|
signal(SIGQUIT, SIG_DFL);
|
||||||
|
|
||||||
EventLoop::call(
|
Log::log(*Traintastic::instance, LogMessage::N1001_RECEIVED_SIGNAL_X, std::string_view{strsignal(signum)});
|
||||||
[signum]()
|
Traintastic::instance->exit();
|
||||||
{
|
|
||||||
Log::log(*Traintastic::instance, LogMessage::N1001_RECEIVED_SIGNAL_X, std::string_view{strsignal(signum)});
|
|
||||||
Traintastic::instance->exit();
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +169,6 @@ int main(int argc, char* argv[])
|
|||||||
Log::disableFileLogger();
|
Log::disableFileLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventLoop::start();
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if(options.tray)
|
if(options.tray)
|
||||||
Windows::TrayIcon::add();
|
Windows::TrayIcon::add();
|
||||||
@ -209,7 +203,6 @@ int main(int argc, char* argv[])
|
|||||||
if(options.tray)
|
if(options.tray)
|
||||||
Windows::TrayIcon::remove();
|
Windows::TrayIcon::remove();
|
||||||
#endif
|
#endif
|
||||||
EventLoop::stop();
|
|
||||||
}
|
}
|
||||||
while(restart);
|
while(restart);
|
||||||
|
|
||||||
|
|||||||
@ -21,21 +21,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
//#include "console.hpp"
|
#include "server.hpp"
|
||||||
//#include "objectregistry.hpp"
|
|
||||||
#include "../traintastic/traintastic.hpp"
|
#include "../traintastic/traintastic.hpp"
|
||||||
#include "../core/eventloop.hpp"
|
#include "../core/eventloop.hpp"
|
||||||
#include "session.hpp"
|
#include "session.hpp"
|
||||||
#include "../log/log.hpp"
|
#include "../log/log.hpp"
|
||||||
|
|
||||||
Client::Client(Traintastic& server, const std::string& id, boost::asio::ip::tcp::socket socket) :
|
#ifndef NDEBUG
|
||||||
m_server{server},
|
#define IS_SERVER_THREAD (std::this_thread::get_id() == m_server.threadId())
|
||||||
m_strand{Traintastic::instance->ioContext()},
|
#endif
|
||||||
m_socket(std::move(socket)),
|
|
||||||
m_id{id},
|
Client::Client(Server& server, std::string id, boost::asio::ip::tcp::socket socket)
|
||||||
m_authenticated{false}//,
|
: m_server{server}
|
||||||
//m_lastObjectHandle{0}
|
, m_socket(std::move(socket))
|
||||||
|
, m_id{std::move(id)}
|
||||||
|
, m_authenticated{false}
|
||||||
{
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
m_socket.set_option(boost::asio::socket_base::linger(true, 0));
|
m_socket.set_option(boost::asio::socket_base::linger(true, 0));
|
||||||
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||||
|
|
||||||
@ -44,20 +47,22 @@ Client::Client(Traintastic& server, const std::string& id, boost::asio::ip::tcp:
|
|||||||
|
|
||||||
Client::~Client()
|
Client::~Client()
|
||||||
{
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::start()
|
void Client::start()
|
||||||
{
|
{
|
||||||
m_strand.post(
|
assert(IS_SERVER_THREAD);
|
||||||
[this]()
|
|
||||||
{
|
doReadHeader();
|
||||||
doReadHeader();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::stop()
|
void Client::stop()
|
||||||
{
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
m_session.reset();
|
m_session.reset();
|
||||||
|
|
||||||
if(!m_socket.is_open())
|
if(!m_socket.is_open())
|
||||||
@ -72,47 +77,54 @@ void Client::stop()
|
|||||||
|
|
||||||
void Client::doReadHeader()
|
void Client::doReadHeader()
|
||||||
{
|
{
|
||||||
auto self(shared_from_this());
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
boost::asio::async_read(m_socket,
|
boost::asio::async_read(m_socket,
|
||||||
boost::asio::buffer(&m_readBuffer.header, sizeof(m_readBuffer.header)),
|
boost::asio::buffer(&m_readBuffer.header, sizeof(m_readBuffer.header)),
|
||||||
m_strand.wrap(
|
[this, weak=weak_from_this()](const boost::system::error_code& ec, std::size_t /*bytesReceived*/)
|
||||||
[this, self](const boost::system::error_code& ec, std::size_t /*bytesReceived*/)
|
{
|
||||||
|
if(weak.expired())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!ec)
|
||||||
{
|
{
|
||||||
if(!ec)
|
m_readBuffer.message.reset(new Message(m_readBuffer.header));
|
||||||
|
if(m_readBuffer.message->dataSize() == 0)
|
||||||
{
|
{
|
||||||
m_readBuffer.message.reset(new Message(m_readBuffer.header));
|
if(m_readBuffer.message->command() != Message::Command::Ping)
|
||||||
if(m_readBuffer.message->dataSize() == 0)
|
EventLoop::call(&Client::processMessage, this, m_readBuffer.message);
|
||||||
{
|
|
||||||
if(m_readBuffer.message->command() != Message::Command::Ping)
|
|
||||||
EventLoop::call(&Client::processMessage, this, m_readBuffer.message);
|
|
||||||
else
|
|
||||||
{} // TODO: ping hier replyen
|
|
||||||
m_readBuffer.message.reset();
|
|
||||||
doReadHeader();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
doReadData();
|
{} // TODO: ping hier replyen
|
||||||
|
m_readBuffer.message.reset();
|
||||||
|
doReadHeader();
|
||||||
}
|
}
|
||||||
else if(ec == boost::asio::error::eof || ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset)
|
else
|
||||||
EventLoop::call(&Client::connectionLost, this);
|
doReadData();
|
||||||
else if(ec != boost::asio::error::operation_aborted)
|
}
|
||||||
EventLoop::call(
|
else if(ec == boost::asio::error::eof || ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset)
|
||||||
[self, ec]()
|
{
|
||||||
{
|
connectionLost();
|
||||||
Log::log(self->m_id, LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
|
}
|
||||||
self->disconnect();
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
});
|
{
|
||||||
}));
|
Log::log(m_id, LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::doReadData()
|
void Client::doReadData()
|
||||||
{
|
{
|
||||||
auto self(shared_from_this());
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
boost::asio::async_read(m_socket,
|
boost::asio::async_read(m_socket,
|
||||||
boost::asio::buffer(m_readBuffer.message->data(), m_readBuffer.message->dataSize()),
|
boost::asio::buffer(m_readBuffer.message->data(), m_readBuffer.message->dataSize()),
|
||||||
m_strand.wrap(
|
//m_strand.wrap(
|
||||||
[this, self](const boost::system::error_code& ec, std::size_t /*bytesReceived*/)
|
[this, weak=weak_from_this()](const boost::system::error_code& ec, std::size_t /*bytesReceived*/)
|
||||||
{
|
{
|
||||||
|
if(weak.expired())
|
||||||
|
return;
|
||||||
|
|
||||||
if(!ec)
|
if(!ec)
|
||||||
{
|
{
|
||||||
if(m_readBuffer.message->command() != Message::Command::Ping)
|
if(m_readBuffer.message->command() != Message::Command::Ping)
|
||||||
@ -123,42 +135,45 @@ void Client::doReadData()
|
|||||||
doReadHeader();
|
doReadHeader();
|
||||||
}
|
}
|
||||||
else if(ec == boost::asio::error::eof || ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset)
|
else if(ec == boost::asio::error::eof || ec == boost::asio::error::connection_aborted || ec == boost::asio::error::connection_reset)
|
||||||
EventLoop::call(&Client::connectionLost, this);
|
{
|
||||||
|
connectionLost();
|
||||||
|
}
|
||||||
else if(ec != boost::asio::error::operation_aborted)
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
EventLoop::call(
|
{
|
||||||
[self, ec]()
|
Log::log(m_id, LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
|
||||||
{
|
disconnect();
|
||||||
Log::log(self->m_id, LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
|
}
|
||||||
self->disconnect();
|
});
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::doWrite()
|
void Client::doWrite()
|
||||||
{
|
{
|
||||||
auto self(shared_from_this());
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
boost::asio::async_write(m_socket, boost::asio::buffer(**m_writeQueue.front(), m_writeQueue.front()->size()),
|
boost::asio::async_write(m_socket, boost::asio::buffer(**m_writeQueue.front(), m_writeQueue.front()->size()),
|
||||||
m_strand.wrap(
|
[this, weak=weak_from_this()](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/)
|
||||||
[this, self](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/)
|
{
|
||||||
|
if(weak.expired())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!ec)
|
||||||
{
|
{
|
||||||
if(!ec)
|
m_writeQueue.pop();
|
||||||
{
|
if(!m_writeQueue.empty())
|
||||||
m_writeQueue.pop();
|
doWrite();
|
||||||
if(!m_writeQueue.empty())
|
}
|
||||||
doWrite();
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
}
|
{
|
||||||
else if(ec != boost::asio::error::operation_aborted)
|
Log::log(m_id, LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec);
|
||||||
EventLoop::call(
|
disconnect();
|
||||||
[self, ec]()
|
}
|
||||||
{
|
});
|
||||||
Log::log(self->m_id, LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec);
|
|
||||||
self->disconnect();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::processMessage(const std::shared_ptr<Message> message)
|
void Client::processMessage(const std::shared_ptr<Message> message)
|
||||||
{
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
if(m_authenticated && m_session)
|
if(m_authenticated && m_session)
|
||||||
{
|
{
|
||||||
if(m_session->processMessage(*message))
|
if(m_session->processMessage(*message))
|
||||||
@ -281,7 +296,9 @@ void Client::processMessage(const std::shared_ptr<Message> message)
|
|||||||
|
|
||||||
void Client::sendMessage(std::unique_ptr<Message> message)
|
void Client::sendMessage(std::unique_ptr<Message> message)
|
||||||
{
|
{
|
||||||
m_strand.post(
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_server.m_ioContext.post(
|
||||||
[this, msg=std::make_shared<std::unique_ptr<Message>>(std::move(message))]()
|
[this, msg=std::make_shared<std::unique_ptr<Message>>(std::move(message))]()
|
||||||
{
|
{
|
||||||
const bool wasEmpty = m_writeQueue.empty();
|
const bool wasEmpty = m_writeQueue.empty();
|
||||||
@ -293,12 +310,16 @@ void Client::sendMessage(std::unique_ptr<Message> message)
|
|||||||
|
|
||||||
void Client::connectionLost()
|
void Client::connectionLost()
|
||||||
{
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
Log::log(m_id, LogMessage::I1004_CONNECTION_LOST);
|
Log::log(m_id, LogMessage::I1004_CONNECTION_LOST);
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::disconnect()
|
void Client::disconnect()
|
||||||
{
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
m_server.clientGone(shared_from_this());
|
m_server.clientGone(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,23 +23,13 @@
|
|||||||
#ifndef TRAINTASTIC_SERVER_NETWORK_CLIENT_HPP
|
#ifndef TRAINTASTIC_SERVER_NETWORK_CLIENT_HPP
|
||||||
#define TRAINTASTIC_SERVER_NETWORK_CLIENT_HPP
|
#define TRAINTASTIC_SERVER_NETWORK_CLIENT_HPP
|
||||||
|
|
||||||
//#include "Message.hpp"
|
|
||||||
//#include "MessageQueue.hpp"
|
|
||||||
//#include <thread>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
//#include <deque>
|
|
||||||
//#include <list>
|
|
||||||
//#include <condition_variable>
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
//#include "connection-callback.hpp"
|
|
||||||
//#include "status.hpp"
|
|
||||||
//#include <chrono>
|
|
||||||
//#include <cmath>
|
|
||||||
#include "../core/objectptr.hpp"
|
#include "../core/objectptr.hpp"
|
||||||
#include <traintastic/network/message.hpp>
|
#include <traintastic/network/message.hpp>
|
||||||
|
|
||||||
class Traintastic;
|
class Server;
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
class Client : public std::enable_shared_from_this<Client>
|
class Client : public std::enable_shared_from_this<Client>
|
||||||
@ -49,8 +39,7 @@ class Client : public std::enable_shared_from_this<Client>
|
|||||||
protected:
|
protected:
|
||||||
using ObjectHandle = uint32_t;
|
using ObjectHandle = uint32_t;
|
||||||
|
|
||||||
Traintastic& m_server;
|
Server& m_server;
|
||||||
boost::asio::io_service::strand m_strand;
|
|
||||||
boost::asio::ip::tcp::socket m_socket;
|
boost::asio::ip::tcp::socket m_socket;
|
||||||
const std::string m_id;
|
const std::string m_id;
|
||||||
struct
|
struct
|
||||||
@ -62,8 +51,6 @@ class Client : public std::enable_shared_from_this<Client>
|
|||||||
std::queue<std::unique_ptr<Message>> m_writeQueue;
|
std::queue<std::unique_ptr<Message>> m_writeQueue;
|
||||||
bool m_authenticated;
|
bool m_authenticated;
|
||||||
std::shared_ptr<Session> m_session;
|
std::shared_ptr<Session> m_session;
|
||||||
//ObjectHandle m_lastObjectHandle;
|
|
||||||
//std::map<ObjectHandle,ObjectPtr> m_objects;
|
|
||||||
|
|
||||||
void doReadHeader();
|
void doReadHeader();
|
||||||
void doReadData();
|
void doReadData();
|
||||||
@ -76,7 +63,7 @@ class Client : public std::enable_shared_from_this<Client>
|
|||||||
void disconnect();
|
void disconnect();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Client(Traintastic& server, const std::string& id, boost::asio::ip::tcp::socket socket);
|
Client(Server& server, std::string id, boost::asio::ip::tcp::socket socket);
|
||||||
virtual ~Client();
|
virtual ~Client();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
|||||||
256
server/src/network/server.cpp
Normale Datei
256
server/src/network/server.cpp
Normale Datei
@ -0,0 +1,256 @@
|
|||||||
|
/**
|
||||||
|
* server/src/network/server.cpp
|
||||||
|
*
|
||||||
|
* This file is part of the traintastic source code.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 Reinder Feenstra
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "server.hpp"
|
||||||
|
#include <traintastic/network/message.hpp>
|
||||||
|
#include <version.hpp>
|
||||||
|
#include "client.hpp"
|
||||||
|
#include "../core/eventloop.hpp"
|
||||||
|
#include "../log/log.hpp"
|
||||||
|
#include "../utils/setthreadname.hpp"
|
||||||
|
|
||||||
|
#define IS_SERVER_THREAD (std::this_thread::get_id() == m_thread.get_id())
|
||||||
|
|
||||||
|
Server::Server()
|
||||||
|
: m_ioContext{1}
|
||||||
|
, m_acceptor{m_ioContext}
|
||||||
|
, m_socketTCP{m_ioContext}
|
||||||
|
, m_socketUDP{m_ioContext}
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
assert(m_ioContext.stopped());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server::start(bool localhostOnly, uint16_t port, bool discoverable)
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_localhostOnly = localhostOnly;
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::asio::ip::tcp::endpoint endpoint(localhostOnly ? boost::asio::ip::address_v4::loopback() : boost::asio::ip::address_v4::any(), port);
|
||||||
|
|
||||||
|
m_acceptor.open(endpoint.protocol(), ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1001_OPENING_TCP_SOCKET_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1002_TCP_SOCKET_ADDRESS_REUSE_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_acceptor.bind(endpoint, ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1003_BINDING_TCP_SOCKET_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_acceptor.listen(5, ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1004_TCP_SOCKET_LISTEN_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(discoverable)
|
||||||
|
{
|
||||||
|
if(port == defaultPort)
|
||||||
|
{
|
||||||
|
m_socketUDP.open(boost::asio::ip::udp::v4(), ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1005_OPENING_UDP_SOCKET_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_socketUDP.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1006_UDP_SOCKET_ADDRESS_REUSE_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_socketUDP.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), defaultPort), ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::F1007_BINDING_UDP_SOCKET_FAILED_X, ec.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::log(id, LogMessage::N1005_DISCOVERY_ENABLED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::W1001_DISCOVERY_DISABLED_ONLY_ALLOWED_ON_PORT_X, defaultPort);
|
||||||
|
discoverable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::log(id, LogMessage::N1006_DISCOVERY_DISABLED);
|
||||||
|
|
||||||
|
Log::log(id, LogMessage::N1007_LISTENING_AT_X_X, m_acceptor.local_endpoint().address().to_string(), m_acceptor.local_endpoint().port());
|
||||||
|
|
||||||
|
m_thread = std::thread(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
setThreadName("server");
|
||||||
|
auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
|
||||||
|
m_ioContext.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this, discoverable]()
|
||||||
|
{
|
||||||
|
if(discoverable)
|
||||||
|
doReceive();
|
||||||
|
|
||||||
|
doAccept();
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::stop()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
if(m_acceptor.cancel(ec))
|
||||||
|
Log::log(id, LogMessage::E1008_SOCKET_ACCEPTOR_CANCEL_FAILED_X, ec);
|
||||||
|
|
||||||
|
m_acceptor.close();
|
||||||
|
|
||||||
|
m_socketUDP.close();
|
||||||
|
|
||||||
|
for(auto& client : m_clients)
|
||||||
|
{
|
||||||
|
client->stop();
|
||||||
|
client.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ioContext.stop();
|
||||||
|
|
||||||
|
m_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::clientGone(const std::shared_ptr<Client>& client)
|
||||||
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
|
m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::doReceive()
|
||||||
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
|
m_socketUDP.async_receive_from(boost::asio::buffer(m_udpBuffer), m_remoteEndpoint,
|
||||||
|
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
if(bytesReceived == sizeof(Message::Header))
|
||||||
|
{
|
||||||
|
Message message(*reinterpret_cast<Message::Header*>(m_udpBuffer.data()));
|
||||||
|
|
||||||
|
if(!m_localhostOnly || m_remoteEndpoint.address().is_loopback())
|
||||||
|
{
|
||||||
|
if(message.dataSize() == 0)
|
||||||
|
{
|
||||||
|
std::unique_ptr<Message> response = processMessage(message);
|
||||||
|
if(response)
|
||||||
|
{
|
||||||
|
m_socketUDP.async_send_to(boost::asio::buffer(**response, response->size()), m_remoteEndpoint,
|
||||||
|
[this](const boost::system::error_code& /*ec*/, std::size_t /*bytesTransferred*/)
|
||||||
|
{
|
||||||
|
doReceive();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doReceive();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::log(id, LogMessage::E1003_UDP_RECEIVE_ERROR_X, ec.message());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Message> Server::processMessage(const Message& message)
|
||||||
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
|
if(message.command() == Message::Command::Discover && message.isRequest())
|
||||||
|
{
|
||||||
|
std::unique_ptr<Message> response = Message::newResponse(message.command(), message.requestId());
|
||||||
|
response->write(boost::asio::ip::host_name());
|
||||||
|
response->write<uint16_t>(TRAINTASTIC_VERSION_MAJOR);
|
||||||
|
response->write<uint16_t>(TRAINTASTIC_VERSION_MINOR);
|
||||||
|
response->write<uint16_t>(TRAINTASTIC_VERSION_PATCH);
|
||||||
|
assert(response->size() <= 1500); // must fit in a UDP packet
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::doAccept()
|
||||||
|
{
|
||||||
|
assert(IS_SERVER_THREAD);
|
||||||
|
|
||||||
|
m_acceptor.async_accept(m_socketTCP,
|
||||||
|
[this](boost::system::error_code ec)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::shared_ptr<Client> client = std::make_shared<Client>(*this, "client[" + m_socketTCP.remote_endpoint().address().to_string() + ":" + std::to_string(m_socketTCP.remote_endpoint().port()) + "]", std::move(m_socketTCP));
|
||||||
|
client->start();
|
||||||
|
m_clients.push_back(client);
|
||||||
|
doAccept();
|
||||||
|
}
|
||||||
|
catch(const std::exception& e)
|
||||||
|
{
|
||||||
|
Log::log(id, LogMessage::C1002_CREATING_CLIENT_FAILED_X, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::log(id, LogMessage::E1004_TCP_ACCEPT_ERROR_X, ec.message());
|
||||||
|
});
|
||||||
|
}
|
||||||
73
server/src/network/server.hpp
Normale Datei
73
server/src/network/server.hpp
Normale Datei
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* server/src/network/server.hpp
|
||||||
|
*
|
||||||
|
* This file is part of the traintastic source code.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 Reinder Feenstra
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRAINTASTIC_SERVER_NETWORK_SERVER_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_NETWORK_SERVER_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
#include <list>
|
||||||
|
#include <thread>
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <boost/asio/ip/udp.hpp>
|
||||||
|
|
||||||
|
class Client;
|
||||||
|
class Message;
|
||||||
|
|
||||||
|
class Server : public std::enable_shared_from_this<Server>
|
||||||
|
{
|
||||||
|
friend class Client;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::string_view id{"server"};
|
||||||
|
boost::asio::io_context m_ioContext;
|
||||||
|
std::thread m_thread;
|
||||||
|
boost::asio::ip::tcp::acceptor m_acceptor;
|
||||||
|
boost::asio::ip::tcp::socket m_socketTCP;
|
||||||
|
boost::asio::ip::udp::socket m_socketUDP;
|
||||||
|
std::array<char, 8> m_udpBuffer;
|
||||||
|
boost::asio::ip::udp::endpoint m_remoteEndpoint;
|
||||||
|
bool m_localhostOnly;
|
||||||
|
std::list<std::shared_ptr<Client>> m_clients;
|
||||||
|
|
||||||
|
void doReceive();
|
||||||
|
std::unique_ptr<Message> processMessage(const Message& message);
|
||||||
|
void doAccept();
|
||||||
|
|
||||||
|
void clientGone(const std::shared_ptr<Client>& client);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr uint16_t defaultPort = 5740; //!< unoffical, not (yet) assigned by IANA
|
||||||
|
|
||||||
|
Server();
|
||||||
|
~Server();
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
inline auto threadId() const { return m_thread.get_id(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool start(bool localhostOnly, uint16_t port, bool discoverable);
|
||||||
|
void stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -25,6 +25,7 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "../core/attributes.hpp"
|
#include "../core/attributes.hpp"
|
||||||
#include "traintastic.hpp"
|
#include "traintastic.hpp"
|
||||||
|
#include "../network/server.hpp"
|
||||||
#include "../log/log.hpp"
|
#include "../log/log.hpp"
|
||||||
#include "../utils/category.hpp"
|
#include "../utils/category.hpp"
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ Settings::PreStart Settings::getPreStartSettings(const std::filesystem::path& pa
|
|||||||
Settings::Settings(const std::filesystem::path& path)
|
Settings::Settings(const std::filesystem::path& path)
|
||||||
: m_filename{path / filename}
|
: m_filename{path / filename}
|
||||||
, localhostOnly{this, "localhost_only", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
, localhostOnly{this, "localhost_only", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
||||||
, port{this, "port", defaultPort, PropertyFlags::ReadWrite, [this](const uint16_t& /*value*/){ saveToFile(); }}
|
, port{this, "port", Server::defaultPort, PropertyFlags::ReadWrite, [this](const uint16_t& /*value*/){ saveToFile(); }}
|
||||||
, discoverable{this, "discoverable", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
, discoverable{this, "discoverable", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
||||||
, lastWorld{this, "last_world", "", PropertyFlags::ReadWrite | PropertyFlags::Internal, [this](const std::string& /*value*/){ saveToFile(); }}
|
, lastWorld{this, "last_world", "", PropertyFlags::ReadWrite | PropertyFlags::Internal, [this](const std::string& /*value*/){ saveToFile(); }}
|
||||||
, loadLastWorldOnStartup{this, "load_last_world_on_startup", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
, loadLastWorldOnStartup{this, "load_last_world_on_startup", true, PropertyFlags::ReadWrite, [this](const bool& /*value*/){ saveToFile(); }}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* This file is part of the traintastic source code.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -60,7 +60,6 @@ class Settings : public Object
|
|||||||
};
|
};
|
||||||
|
|
||||||
static constexpr std::string_view id = classId;
|
static constexpr std::string_view id = classId;
|
||||||
static constexpr uint16_t defaultPort = 5740; //!< unoffical, not (yet) assigned by IANA
|
|
||||||
|
|
||||||
static PreStart getPreStartSettings(const std::filesystem::path& path);
|
static PreStart getPreStartSettings(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
#include <traintastic/codename.hpp>
|
#include <traintastic/codename.hpp>
|
||||||
#include <traintastic/utils/str.hpp>
|
#include <traintastic/utils/str.hpp>
|
||||||
#include "../core/eventloop.hpp"
|
#include "../core/eventloop.hpp"
|
||||||
#include "../network/client.hpp"
|
#include "../network/server.hpp"
|
||||||
#include "../core/attributes.hpp"
|
#include "../core/attributes.hpp"
|
||||||
#include "../world/world.hpp"
|
#include "../world/world.hpp"
|
||||||
#include "../world/worldlist.hpp"
|
#include "../world/worldlist.hpp"
|
||||||
@ -43,10 +43,7 @@ std::shared_ptr<Traintastic> Traintastic::instance;
|
|||||||
Traintastic::Traintastic(const std::filesystem::path& dataDir) :
|
Traintastic::Traintastic(const std::filesystem::path& dataDir) :
|
||||||
m_restart{false},
|
m_restart{false},
|
||||||
m_dataDir{std::filesystem::absolute(dataDir)},
|
m_dataDir{std::filesystem::absolute(dataDir)},
|
||||||
m_ioContext{},
|
m_server{std::make_shared<Server>()},
|
||||||
m_acceptor{m_ioContext},
|
|
||||||
m_socketTCP{m_ioContext},
|
|
||||||
m_socketUDP{m_ioContext},
|
|
||||||
settings{this, "settings", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
|
settings{this, "settings", nullptr, PropertyFlags::ReadWrite/*ReadOnly*/},
|
||||||
world{this, "world", nullptr, PropertyFlags::ReadWrite,
|
world{this, "world", nullptr, PropertyFlags::ReadWrite,
|
||||||
[this](const std::shared_ptr<World>& /*newWorld*/)
|
[this](const std::shared_ptr<World>& /*newWorld*/)
|
||||||
@ -130,11 +127,6 @@ Traintastic::Traintastic(const std::filesystem::path& dataDir) :
|
|||||||
m_interfaceItems.add(shutdown);
|
m_interfaceItems.add(shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
Traintastic::~Traintastic()
|
|
||||||
{
|
|
||||||
assert(m_ioContext.stopped());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Traintastic::importWorld(const std::vector<std::byte>& worldData)
|
bool Traintastic::importWorld(const std::vector<std::byte>& worldData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -169,11 +161,10 @@ Traintastic::RunStatus Traintastic::run()
|
|||||||
if(settings->loadLastWorldOnStartup && !settings->lastWorld.value().empty())
|
if(settings->loadLastWorldOnStartup && !settings->lastWorld.value().empty())
|
||||||
loadWorld(settings->lastWorld.value());
|
loadWorld(settings->lastWorld.value());
|
||||||
|
|
||||||
if(!start())
|
if(!m_server->start(settings->localhostOnly, settings->port, settings->discoverable))
|
||||||
return ExitFailure;
|
return ExitFailure;
|
||||||
|
|
||||||
auto work = std::make_shared<boost::asio::io_service::work>(m_ioContext);
|
EventLoop::exec();
|
||||||
m_ioContext.run();
|
|
||||||
|
|
||||||
return m_restart ? Restart : ExitSuccess;
|
return m_restart ? Restart : ExitSuccess;
|
||||||
}
|
}
|
||||||
@ -188,99 +179,7 @@ void Traintastic::exit()
|
|||||||
if(settings->autoSaveWorldOnExit && world)
|
if(settings->autoSaveWorldOnExit && world)
|
||||||
world->save();
|
world->save();
|
||||||
|
|
||||||
stop();
|
EventLoop::stop();
|
||||||
|
|
||||||
m_ioContext.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Traintastic::start()
|
|
||||||
{
|
|
||||||
boost::system::error_code ec;
|
|
||||||
boost::asio::ip::tcp::endpoint endpoint(settings->localhostOnly ? boost::asio::ip::address_v4::loopback() : boost::asio::ip::address_v4::any(), settings->port);
|
|
||||||
|
|
||||||
m_acceptor.open(endpoint.protocol(), ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1001_OPENING_TCP_SOCKET_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1002_TCP_SOCKET_ADDRESS_REUSE_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_acceptor.bind(endpoint, ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1003_BINDING_TCP_SOCKET_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_acceptor.listen(5, ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1004_TCP_SOCKET_LISTEN_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(settings->discoverable)
|
|
||||||
{
|
|
||||||
if(settings->port == Settings::defaultPort)
|
|
||||||
{
|
|
||||||
m_socketUDP.open(boost::asio::ip::udp::v4(), ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1005_OPENING_UDP_SOCKET_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_socketUDP.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1006_UDP_SOCKET_ADDRESS_REUSE_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_socketUDP.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), Settings::defaultPort), ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::F1007_BINDING_UDP_SOCKET_FAILED_X, ec.message());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::log(*this, LogMessage::N1005_DISCOVERY_ENABLED);
|
|
||||||
doReceive();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::log(*this, LogMessage::W1001_DISCOVERY_DISABLED_ONLY_ALLOWED_ON_PORT_X, Settings::defaultPort);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::log(*this, LogMessage::N1006_DISCOVERY_DISABLED);
|
|
||||||
|
|
||||||
Log::log(*this, LogMessage::N1007_LISTENING_AT_X_X, m_acceptor.local_endpoint().address().to_string(), m_acceptor.local_endpoint().port());
|
|
||||||
doAccept();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Traintastic::stop()
|
|
||||||
{
|
|
||||||
for(auto& client : m_clients)
|
|
||||||
{
|
|
||||||
client->stop();
|
|
||||||
client.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::system::error_code ec;
|
|
||||||
m_acceptor.cancel(ec);
|
|
||||||
if(ec)
|
|
||||||
Log::log(*this, LogMessage::E1008_SOCKET_ACCEPTOR_CANCEL_FAILED_X, ec);
|
|
||||||
m_acceptor.close();
|
|
||||||
|
|
||||||
m_socketUDP.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Traintastic::loadWorldUUID(const boost::uuids::uuid& uuid)
|
void Traintastic::loadWorldUUID(const boost::uuids::uuid& uuid)
|
||||||
@ -309,87 +208,3 @@ void Traintastic::loadWorldPath(const std::filesystem::path& path)
|
|||||||
Log::log(*this, LogMessage::C1001_LOADING_WORLD_FAILED_X, e.what());
|
Log::log(*this, LogMessage::C1001_LOADING_WORLD_FAILED_X, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Traintastic::clientGone(const std::shared_ptr<Client>& client)
|
|
||||||
{
|
|
||||||
m_clients.erase(std::find(m_clients.begin(), m_clients.end(), client));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Traintastic::doReceive()
|
|
||||||
{
|
|
||||||
m_socketUDP.async_receive_from(boost::asio::buffer(m_udpBuffer), m_remoteEndpoint,
|
|
||||||
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
|
|
||||||
{
|
|
||||||
if(!ec)
|
|
||||||
{
|
|
||||||
if(bytesReceived == sizeof(Message::Header))
|
|
||||||
{
|
|
||||||
Message message(*reinterpret_cast<Message::Header*>(m_udpBuffer.data()));
|
|
||||||
|
|
||||||
if(!settings->localhostOnly || m_remoteEndpoint.address().is_loopback())
|
|
||||||
{
|
|
||||||
if(message.dataSize() == 0)
|
|
||||||
{
|
|
||||||
std::unique_ptr<Message> response = processMessage(message);
|
|
||||||
if(response)
|
|
||||||
{
|
|
||||||
m_socketUDP.async_send_to(boost::asio::buffer(**response, response->size()), m_remoteEndpoint,
|
|
||||||
[this](const boost::system::error_code& /*ec*/, std::size_t /*bytesTransferred*/)
|
|
||||||
{
|
|
||||||
doReceive();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
doReceive();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::log(*this, LogMessage::E1003_UDP_RECEIVE_ERROR_X, ec.message());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Message> Traintastic::processMessage(const Message& message)
|
|
||||||
{
|
|
||||||
if(message.command() == Message::Command::Discover && message.isRequest())
|
|
||||||
{
|
|
||||||
std::unique_ptr<Message> response = Message::newResponse(message.command(), message.requestId());
|
|
||||||
response->write(boost::asio::ip::host_name());
|
|
||||||
response->write<uint16_t>(TRAINTASTIC_VERSION_MAJOR);
|
|
||||||
response->write<uint16_t>(TRAINTASTIC_VERSION_MINOR);
|
|
||||||
response->write<uint16_t>(TRAINTASTIC_VERSION_PATCH);
|
|
||||||
assert(response->size() <= 1500); // must fit in a UDP packet
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Traintastic::doAccept()
|
|
||||||
{
|
|
||||||
m_acceptor.async_accept(m_socketTCP,
|
|
||||||
[this](boost::system::error_code ec)
|
|
||||||
{
|
|
||||||
EventLoop::call(
|
|
||||||
[this, ec]()
|
|
||||||
{
|
|
||||||
if(!ec)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::shared_ptr<Client> client = std::make_shared<Client>(*this, "client[" + m_socketTCP.remote_endpoint().address().to_string() + ":" + std::to_string(m_socketTCP.remote_endpoint().port()) + "]", std::move(m_socketTCP));
|
|
||||||
client->start();
|
|
||||||
m_clients.push_back(client);
|
|
||||||
doAccept();
|
|
||||||
}
|
|
||||||
catch(const std::exception& e)
|
|
||||||
{
|
|
||||||
Log::log(*this, LogMessage::C1002_CREATING_CLIENT_FAILED_X, e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::log(*this, LogMessage::E1004_TCP_ACCEPT_ERROR_X, ec.message());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -35,13 +35,10 @@
|
|||||||
#include "../world/world.hpp"
|
#include "../world/world.hpp"
|
||||||
#include "../world/worldlist.hpp"
|
#include "../world/worldlist.hpp"
|
||||||
|
|
||||||
class Client;
|
class Server;
|
||||||
class Message;
|
|
||||||
|
|
||||||
class Traintastic final : public Object
|
class Traintastic final : public Object
|
||||||
{
|
{
|
||||||
friend class Client;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum RunStatus
|
enum RunStatus
|
||||||
{
|
{
|
||||||
@ -53,13 +50,7 @@ class Traintastic final : public Object
|
|||||||
private:
|
private:
|
||||||
bool m_restart;
|
bool m_restart;
|
||||||
const std::filesystem::path m_dataDir;
|
const std::filesystem::path m_dataDir;
|
||||||
boost::asio::io_context m_ioContext;
|
std::shared_ptr<Server> m_server;
|
||||||
boost::asio::ip::tcp::acceptor m_acceptor;
|
|
||||||
boost::asio::ip::tcp::socket m_socketTCP;
|
|
||||||
boost::asio::ip::udp::socket m_socketUDP;
|
|
||||||
std::array<char, 8> m_udpBuffer;
|
|
||||||
boost::asio::ip::udp::endpoint m_remoteEndpoint;
|
|
||||||
std::list<std::shared_ptr<Client>> m_clients;
|
|
||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
void stop();
|
void stop();
|
||||||
@ -67,13 +58,6 @@ class Traintastic final : public Object
|
|||||||
void loadWorldUUID(const boost::uuids::uuid& uuid);
|
void loadWorldUUID(const boost::uuids::uuid& uuid);
|
||||||
void loadWorldPath(const std::filesystem::path& path);
|
void loadWorldPath(const std::filesystem::path& path);
|
||||||
|
|
||||||
void doReceive();
|
|
||||||
static std::unique_ptr<Message> processMessage(const Message& message);
|
|
||||||
void doAccept();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void clientGone(const std::shared_ptr<Client>& client);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CLASS_ID("traintastic");
|
CLASS_ID("traintastic");
|
||||||
|
|
||||||
@ -91,12 +75,10 @@ class Traintastic final : public Object
|
|||||||
Method<void()> shutdown;
|
Method<void()> shutdown;
|
||||||
|
|
||||||
Traintastic(const std::filesystem::path& dataDir);
|
Traintastic(const std::filesystem::path& dataDir);
|
||||||
~Traintastic() final;
|
~Traintastic() final = default;
|
||||||
|
|
||||||
std::string getObjectId() const final { return std::string(id); }
|
std::string getObjectId() const final { return std::string(id); }
|
||||||
|
|
||||||
boost::asio::io_context& ioContext() { return m_ioContext; }
|
|
||||||
|
|
||||||
const std::filesystem::path& dataDir() const { return m_dataDir; }
|
const std::filesystem::path& dataDir() const { return m_dataDir; }
|
||||||
std::filesystem::path dataBackupDir() const { return m_dataDir / ".backup"; }
|
std::filesystem::path dataBackupDir() const { return m_dataDir / ".backup"; }
|
||||||
|
|
||||||
|
|||||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren