Hi everybody! This blog is about the Cute server. Cute is a high-performance, Linux-based server that extends Qt's signals and slots to network communication. There are also client SDKs for Linux, Windows, macOS, iOS, Android, and WebAssembly. They enable seamless integration between clients and servers, and users can download them alongside all Cute server editions on cuteserver.io.
The signals and slots mechanism is a central feature of Qt. It allows communication between objects that know nothing about each other in an easy-to-use and intuitive way. But the main strength of the signals and slots mechanism lies in its ability to represent asynchronous, event-driven programming transparently.
Cute server enables communication between objects over a network using the signals and slots mechanism.
Cute's main strength is empowering clients to use the signals and slots mechanism to write network apps as if all objects were local. In fact, by relying heavily upon Qt's signals and slots and meta-type system, Cute can abstract away from clients that they are interacting with an object that lives on the server.
The objects created by the Cute server are called remote objects. These objects are instances of QObject-derived classes containing specially tagged signals and slots. Developers use a macro to register remote object classes to the Cute server. The Cute server fetches all registered remote object classes from a shared library that it loads on startup.
Clients interact with remote objects using the RemoteObject class. This class acts as a proxy to the remote object instantiated by the Cute server. It enables clients to interact with remote signals and slots as if they were local, hiding from them that they are interacting with objects on the Cute server.
With Cute, signals and slots play a unifying role in client-side and server-side programming. Server-side programming becomes just a matter of applying a couple of macros to QObject-derived classes to tag their signals and slots and register them to the Cute server. Client-side programming does not change. Remote objects are used by clients as if they were local. The asynchronous nature of network programming is gracefully abstracted away by the signals and slots mechanism.
If you know how to use signals and slots, you can start coding network-connected apps. Let's look at an example:
// Calculator is a QObject-derived class
// with tagged signals and slots.
class Calculator : public QObject
{
Q_OBJECT
public:
// Constructor receives connection information
Calculator(QSharedPointer<Cute::IConnectionInformation> connInfo)
{
Q_UNUSED(connInfo);
}
public slots:
// Remote slots are tagged with the REMOTE_SLOT macro
REMOTE_SLOT qint32 addIntegers(qint32 a, qint32 b)
{
const auto sum = a+b;
emit newSum(sum);
return sum;
}
signals:
// Remote signals are tagged with the REMOTE_SIGNAL macro
REMOTE_SIGNAL void newSum(qint32 sum);
};
And in a source file:
// Classes are registered to Cute server with a macro,
// by mapping them to an endpoint.
REGISTER_REMOTE_OBJECT("/calculator", Calculator);
The Calculator class is now available at the /calculator
endpoint. Whenever a client instantiates a RemoteObject with this endpoint, the Cute server creates an instance of the Calculator class. All remote signals and slots declared in the Calculator class are available to clients, which can use them to establish remote signal-slot connections. Clients can also call remote slots directly. For example, suppose the Cute server is listening on port 2088 on an IPV4 IP that is the A record value of example.com. In that case, clients can create an instance of the Calculator class on the Cute server as follows:
// Cute server creates remote objects that clients can interact
// with through the RemoteObject class, that acts as a proxy
// to the remote object instantiated by Cute server.
RemoteObject calculator("Calculator", QUrl("cute://example.com:2088/calculator");
Now, suppose the Cute server is listening on the IPV4 localhost. In that case, clients can create a Calculator instance on the Cute server as shown below:
RemoteObject calculator("Calculator", QUrl("cute://127.0.0.1:2088/calculator");
Unencrypted connections use the cute
URL scheme, while encrypted connections require the cutes
scheme. The Cute server supports both IPV4 and IPV6.
The calculator
object in the client code acts as a proxy to the actual instance on the Cute server. Clients interact with remote objects through remote signal and slot connection and direct remote slot calls. For example, if the MyClass class is declared on the client-side as follows:
// MyClass is a class on client-side
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass() = default;
~MyClass() override = default;
public slots:
void onNewSum(qint32 sum);
signals:
void newAddIntegersRequest(qint32 a, qint32 b);
};
Then clients can establish remote signal and slot connections as shown below:
RemoteObject calculator("Calculator", QUrl("cute://127.0.0.1:2088/calculator");
MyClass myObject;
// Connecting local signal to remote slot
RemoteObject::connect(&myObject, SIGNAL(newAddIntegersRequest(qint32,qint32)),
&calculator, SLOT(addIntegers(qint32,qint32)));
// Connecting remote signal to local slot
RemoteObject::connect(&calculator, SIGNAL(newSum(qint32)),
&myObject, SLOT(onNewSum(qint32)));
Whenever myObject
emits the newAddIntegersRequest
signal on the client-side, the Cute server calls the addIntegers
slot on the remote object represented by the calculator instance. When the remote object on the Cute server represented by the calculator instance emits the newSum
signal, the Cute's client SDK calls the onNewSum
slot on the local myObject
instance.
Remote signals and slots can also have custom types as arguments.
Clients can also call remote objects directly. For example, clients can call the addIntegers
slot on the remote object represented by the calculator
instance as follows:
// Clients can call remote slots on remote objects
RemoteObject calculator("Calculator", QUrl("cute://127.0.0.1:2088/calculator");
qint32 a = 10;
qint32 b = 12;
auto response = calculator.callRemoteSlot("addIntegers", a, b);
QObject::connect(response.data(), &IRemoteSlotResponse::responded,
[](const QVariant &response)
{
qWarning("Remote slot responded. Sum is: %d.", response.value<qint32>());
});
The calculator example shown above is developed step-by-step in a tutorial.
All classes used to instantiate remote objects are mapped to an endpoint upon registration to the Cute server. All registered classes are compiled into a shared library that the Cute server loads at startup.
By relying on Qt's meta-type system, Cute can extend the signals and slots mechanism to network apps with a highly concise SDK that is very easy to learn. Server-side programming consists of employing a couple of macros on a QObject-derived class. In contrast, client-side programming relies on a single class (RemoteObject) and two helper classes that are responsible for informing the status of remote slot calls (IRemoteSlotResponse) and remote signal slot connections (IRemoteObjectConnection).
Server-side development involves applying a couple of macros on a QObject-derived class, while the RemoteObject class forms the basis of client-side programming.
On cuteserver.io, there is a simple step-by-step tutorial illustrating how easy it is to write code interacting on the network using signals and slots.
Cute server Business and Enterprise versions are also capable of handling HTTP requests. Like remote slots, HTTP request handlers are slots tagged according to the HTTP actions (delete, post, get, put, patch) they are allowed to process and must have a standard signature, as shown below:
// The slot below is responsible for processing
// HTTP GET and POST requests
public slots:
HTTP_POST HTTP_GET void httpPostAndGetHandler(QSharedPointer<Cute::Server::HttpBroker> broker);
Suppose that the slot shown above belongs to a class mapped to the /my_class
endpoint. Then, whenever the Cute server parses HTTP POST and GET requests targeting the /my_class/httpPostAndGetHandler
endpoint, it creates an instance of the class mapped to the /my_class
endpoint and calls its httpPostAndGetHandler
slot.
The broker argument is used instead of passing references to HttpRequest
and HttpResponse
as arguments to the HTTP handler to allow the object handling the HTTP request to send the HTTP response after it returns from the handler slot. In this scenario, an HTTP handler can, for example, make a call to a database and return, being the HTTP response sent after the instance handling the HTTP request receives the response from the database.
The Cute server has three versions: Startup, Business, and Enterprise, which users can evaluate for 30 days on AWS. Cute also provides SDKs targeting Linux, Windows, macOS, iOS, and Android, and all of them are available for download on cuteserver.io.
Besides being easy to use by extending the well-known Qt's signals and slots mechanism to network apps, Cute is also a highly performant server. By relying on epoll-based event handlers, the Business and Enterprise editions can handle 1 million connections on an 18vcpu AWS instance while processing 129k remote slot calls per second. All messages exchanged with the Cute server have their integrity checked using hash-based message authentication codes.
Our next blog post showcases a load test where Cute server Enterprise, which users can evaluate on AWS (EC2/Lightsail) for 30 days, is put under a load test. The load test establishes 1 million connections to remote objects. Then 10 million calls to remote slots are made afterward. Load test source code is also available on GitHub. Running the load test on your own AWS infrastructure and tweaking the test code/Linux configuration to deal with high network loads is a great way to learn. Don't miss that opportunity! It will extend your knowledge to network apps with little effort.