Well I'll be happy to post the code (at least what matters as far as Boost.Asio needs). I'm implementing a server for an old visual avatar chat thing called Palace. The PDF I have of its protocol specifies a binary protocol where the server and client send and receive message packets from each other.
The format of these messages are as follows:
event type: uint32 or a four-byte ASCII string describing the opcode
length: uint32, Length of the message's body, minus the 12 bytes the header's made up of
refnum: sint32, usually used as a variable relative to the message type (if it was "assign ID to the client logging in", this field would be that ID number)
body: uint8 array[length] for the message body, if it has one
Below I'll post what code I have (the important stuff anyway) along with some added comments, #includes are implied here, so yes, I did include deque/vector/string etc
Code:
class Server final
{
public:
Server(boost::asio::io_service&, const tcp::endpoint&);
private:
void Listen();
void SendID(Connection*); // sends the client their assigned user ID
void ReadLogin(Connection*); // client will respond with login data message
void SendLoginReply(Connection*); // basically echoes client's login message for integrity
std::deque<Message> msg_queue;
std::vector<Connection*> users;
std::vector<Room> rooms;
tcp::acceptor listener;
tcp::socket socket;
// various metadata fields omitted due to irrelevancy
};
// represents a connection between server and client
class Connection final:
{
public:
Connection(tcp::socket&);
inline tcp::socket& Socket() const { return socket; }
// get/set method madness for misc fields omitted
void Disconnect();
private:
tcp::socket socket;
// other stuff omitted
};
// a chatroom, more or less
class Room final
{
public:
Room(std::uint16_t); // param is the room ID
void Join(Connection*);
void Leave(Connection*);
private:
// various metadata fields omitted
std::uint16_t id;
};
struct Message final
{
char *data;
std::uint32_t type;
std::uint32_t size;
std::int32_t refnum;
const char* SerialiseHeader() const; // inserts above 3 fields into a char array of 12 bytes for buffering
};
Server::Server(boost::asio::io_service &iosvc, const tcp::endpoint &ep):
listener(iosvc, ep),
socket(iosvc),
last_user_id(0)
{
Listen();
}
void Server::Listen()
{
listener.async_accept(socket,
[this](boost::system::error_code ec)->void
{
if (!ec)
{
auto c = new Connection(std::move(socket));
SendID(c);
users.push_back(c);
}
Listen();
});
}
void Server::SendID(Connection *c)
{
Message msg = { nullptr, MSG_TIYID, 0, ++last_user_id };
char *data = msg.SerialiseHeader();
boost::asio::async_write(c->Socket(), boost::asio::buffer(data, 12),
[c](boost::system::error_code ec)->void
{
if (ec)
c->Disconnect();
});
}
After Server::SendID(), ReadLogin() should be executed if the client sends its login info, but I am unsure how to chronologically do that. I was planning to have the server utilise async_read() inside a method with a switch case depending what the client sends, but am unsure how to go about that.