lundi 16 mai 2016

C++ server design - stuck handling packets

I've been coding a OO server this weekend. There is problem I cannot overcome and have to ask for guidance. I added a lot of comments in the problematic part of code. Quickly on design: pacbuf is an instance of packet_buffer which receives chunks of data and launches the lambda is the packet is complete (first 2 bytes of raw data is the size, so for example 0x06 0x00 0x01 0x22 is a valid packet of length 4). I am completly stuck on handling the parsed packet. I do not know how can I interpret an abstract response from received_packet (which is an abstract base class for any concrete packet). client instance represents one user (think of it as of session). Below I attach the main handler with my comments and example of concrete packets.

void client::process_received_data(const char data[], size_t size)
{
    pacbuf.append_raw_data(data, size);
    pacbuf.retrieve_packets([this](unsigned short opcode, std::string&& data)
    {
        //encdec is a class responsible for decrypting valid packet
        encdec->decrypt_opcode(opcode); // here i got valid opcode (unique identifier of packet)
        encdec->decrypt(data); // here i got decrypted contents
        //packet factory returns a std::unique_ptr of abstraction -
        //  "class received_packet" which contains serialized contents
        auto packet = packet_factory->construct(opcode, std::forward<std::string>(data));
        if (packet == nullptr)
        {
            //when opcode is not associated with any implementation of received_packet
            logger::get_logger().log_error(login_ + " sent unknown packet");
            observer.request_close();
            return;
        }
        try
        {
            //serialize the string with binary data into clean form
            //eventually throw std::underflow_error if not enough data was passed
            packet->parse_packet(); 
            //below is the a problem. I somehow need to get response if the packet
            //was a login request, or it was registration request,
            //or maybe something else - and every packet is handled in different way.
            //for example, login should update the "credentials" member of current client.
            //But the execute is virtual member of "received_packet" and so its an abstraction again
            //of course i can implement it in different ways but i got no idea how to pass it back to client
            //i need some mechanism that would take the "received_packet" and knew how to operate on "client"
            //so client could for example send back a packet with response to activity 
            //associated with concrete "received_packet"
            packet->execute();
        }
        catch (std::underflow_error& e)
        {
            logger::get_logger().log_error(login_ + ' ' + e.what());
            observer.request_close();
        }
    }
    );
}

Thats the abstract base

#pragma once
#include <string>
#include "ByteSerializer.hpp"

class received_packet
{
public:

    virtual void parse_packet() = 0;
    virtual void execute() = 0; // execute should either know what to do or return something
    virtual ~received_packet() = default;

protected:

    received_packet(const std::string& rawData)
        : bs(rawData, 0) {}

    byte_serializer bs; // this is my own helper class (irrelevant)
};

And 2 example implementations.

#pragma once
#include "Packet.hpp"

class login_pass_packet : public received_packet
{
public:
    login_pass_packet(const std::string& rawData)
        :received_packet(rawData) {}

    virtual void parse_packet() override;
    virtual void execute() override;

private:

    char login[20];
    char pass[20];
};

void login_pass_packet::parse_packet()
{
    bs >> login >> pass;
}

void login_pass_packet::execute()
{
    //How can i let "client" know that its a login packet? Remember - execute is virtual    
}

And it this case i expect different kind of reponse

#pragma once
#include "Packet.hpp"

class key_register_packet : public received_packet
{
public:

    key_register_packet(const std::string& rawData)
        :received_packet(rawData) {}

    virtual void parse_packet() override;
    virtual void execute() override;

private:

    std::string key;
};

void key_register_packet::parse_packet()
{
    bs >> key;
}

void key_register_packet::execute()
{
    //and here, execute have to return key register request to client
}

So in the end - i am stuck handling the abstraction. After having concrete packet like login or key request i still see it is as received_packet. Could someone point me in right direction?

Aucun commentaire:

Enregistrer un commentaire