Introduction
You can find the original German version of this tutorial written by forum member anda_skoa here.
This tutorial gives an example of the use of eventloop based Qt socket classes.
Eventloop based means that one can deal with non-blocking input/output without threads.
It is important to pay attention to the fact that when you work with eventloop, you can not block any function called from eventloop. In the case of QSocket it means that slots connected to QSocket signals must be used only for short tasks.
Besides, it means that the methods of QSocket class terminate usually immediately and the task is delayed, therefore the result of the task can be known only on the ground of signals.
Production
The demo project is supplied with qmake project data. In order to compile the server and the client it is nessesary to set QMAKESPEC and QTDIR propely. Then the production goes easely:
|
Client
The client has simple GUI made with Designer. The first interesting code snippet is located in the constructor of ClientMainWindow class:
|
As stated above, the result of work of a QSocket method can be got through a signal. For example, if QSocket::connectToHost establishes connection successfully, then the socket emits connected() signal. If the attempt of connection failed, error(int) is sent so that the integer parameter will give us the hint for finding the reason of the failure (see ClientMainWindow::slotError method connected for that purpose).
It is very easy to set up a server connection:
|
The first parameter is the hosts IP address or hostname transformed into IP address by means of QDns.
Please note that after the call the eventloop is not blocked, thus we have in that call thereafter e.g. an infinite loop.
QSocket is inherited from QIODevice and therefore can be used for example as the destination of a stream:
|
In this case the endline is important because the protocol of this simple example in based on the assumption that a transmission is always ended with newline, in other words, consists of plain lines.
In general, if arbitrary data is transferred, it is always better to transfer only little packets and to store the rest in a buffer.
Using bytesWritten signals one can track when all the data of a whole current packet were transferred and when it is time to send the next one.
To read from the socket it is possible either again using QIODevice functionality or as in the example with the method of the QSocket class:
|
Since we know that the trailing newline comes, we begin to do something if the a whole line can be read.
In general, it is checked with QSocket::bytesAvialable the amount of the data received and then this amount is read in a buffer.
Server
The server has also simple GUI.
The ServerSocket class derived from QServerSocket performs the main part of the server functionality - the establishment of client connections.
The abstract method (pure virtual) newConnection(int) is implemented for that purpose:
|
We create a new QSocket instance and let it work with this file descriptor.
In order to use this new connection we have to let our server know that a new connection was already established and it is available now. This is done with a self-explanatory newClient(QSocket*) signal. The new socket as the parameter is passed to newClient().
The first part of servermainwindow.cpp file is the client class:
|
The number is a sequential number used for the sake of simplicity here to name a client.
The class is defined in CPP file since it has importance only for the server class.
In the constructor of the ServerMainWindow class we use a feature of the Qt container:
|
We will use this later in order to delete all the client instances with simple clear() call.
Our server behaves like a simple chatserver. It sends the lines of text to all clients continually.
The sending process take place in the same manner as in case of client with a QTextStream, which treats connection sockets as QIODevice.
|
The entries in the dictionary are therefore client instances and therefore they have the socket() method returning the connection socket.
In order to be able to accept all connections we must create a ServerSocket and connect it to newClient:
|
If it failed, we delete useless SocketServer instant and terminate the method. Otherwise we connect newClient signal with a slot which processes the new connection.
This method looks like the following:
|
We connect QSocket signals with corresponding slots (as done in the client code) in order to manage incoming data and disconnection.
If a client terminates the connection, the socket emits connectionClosed signal. The slot is called and the following code is run:
|
We implement that with QObject::sender(). At this point it is necessary to pay attention that this slot may not be called as a method because in this case the value returned by the method is undefined!
The take() removes the entry from the dictionary without performing the automatic deletion which we have permitted with AutoDelete. If we need not the client instance any longer, we can use remove instead of take and delete.
We process incoming data similarly as in client code:
|
Then we read all available lines and send them to all the clients including the current client.
At the end we update still the view of the server GUI.
If the server is to stop its service, we must turn off all the clients:
|
Translated from German by Alexander Atamas