Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions | ![]() |
This module is part of the Qt Enterprise Edition.
Qt's networking classes make it straightforward to write cross-platform networking applications.
The networking classes can be divided into three groups:
QUrlOperator, QNetworkProtocol (and its QFtp and QHttp subclasses), along with QNetworkOperator, provide a simple high level API for performing network operations in a network- and protocol-transparent manner. For example, using QUrlOperator, you can fetch a file across the network, with just one line of code.
The QSocket and QServerSocket classes provide an easy-to-use API for client and server socket programming.
These classes require an event loop and must be used within an application's GUI thread. They are ideal for end-user applications that need network access, but they are not suitable for console applications or for high performance daemons (services).
The QSocketDevice and QDns classes provide low level networking access. QSocketDevice can be used for UDP as well as TCP/IP, and can be used in the non-GUI threads of a multi-threaded application. QDns performs simple hostname lookups, and also more specialised lookups, such as Mx records, name Ptr records, etc.
These classes are suitable for all types of applications, from GUI end-user applications to console applications and high performance daemons (services).
The class documentation for each class presents the functionality and usage of the class. In this document we will explore typical uses of Qt's networking classes, showing how they are used in practice.
If you are using standard network protocols, such as FTP and HTTP, you must register them before you can use QUrlOperator. For example, put the following call in your main() function, after creating your QApplication object:
qInitNetworkProtocols();
Copying files is simple:
QUrlOperator url; url.copy( QString("ftp://ftp.trolltech.com/qt/source/qt-3.0.0.tar.gz"), "file:/tmp" );The first (source) URL is copied to the second (destination) URL. The copy() function can be used to move as well as copy.
QUrlOperator also provides listChildren() to obtain a list of the files in a given directory, mkdir(), to create a directory, as well as rename() and remove().
QUrlOperator url; url.get( "http://www.somedomain.com/cgi-bin/lookup.pl?name=Buzz" );The get() function is used to get data, in this example from a query issued to a web site. There is also a put() function for writing data to a remote file.
All operations are performed asynchronously. To find out the results of an operation connect to the QUrlOperator::finished() signal and examine the QNetworkOperation object passed to it.
The QUrlOperator class also provides other functions and many more signals to make it easy to track the progress of an operation. These signals could be used, for example, to provide feedback on progress to the user.
This example class is used as follows:
FetchFiles ff( "ftp://ftp.trolltech.com" ); connect( &myWidget, stop(), &ff, stop() ); connect( &ff, start(), &myWidget, start() ); connect( &ff, startFile(), &myWidget, startFile() ); connect( &ff, finishedFile(), &myWidget, finishedFile() ); connect( &ff, finished(), &myWidget, finished() ); connect( &ff, error(), &myWidget, error() ); ff.fetch();The FetchFiles constructor takes a URL. When fetch() is called, the FetchFiles object will iterate over every file it finds at the given URL and download each one that is a regular file, (i.e. ignoring directories). The FetchFiles object will emit signals as follows:
By connecting the signals to a widget you can keep the user notified regarding progress.
The object also has a stop() slot, which you can connect to. This is useful if you want the user to be able to cancel the operation before it is complete.
### Rainer: is this a good example? If it is, I'll try to write the code.
In this section we'll present creation of the simple Information Server and two Clients - one that uses direct network programming, using QServerSocket and QSocket and the other one that uses QNetworkProtocol 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 - LIST and 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.
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 InfoData class. We will present here just the public interface of that class:
class InfoData { public: InfoData(); QStringList list( QString path, bool *found ) const; QString get( QString path, bool *found ) const; private: QDict< QStringList > nodes; QDict< QString > data; };
From network/infoprotocol/infoserver/infodata.h
InfoData::list lists all node children, while InfoData::get gets the data file. You can see this class implementation in file 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 (QServerSocket subclass) which listens on the specified port and, when receives connection request from a client, creates ClientSocket object (QSocket 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.
Let us look at Server implementation (network/infoprotocol/infoserver/server.cpp):
SimpleServer::SimpleServer( Q_UINT16 port, QObject* parent, const char *name ) : QServerSocket( port, 1, parent, name ) { if ( !ok() ) { QMessageBox::critical( 0, tr( "Error" ), tr( "Failed to bind to port %1" ).arg( port ) ); exit(1); } }
In the SimpleServer base class QServerSocket constructor tries to bind itself to the given port. We use QServerSocket::ok() to find out if that was successful and to detect eventual problems. After that it will monitor that port an call QServerSocket::newConnection() every time a succesful connection with client is made.
void SimpleServer::newConnection( int socket ) { (void)new ClientSocket( socket, &info, this, "client socket" ); emit newConnect(); }
This is reimplemented pure virtual function QServerSocket::newConnection(). It creates ClientSocket which will be responsible for just established incoming connection with socket as the file descriptor.
ClientSocket::ClientSocket( int sock, InfoData *i, QObject *parent, const char *name ) : QSocket( parent, name ), info( i ) { connect( this, SIGNAL(readyRead()), SLOT(readClient()) ); connect( this, SIGNAL(connectionClosed()), SLOT(connectionClosed()) ); setSocket( sock ); }
ClientSocket constructor connects QSocket::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 QSocket::setSocket().
void ClientSocket::readClient() { QTextStream stream( this ); QStringList answer; while ( canReadLine() ) { stream << processCommand( stream.readLine() ); } }
This slot is called every time we receive some data via socket. 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 QSocket::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. QSocket is a subclass of QIODevice, thus we can use QTextStream to read and write lines to it.
void ClientSocket::connectionClosed() { delete this; }
When connection closes, ClientSocket is not needed anymore, so it deletes itself. New one will be created upon new connection.
Now, lets see the example of a Client that will use this Server, implemented through direct socket programming with QSocket (network/infoprotocol/infoclient/client.cpp)
void ClientInfo::connectToServer() { delete socket; socket = new QSocket( this ); connect( socket, SIGNAL(connected()), SLOT(socketConnected()) ); connect( socket, SIGNAL(connectionClosed()), SLOT(socketConnectionClosed()) ); connect( socket, SIGNAL(readyRead()), SLOT(socketReadyRead()) ); connect( socket, SIGNAL(error(int)), SLOT(socketError(int)) ); socket->connectToHost( edHost->text(), edPort->text().toInt() ); }
Because user connects to the server by request (btnConnect), connectToServer() dynamically creates new QSocket which will carry out the connection with the server. Signal QSocket::connected() is emitted after connection is successfully performed, and QSocket::error(int) is emitted with error code on any error. Because communication is asynchronous, we can't check if QSocket::connectToHost() succeeded or not. That's why we must rely on signals.
void ClientInfo::sendToServer( Operation op, const QString& location ) { QString line; switch (op) { case List: infoList->clear(); line = "LIST " + location; break; case Get: line = "GET " + location; break; } infoText->clear(); QTextStream os(socket); os << line << "\r\n"; }
This function sends a command to the server. Notice that is uses QTextStream to send data via socket.
void ClientInfo::socketReadyRead() { QTextStream stream( socket ); QString line; while ( socket->canReadLine() ) { line = stream.readLine(); if ( line.startsWith( "500" ) || line.startsWith( "550" ) ) { infoText->append( tr( "error: " ) + line.mid( 4 ) ); } else if ( line.startsWith( "212+" ) ) { infoList->insertItem( line.mid( 6 ) + QString( ( line[ 4 ] == 'D' ) ? "/" : "" ) ); } else if ( line.startsWith( "213+" ) ) { infoText->append( line.mid( 4 ) ); } } }
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.
void ClientInfo::socketConnectionClosed() { infoText->clear(); infoText->append( tr( "error: Connection closed by the server\n" ) ); } void ClientInfo::socketError( int code ) { infoText->clear(); infoText->append( tr( "error: Error number %1 occurred\n" ).arg( code ) ); }
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 QNetworkProtocol, QNetworkOperation and QUrlOperator to register our communication protocol and make it on par to already implemented protocols, like QFtp, QHttp and QLocalFs. This will give us much larger flexibility in use and let us use Qt class that supports Network Protocols, e.g. QFileDialog.
First, here is the header file in which Qip (our custom Network Protocol) is declared (network/infoprotocol/infourlclient/qip.h):
class Qip : public QNetworkProtocol { Q_OBJECT public: Qip(); virtual int supportedOperations() const; protected: virtual void operationListChildren( QNetworkOperation *op ); virtual void operationGet( QNetworkOperation *op ); virtual bool checkConnection( QNetworkOperation *op ); private slots: void socketConnected(); void socketReadyRead(); void socketConnectionClosed(); void socketError( int code ); private: QSocket *socket; enum State { Start, List, Data } state; void error( int code, const QString& msg ); };
QNetworkProtocol is the base class for every Network Protocol class. Because this protocol uses network, we embedded one QSocket* 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. QLocalFs uses QDir, 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).
Let us go to the Qip implementation (network/infoprotocol/infourlclient/qip.cpp):
Qip::Qip() { state = Start; socket = new QSocket( this ); connect( socket, SIGNAL(connected()), SLOT(socketConnected()) ); connect( socket, SIGNAL(connectionClosed()), SLOT(socketConnectionClosed()) ); connect( socket, SIGNAL(readyRead()), SLOT(socketReadyRead()) ); connect( socket, SIGNAL(error(int)), SLOT(socketError(int)) ); }
Because we use QSocket to perform network communication for us we have to initialize socket in the similar way we did in previous example.
int Qip::supportedOperations() const { return OpListChildren | OpGet; }
We have to announce which of the supported operations our protocol supports. For the complete list of available operations, see QNetworkProtocol::Operation.
bool Qip::checkConnection( QNetworkOperation * ) { if ( socket->isOpen() ) return TRUE; // don't call connectToHost() if we are already trying to connect if ( socket->state() == QSocket::Connecting ) return FALSE; socket->connectToHost( url()->host(), url()->port() != -1 ? url()->port() : infoPort ); return FALSE; }
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 QSocket, 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.
void Qip::operationListChildren( QNetworkOperation * ) { QTextStream os(socket); os << "LIST " + url()->path() + "\r\n"; operationInProgress()->setState( StInProgress ); } void Qip::operationGet( QNetworkOperation * ) { QTextStream os(socket); os << "GET " + url()->path() + "\r\n"; operationInProgress()->setState( StInProgress ); }
Here we implement two supported operations. QUrlOperator 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, QNetworkOperation object is created to hold its state and description. We will mark here that current operation started. See all operation states at QNetworkProtocol::State.
void Qip::socketReadyRead() { // read from the server QTextStream stream( socket ); QString line; while ( socket->canReadLine() ) { line = stream.readLine(); if ( line.startsWith( "500" ) ) { error( ErrValid, line.mid( 4 ) ); } else if ( line.startsWith( "550" ) ) { error( ErrFileNotExisting, line.mid( 4 ) ); } else if ( line.startsWith( "212+" ) ) { if ( state != List ) { state = List; emit start( operationInProgress() ); } QUrlInfo inf; inf.setName( line.mid( 6 ) + QString( ( line[ 4 ] == 'D' ) ? "/" : "" ) ); inf.setDir( line[ 4 ] == 'D' ); inf.setSymLink( FALSE ); inf.setFile( line[ 4 ] == 'F' ); inf.setWritable( FALSE ); inf.setReadable( TRUE ); emit newChild( inf, operationInProgress() ); } else if ( line.startsWith( "213+" ) ) { state = Data; emit data( line.mid( 4 ).utf8(), operationInProgress() ); } if( line[3] == ' ' && state != Start) { state = Start; operationInProgress()->setState( StDone ); emit finished( operationInProgress() ); } } }
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(QNetworkOperation*) before first child, and then to emit QNetworkProtocol::newChild (const QUrlInfo&, QNetworkOperation*) for each child listed from the server. For get operation, we should emit QNetworkProtocol::data (const QByteArray&, QNetworkOperation*) for each data chunk received (in this case, one text line). It is very important that every operation finishes with QNetworkProtocol::finished(QNetworkOperation*) signal!
void Qip::error( int code, const QString& msg ) { if ( operationInProgress() ) { operationInProgress()->setState( StFailed ); operationInProgress()->setErrorCode( code ); operationInProgress()->setProtocolDetail( msg ); clearOperationQueue(); emit finished( operationInProgress() ); } state = Start; }
In the case of an error we set error state, code and message, and, important enough and deserves to be mention again, emit finished(QNetworkOperation*) signal.
Now, when we have our QNetworkProtocol Qip implemented, Info Url Client will be much simpler than in previous QSocket example. But, before we can use our new protocol, we have to register it first, so QUrlOperators can react on it. We have done it in the main.cpp (network/infoprotocol/infourlclient/main.cpp):
QNetworkProtocol::registerNetworkProtocol( "qip", new QNetworkProtocolFactory< Qip > );
This registers Qip protocol and bonds it to prefix "qip". You can use qInitNetworkProtocols() which registers pre coded Ftp (for "ftp") and Http ("http") protocols. Local file system (QLocalFs) is always registered.
Client implementation (network/infoprotocol/infourlclient/client.cpp):
ClientInfo::ClientInfo( QWidget *parent, const char *name ) : ClientInfoBase( parent, name ) { connect( btnOpen, SIGNAL(clicked()), SLOT(downloadFile()) ); connect( btnQuit, SIGNAL(clicked()), qApp, SLOT(quit()) ); connect( &op, SIGNAL( data( const QByteArray &, QNetworkOperation * ) ), this, SLOT( newData( const QByteArray & ) ) ); }
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() == QNetworkProtocol::OpGet ).
void ClientInfo::downloadFile() { // under Windows you must not use the native file dialog QString file = getOpenFileName(); if ( !file.isEmpty() ) { infoText->clear(); // download the data op = file; op.get(); } }
Here is where we use some QNetworkProtocol magic. (QUrlOperator)op is constructed from a selected url file, and then we just use QUrlOperator::get() to fetch its content.
QString ClientInfo::getOpenFileName() { static QString workingDirectory( "qip://localhost/" ); QFileDialog dlg( workingDirectory, QString::null, 0, 0, TRUE ); dlg.setCaption( QFileDialog::tr( "Open" ) ); dlg.setMode( QFileDialog::ExistingFile ); QString result; if ( dlg.exec() == QDialog::Accepted ) { result = dlg.selectedFile(); workingDirectory = dlg.url(); } return result; }
This function implements simple QFileDialog 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 QFileDialog 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 QFileDialog::getOpenFileName() because under Windows and Mac OS X, it will usually use the native file dialog and not a QFileDialog, in which case we wouldn't be able to use our protocol at all.
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
### Rainer: have we an example that I can use?
Copyright © 2002 Trolltech | Trademarks | Qt version 3.1.0-b1
|