We're hiring!

We're actively seeking developers and designers for our Detroit & Ann Arbor locations.

Conversing JSON with Qt and Ruby

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 this handy overview 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.

 

This entry was posted in Embedded Systems and tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.