I recently had a need to create a command processor in Qt, using C++ only, on a resource-limited system. I decided to use JSON rather than implement a fully-custom protocol.
Rather than pulling a 3rd party library into my Qt application, I ran across a handy overview from the QT Wiki demonstrating how to use QScriptEngine to do the parsing and validation. Unfortunately, QScriptEngine does not support serialization of data structures to JSON, although generating JSON using QString is pretty trivial.
The Ruby server implementation is very trivial using the JSON gem:
def initialize
@server = TCPServer.new 19024
end
def start
@server_thread = Thread.new do
@client_thread = Thread.start(@server.accept) do |client|
puts "server: Accepted client connection!"
send_request(client, 'greeting', :message => 'Hello client!')
receive_response(client)
send_request(client, 'shutdown')
receive_response(client)
client.close
end
end
end
...
def send_request(client, command, args={})
data = {'request' => {'command' => command}}
data['request'].merge!(args)
request = JSON.generate(data)
client.print request
if args[:message]
puts "server: >> #{args[:message]}"
end
end
def receive_response(client)
response = client.recv(1024)
data = JSON.parse(response)
if data['response'] && data['response']['message']
puts "server: << #{data['response']['message']}"
end
return data
end
The Qt client uses QTcpSocket to connect to the server and is fully event-driven due to the excellent signals/slots mechanism that Qt brings to C++. I have wired up a few signals from the QTcpSocket to local slots in the Client class to demonstrate a few of the event notifications that QTcpSocket provides. Although the key signal for receipt of JSON data is readyRead(), which is fired whenever the socket receives data.
The constructor does the wiring up of readyRead() to my ProcessRequest() handler:
tcpSocket = new QTcpSocket(this);
...
Q_ASSERT(connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(ProcessRequest())));
ProcessRequest() uses QScriptEngine to parse the JSON packet, which returns a QScriptValue object for the root node of the data structure:
void Client::ProcessRequest(void)
{
QByteArray request = tcpSocket->readAll();
QScriptEngine engine;
// Validate that the request was parsed and non-empty
QScriptValue data = engine.evaluate("(" + QString(request) + ")");
...
}
QScriptValue::property() allows access to the key/value pairs and also has some nice helper methods for validation of the schema:
QString Client::ProcessCommand(QScriptValue req)
{
QScriptValue cmd = req.property("command");
QString response;
// Basic sanity check
if (!cmd.isValid())
{
response = CommandErrorResponse("invalid", "No command specified");
}
// Process greeting
else if (cmd.toString() == "greeting")
{
QScriptValue message = req.property("message");
if (!message.isValid())
{
response = CommandErrorResponse("greeting", "No message specified");
}
else
{
response = CommandResponse("greeting", "Hello server. Hope things are well!");
}
}
...
}
The full project can be found on GitHub at: http://github.com/barneywilliams/JSONSocket/. It includes a README that gives details on building and running the example.