In this section we'll present creation of the simple Information Server and two Clients - one that uses direct network programming, using TQServerSocket and TQSocket and the other one that uses TQNetworkProtocol subclass.
For that purpose we developed a very simple communication protocol between sever and clients.
Data is stored at server in hierarchical structure (similar to file systems) with folder (directory) and data (file) nodes. Our protocol uses only two directives - \c LIST and \c GET.
Server accepts only those two commands and returns one or more lines of the following format:
"xxxM line_content"
xxx represents the return message code, If character M (More) is '+' that means that it's not the last line of the response, and if M is ' ' (space) than it is the last line.
LIST directory_node_path
Response: "212+T child_address"
It lists all children of given directory node. It returns a line for every child of the directory. T stands for Type and can be 'D' for directories or 'F' for file nodes. Last line of response will always be "212 ".
GET data_node_path
Response: "213+ one_data_line"
GET returns specified file line by line. Last line is always "213 ".
There are two more responses:
"500 File not found" - path points to non-existent node.
"550 Syntax error" - error in command parsing.
\section2 Info Server
First, we'll write a simple server that keeps information data in a tree structure and supports described communication protocol.
Data and supported operations handles \e InfoData class. We will present here just the public interface of that class:
\caption From \l network/infoprotocol/infoserver/infodata.h
\c InfoData::list lists all node children, while \c InfoData::get gets the data file. You can see this class implementation in file \l network/infoprotocol/infoserver/infodata.cpp. For this example, we just hard coded description of one small office network.
The main idea follows: Server creates the SimpleServer object (TQServerSocket subclass) which listens on the specified port and, when receives connection request from a client, creates ClientSocket object (TQSocket subclass) to handle that connection. ClientSocket recognizes two mentioned operations (list and get), performs them on InfoData object and returns generated response back to the client.
In the SimpleServer base class TQServerSocket constructor tries to bind itself to the given port. We use TQServerSocket::ok() to find out if that was successful and to detect eventual problems. After that it will monitor that port an call TQServerSocket::newConnection() every time a succesful connection with client is made.
This is reimplemented pure virtual function TQServerSocket::newConnection(). It creates ClientSocket which will be responsible for just established incoming connection with \e socket as the file descriptor.
ClientSocket constructor connects TQSocket::readyRead() signal which is emitted when something arrives from the Client. We also want to know when connection is terminated. When SimpleServer creates ClientSocket, connection is already established, so we just need to specify used socket with TQSocket::setSocket().
Our communication protocol is textual and line oriented, and socket communication is asynchronous (don't forget that, we don't know when readyRead() will be emitted, or will that be at the end of the line), so we have to check with TQSocket::canReadLine() if the full line has been received. Because each input line presents one command in this protocol (list or get) we will process it and return generated answer through the socket back to the Client.
Function processCommand() parses the input line and if it recognizes LIST or GET command, calls corresponding (InfoData*)info methods - list and get, otherwise creates appropriate error message.
Now, lets see the example of a Client that will use this Server, implemented through direct socket programming with TQSocket (\l network/infoprotocol/infoclient/client.cpp)
Because user connects to the server by request (btnConnect), connectToServer() dynamically creates new TQSocket which will carry out the connection with the server. Signal TQSocket::connected() is emitted after connection is successfully performed, and TQSocket::error(int) is emitted with error code on any error. Because communication is asynchronous, we can't check if TQSocket::connectToHost() succeeded or not. That's why we must rely on signals.
Here we parse responses from the server. It's worth noting again that the communication is asynchronous and it must not be assumed when and how the data will come. The fact that server socket sends all data lines at once (in one loop) does not mean that client socket will receive them as one package and emit one readyRead() signal. That is why we designed our protocol to have termination line , with M = ' ' (e.g. "213 "). In this example the line code will determine its destination (infoList or infoText), but in more advanced usage client would probably require some sort of the finite state machine, as we'll se in the next example.
It is not mandatory, of course, but it's good programming practice to cover errors and termination of the connection. Also, the best way to detect if the connecting to host succeeded (using connectToHost()) is use of an error(int) signal.
It is time to illustrate how to use TQNetworkProtocol, TQNetworkOperation and TQUrlOperator to register our communication protocol and make it on par to already implemented protocols, like TQFtp, TQHttp and TQLocalFs. This will give us much larger flexibility in use and let us use TQt class that supports Network Protocols, e.g. TQFileDialog.
TQNetworkProtocol is the base class for every Network Protocol class. Because this protocol uses network, we embedded one TQSocket* member variable to which we'll delegate network communication. Protocols that doesn't require to use network will do it on their own way - e.g. TQLocalFs uses TQDir, some data acquisition protocol may use serial or USB connection, only requirement is that protocol uses hierarchical structure and can be accessed using URLs (to have addressable nodes).
We have to announce which of the supported operations our protocol supports. For the complete list of available operations, see TQNetworkProtocol::Operation.
In this function protocol checks if the connection is established, and if not, tries to do so. Again, because of the asynchronous nature of the TQSocket, we don't know when and how connectToHost() will be finished, so we need to test if socket are still trying to make a connection.
Here we implement two supported operations. TQUrlOperator is class that initiated our protocol at first (after spotting that url starts with "qip://"), and can be approached with url() function. We'll use it to find path to our node. E.g. if url was "qip://my_server/network/fax/", path() would return "/network/fax/", while host() would be "my_server". For each operation, TQNetworkOperation object is created to hold its state and description. We will mark here that current operation started. See all operation states at TQNetworkProtocol::State.
Implementation is very similar to previous example in addition that there are now some signal emitting requirements, so we had to use simple state machine here. In list operation we have to emit start(TQNetworkOperation*) before first child, and then to emit TQNetworkProtocol::newChild (const TQUrlInfo&, TQNetworkOperation*) for each child listed from the server. For get operation, we should emit TQNetworkProtocol::data (const TQByteArray&, TQNetworkOperation*) for each data chunk received (in this case, one text line). It is very important that \e every operation finishes with TQNetworkProtocol::finished(TQNetworkOperation*) signal!
In the case of an error we set error state, code and message, and, important enough and deserves to be mention again, emit finished(TQNetworkOperation*) signal.
Now, when we have our TQNetworkProtocol Qip implemented, Info Url Client will be much simpler than in previous TQSocket example.
But, before we can use our new protocol, we have to register it first, so TQUrlOperators can react on it. We have done it in the main.cpp (\l network/infoprotocol/infourlclient/main.cpp):
This registers Qip protocol and bonds it to prefix "qip". You can use tqInitNetworkProtocols() which registers pre coded Ftp (for "ftp") and Http ("http") protocols. Local file system (TQLocalFs) is always registered.
Qip protocol will send us data lines, our is just to pick them and process. Note that here we don't use finished() signal which we'd like to use if we want to know when the full file is received. In that case, in appropriate slot, we would like to check if it's ( operationInProgress()->operation() == TQNetworkProtocol::OpGet ).
Here is where we use some TQNetworkProtocol magic. (TQUrlOperator)op is constructed from a selected url \e file, and then we just use TQUrlOperator::get() to fetch its content.
This function implements simple TQFileDialog that will serve us to browse through the nodes on the server and to select one data node to view. Starting url is "qip://localhost/" which indicates to TQFileDialog that we want to use Qip protocol served on the local server. We could also specify the exact port, e.g. "qip://my_server:123" will try to inquire my_server over port 123, otherwise the default port is used.
We didn't use static function TQFileDialog::getOpenFileName() because under Windows and Mac OS X, it will usually use the native file dialog and not a TQFileDialog, in which case we wouldn't be able to use our protocol at all.