initial
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
project(tw_chat_server_exe)
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
src/ChatServerController.cpp
|
||||
src/ChatServerRuntime.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
src/
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
tw::chat::service
|
||||
tw::messaging
|
||||
tw::protocol
|
||||
tw::network
|
||||
spdlog::spdlog
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "ChatServerController.hpp"
|
||||
|
||||
#include "Chat.pb.h"
|
||||
#include "MessageRegistry.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace tw::chat {
|
||||
|
||||
static mmo::chat::ChatErrorCode to_error_code(tl::expected<void, ChatServerError> result) {
|
||||
if (result) return mmo::chat::CHAT_ERROR_CODE_OK;
|
||||
switch (result.error()) {
|
||||
case ChatServerError::PermissionDenied: return mmo::chat::CHAT_ERROR_CODE_NOT_IN_CHANNEL;
|
||||
case ChatServerError::ChannelNotFound: return mmo::chat::CHAT_ERROR_CODE_CHANNEL_NOT_FOUND;
|
||||
}
|
||||
return mmo::chat::CHAT_ERROR_CODE_CHANNEL_NOT_FOUND;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::vector<std::byte> serialize(const T& msg) {
|
||||
std::vector<std::byte> buf(msg.ByteSizeLong());
|
||||
(void)msg.SerializeToArray(buf.data(), static_cast<int>(buf.size()));
|
||||
return buf;
|
||||
}
|
||||
|
||||
ChatServerController::ChatServerController(int port)
|
||||
: m_endpoint(net::quicr::QuicrEndpoint::create_and_bind(port).value())
|
||||
, m_listener(net::quicr::QuicrConnectionListener::listen(m_endpoint.get()).value())
|
||||
, m_service([this](uint64_t id, const ChatMessage& msg) { broadcast(id, msg); })
|
||||
{
|
||||
register_handlers();
|
||||
spdlog::info("Chat server listening on port {}", port);
|
||||
}
|
||||
|
||||
void ChatServerController::register_handlers() {
|
||||
m_handlers[Message<mmo::chat::SendChatMessageRequest>::value] =
|
||||
[this](uint64_t client_id, std::span<const std::byte> data) {
|
||||
mmo::chat::SendChatMessageRequest msg;
|
||||
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
mmo::chat::SendChatMessageResponse r;
|
||||
r.set_channel_id(msg.channel_id());
|
||||
r.set_error(to_error_code(m_service.send_message(client_id, msg.channel_id(), msg.message())));
|
||||
send_to(client_id, Message<mmo::chat::SendChatMessageResponse>::value, serialize(r));
|
||||
};
|
||||
|
||||
m_handlers[Message<mmo::chat::JoinChannelRequest>::value] =
|
||||
[this](uint64_t client_id, std::span<const std::byte> data) {
|
||||
mmo::chat::JoinChannelRequest msg;
|
||||
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
m_service.join_channel(client_id, msg.channel_id());
|
||||
mmo::chat::JoinChannelResponse r;
|
||||
r.set_channel_id(msg.channel_id());
|
||||
r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
|
||||
send_to(client_id, Message<mmo::chat::JoinChannelResponse>::value, serialize(r));
|
||||
};
|
||||
|
||||
m_handlers[Message<mmo::chat::LeaveChannelRequest>::value] =
|
||||
[this](uint64_t client_id, std::span<const std::byte> data) {
|
||||
mmo::chat::LeaveChannelRequest msg;
|
||||
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
m_service.leave_channel(client_id, msg.channel_id());
|
||||
mmo::chat::LeaveChannelResponse r;
|
||||
r.set_channel_id(msg.channel_id());
|
||||
r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
|
||||
send_to(client_id, Message<mmo::chat::LeaveChannelResponse>::value, serialize(r));
|
||||
};
|
||||
}
|
||||
|
||||
void ChatServerController::update() {
|
||||
m_endpoint->poll();
|
||||
|
||||
net::quicr::QuicrConnection* conn = nullptr;
|
||||
while ((conn = m_listener->listen())) {
|
||||
m_connections.emplace(conn->self_id(), conn);
|
||||
spdlog::info("Chat client connected: {}", conn->self_id());
|
||||
}
|
||||
|
||||
for (auto& [client_id, conn] : m_connections) {
|
||||
auto r = conn->read_into(m_recv_buf);
|
||||
if (!r || *r == 0) continue;
|
||||
dispatch(client_id, std::span(m_recv_buf.data(), *r));
|
||||
}
|
||||
}
|
||||
|
||||
void ChatServerController::dispatch(uint64_t client_id, std::span<const std::byte> data) {
|
||||
constexpr size_t HEADER = sizeof(uint32_t) * 2;
|
||||
if (data.size() < HEADER) {
|
||||
spdlog::warn("ChatServerController: dropped short datagram ({} bytes)", data.size());
|
||||
return;
|
||||
}
|
||||
uint32_t type{};
|
||||
std::memcpy(&type, data.data(), sizeof(type));
|
||||
|
||||
if (type >= m_handlers.size() || !m_handlers[type]) {
|
||||
spdlog::warn("ChatServerController: no handler for type {}", type);
|
||||
return;
|
||||
}
|
||||
m_handlers[type](client_id, data.subspan(HEADER));
|
||||
}
|
||||
|
||||
void ChatServerController::send_to(uint64_t client_id, uint32_t type,
|
||||
std::span<const std::byte> payload, bool reliable) {
|
||||
auto it = m_connections.find(client_id);
|
||||
if (it == m_connections.end()) return;
|
||||
|
||||
constexpr uint32_t SEQ_NONE = 0;
|
||||
std::vector<std::byte> buf(sizeof(type) + sizeof(SEQ_NONE) + payload.size());
|
||||
std::memcpy(buf.data(), &type, sizeof(type));
|
||||
std::memcpy(buf.data() + sizeof(type), &SEQ_NONE, sizeof(SEQ_NONE));
|
||||
std::memcpy(buf.data() + sizeof(type) + sizeof(SEQ_NONE), payload.data(), payload.size());
|
||||
(void)it->second->send_message(std::span(buf), reliable);
|
||||
}
|
||||
|
||||
void ChatServerController::broadcast(uint64_t client_id, const ChatMessage& msg) {
|
||||
mmo::chat::ChatMessageBroadcastRequest bcast;
|
||||
bcast.set_channel_id(msg.channel_id);
|
||||
bcast.set_sender_id(msg.client_id);
|
||||
bcast.set_message(msg.message);
|
||||
send_to(client_id, Message<mmo::chat::ChatMessageBroadcastRequest>::value,
|
||||
serialize(bcast), true);
|
||||
}
|
||||
|
||||
} // namespace tw::chat
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "ChatService.hpp"
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace tw::chat {
|
||||
|
||||
class ChatServerController {
|
||||
static constexpr size_t MAX_TYPES = 32;
|
||||
|
||||
std::unique_ptr<net::quicr::QuicrEndpoint> m_endpoint;
|
||||
std::unique_ptr<net::quicr::QuicrConnectionListener> m_listener;
|
||||
std::unordered_map<uint64_t, net::quicr::QuicrConnection*> m_connections;
|
||||
std::vector<std::byte> m_recv_buf{64 * 1024};
|
||||
ChatService m_service;
|
||||
|
||||
std::array<std::function<void(uint64_t, std::span<const std::byte>)>, MAX_TYPES> m_handlers{};
|
||||
|
||||
public:
|
||||
explicit ChatServerController(int port = CHAT_DEFAULT_PORT);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
void register_handlers();
|
||||
void dispatch(uint64_t client_id, std::span<const std::byte> data);
|
||||
void send_to(uint64_t client_id, uint32_t type, std::span<const std::byte> payload,
|
||||
bool reliable = false);
|
||||
void broadcast(uint64_t client_id, const ChatMessage& msg);
|
||||
};
|
||||
|
||||
} // namespace tw::chat
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "ChatServerController.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
static std::atomic<bool> g_quit{false};
|
||||
|
||||
static void on_signal(int) {
|
||||
g_quit.store(true);
|
||||
}
|
||||
|
||||
static void register_signal_handler() {
|
||||
struct sigaction sa{};
|
||||
sa.sa_handler = on_signal;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
}
|
||||
|
||||
int main() {
|
||||
register_signal_handler();
|
||||
|
||||
spdlog::info("Starting chat server on port {}", tw::chat::CHAT_DEFAULT_PORT);
|
||||
|
||||
tw::chat::ChatServerController controller;
|
||||
|
||||
while (!g_quit.load()) {
|
||||
controller.update();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
spdlog::info("Chat server shutting down");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user