返回介绍

Network Module

发布于 2019-10-04 14:57:24 字数 19724 浏览 1022 评论 0 收藏 0

This module is part of the Qt Enterprise Edition.

  • Introduction
  • Working Network Protocol independent with QUrlOperator and QNetworkOperation
    • Implementing your own Network Protocol
    • Error Handling

Introduction

The network module offers classes to make network programming easier and portable. Basically there are three sets of classes, first very basic classes like QSocket, QServerSocket, QDns, etc. which allow to work in a portable way with TCP/IP sockets. In addition, there are classes like QNetworkProtocol, QNetworkOperation in the Qt base library, which provide an abstract layer for implementing network protocols and QUrlOperator which operates on such network protocols. Finally the third set of network classes are the passive ones, namely QUrl and QUrlInfo which do URL parsing and similar stuff.

The first set of classes (QSocket, QServerSocket, QDns, QFtp, etc.) are included in the "network" module of Qt.

The QSocket classes are not directly related to the QNetwork classes, but QSocket should and will be used for implementing network protocols, which are directly related to the QNetwork classes. E.g. the QFtp class (implementation of the FTP protocol) uses QSockets. But QSockets don't need to be used for protocol implementations, e.g. QLocalFs (which is an implementation of the local filesystem as network protocol) uses QDir and no QSocket. Using QNetworkProtocols you can implement everything which fits into a hierarchical structure and can be accessed using URLs. This could be e.g. a protocol which can read pictures from a digital camera using a serial connection.

Working Network Protocol independent with QUrlOperator and QNetworkOperation

To just use existing network protocol implementations and operate on URLs using them is quite easy. E.g. downloading a file from an FTP server to the local filesystem can be done with following code:

    QUrlOperator op;
    op.copy( "ftp://ftp.trolltech.com/qt/source/qt-2.1.0.tar.gz", "file:/tmp", FALSE );

And that's all! Of course an implementation of the FTP protocol has to be available and registered for doing that. More information on that later.

You can also do stuff like creating directories, removing files, renaming, etc. E.g. to create a folder on a private FTP account do

    QUrlOperator op( "ftp://username:password@host.domain.no/home/username" );
    op.mkdir( "New Directory" );

That's it again. To see all available operations, look at the QUrlOperator class documentation.

Now as everything works asynchronous, the function call for an operation returns before the operation has been processed. So you don't get a return value which tells you something about failure or success. The return value always is a pointer to a QNetworkOperation.

In this QNetworkOperation all information about the operation is stored. There is e.g. a method of QNetworkOperation which returns the state of this operation. Using that you can find out all the time in which state the operation currently is. Also you get the arguments you passed to the QUrlOperator method, the type of the operation and some more stuff from this QNetworkOperation object. For more details see the class documentation of QNetworkOperation.

Now, later you get signals emitted by the QUrlOperator, which inform you about the process of the operations. As you can call many methods which operate on a URL of one QUrlOperator, it queues up all these operations. So you can't know which operation the QUrlOperator just processes. Because of this you get in each signal as the last argument a pointer to the QNetworkOperation object which is just processed and from which this signal comes.

Some of these operations send a start() signal at the beginning (depending if it makes sense or not), then some of them send some signals during processing the operation, and all operations send a finished() signal after they are done. Now, finished could mean that the operation has been successfully finished or that it failed. To find that out you can use the QNetworkOperation pointer you got with the finished() signal. If QNetworkOperation::state() equals QNetworkProtocol::StDone the operation finished successful, if it is QNetworkProtocol::StFailed the operation failed.

Now, a slot which you connected to the QUrlOperator::finished( QNetworkOperation * ) signal could look like this


void MyClass::slotOperationFinished( QNetworkOperation *op )
{
    switch ( op->operation() ) {
    case QNetworkProtocol::OpMkDir: {
        if ( op->state() == QNetworkProtocol::StFailed )
            qDebug( "Couldn't create directory %s", op->arg( 0 ).latin1() );
        else
            qDebug( "Successfully created directory %s", op->arg( 0 ).latin1() );
    } break;
    // ... and so on
    }
}

As mentioned before, some operations send other signals too. Let's take the list children operation as an example (e.g. read the directory of a directory on a FTP server):


QUrlOperator op;

MyClass::MyClass() : QObject(), op( "ftp://ftp.trolltech.com" )
{
    connect( &op, SIGNAL( newChildren( const QValueList<QUrlInfo> &, QNetworkOperation * ) ),
             this, SLOT( slotInsertEntries( const QValueList<QUrlInfo> &, QNetworkOperation * ) ) );
    connect( &op, SIGNAL( start( QNetworkOperation * ) ),
             this, SLOT( slotStart( QNetworkOperation *) ) );
    connect( &op, SIGNAL( finished( QNetworkOperation * ) ),
             this, SLOT( slotFinished( QNetworkOperation *) ) );
}

void MyClass::slotInsertEntries( const QValueList<QUrlInfo> &info, QNetworkOperation * )
{
    QValueList<QUrlInfo>::ConstIterator it = info.begin();
    for ( ; it != info.end(); ++it ) {
        const QUrlInfo &inf = *it;
        qDebug( "Name: %s, Size: %d, Last Modified: %s",
            inf.name().latin1(), inf.size(), inf.lastModified().toString().latin1() );
    }
}

void MyClass::slotStart( QNetworkOperation * )
{
    qDebug( "Start reading '%s'", op.toString().latin1() );
}

void MyClass::slotFinished( QNetworkOperation *operation )
{
    if ( operation->operation() == QNetworkProtocol::OpListChildren ) {
        if ( operation->state() == QNetworkProtocol::StFailed )
            qDebug( "Couldn't read '%s'! Following error occurred: %s",
                op.toString().latin1(), operation->protocolDetail().latin1() );
        else
            qDebug( "Finished reading '%s'!", op.toString().latin1() );
    }
}

These examples explained now how to use the QUrlOperator and QNetworkOperations. The network extension will contain some good examples for this too.

Implementing your own Network Protocol

QNetworkProtocol provides a base class for implementations of network protocols and an architecture to a dynamic registration and unregistration of network protocols. If you use this architecture you also don't need to care about asynchronous programming, as the architecture hides this and does all the work for you.

Limitation: As it is quite hard to design a base class for network protocols which satisfies all network protocols, the architecture described here is designed to work with all kinds of hierarchical structures, like filesystems. So everything which can be interpreted as hierarchical structure and accessed via URLs, can be implemented as network protocol and easily used in Qt. This is not limited to filesystems only!

To implement a network protocol create a class derived from QNetworkProtocol.

Other classes will use this network protocol implementation to operate on it. So you should reimplement following protected members

    void QNetworkProtocol::operationListChildren( QNetworkOperation *op );
    void QNetworkProtocol::operationMkDir( QNetworkOperation *op );
    void QNetworkProtocol::operationRemove( QNetworkOperation *op );
    void QNetworkProtocol::operationRename( QNetworkOperation *op );
    void QNetworkProtocol::operationGet( QNetworkOperation *op );
    void QNetworkProtocol::operationPut( QNetworkOperation *op );

Some words about how to reimplement these methods: You always get a pointer to a QNetworkOperation as argument. This pointer holds all information about the operation in the current state. If you start processing such an operation, set the state to QNetworkProtocol::StInProgress. If you finished processing the operation, set the state to QNetworkProtocol::StDone if it was successful or QNetworkProtocol::StFailed if an error occurred. If an error occurred you have to set an error code (see QNetworkOperation::setErrorCode() ) and if you know some details (e.g. an error message) you can also set this message to the operation pointer (see QNetworkOperation::setProtocolDetail() ). Also you get all information (type, arguments, etc.) of the operation from this QNetworkOperation pointer. For details about which arguments you can get and set look at the class documentation of QNetworkOperation.

If you reimplement such an operation method, it's also very important to emit the correct signals at the correct time: In general always emit at the end of an operation (when you either successfully finished processing the operation or and error occurred) the finished() signal with the network operation as argument. The whole network architecture relies on correctly emitted finished() signals! So be careful with that! Then there are some more special signals which are specific to operations:

  • Emit in operationListChildren:
    • start() just before starting listing the children
    • newChildren() when new children are read
  • Emit in operationMkDir:
    • createdDirectory() after the directory has been created
    • newChild() (or newChildren()) after the directory has been created (as a new directory is a new child)
  • Emit in operationRemove:
    • removed() after the child has been removed
  • Emit in operationRename:
    • itemChanged() after the child has been renamed
  • Emit in operationGet:
    • data() each time new data has been read
    • dataTransferProgress() each time new data has been read to indicate how much of the data has been read now.
  • Emit in operationPut:
    • dataTransferProgress() each time data has been written to indicate how much of the data has been written. Although you know the whole data when this operation is called, it's suggested not to write the whole data at once, but to do it step by step to avoid blocking the GUI and also this way the progress can be made visible to the user.

And remember, always emit the finished() signal at the end!

For more details about the arguments of these signals take a look at the QNetworkProtocol class documentation.

Now, as argument in such a method you get the QNetworkOperation which you process. Here is a list which arguments of the QNetworkOperation you can get and which you have to set in which method:

(To get the URL on which you should work, use the QNetworkProtocol::url() method which returns the pointer to the URL operator. Using that you can get the path, host, name filter and everything else of the URL)

  • In operationListChildren:
    • Nothing.
  • In operationMkDir:
    • QNetworkOperation::arg( 0 ) contains the name of the directory which should be created
  • In operationRemove:
    • QNetworkOperation::arg( 0 ) contains the name of the file which should be removed. Normally this is a relative name. But it may be absolute too, so use QUrl( op->arg( 0 ) ).fileName() to get the filename.
  • In operationRename:
    • QNetworkOperation::arg( 0 ) contains the name of the file which should be renamed
    • QNetworkOperation::arg( 1 ) contains the name to which it should be renamed.
  • In operationGet:
    • QNetworkOperation::arg( 0 ) contains the full URL of the file which should be retrieved.
  • In operationPut:
    • QNetworkOperation::arg( 0 ) contains the full URL of the file in which the data should be stored.
    • QNetworkOperation::rawArg( 1 ) contains the data which should be stored in QNetworkOperation::arg( 0 )

So, to sum it up: If you reimplement such an operation method, you have to emit some special signals and always at the end a finished() signal, either on success or on failure. Also you have to change the state of the QNetworkOperation during processing it and can get and set arguments of the operation as well.

But it's unlikely that the network protocol you implement supports all these operations. So, just reimplement the operations, which are supported by the protocol. Additionally you have to specify which operations are supported then. This is done by reimplementing

    int QNetworkProtocol::supportedOperations() const;

In your implementation of this method return an int value which is constructed by or'ing together the correct values (supported operations) of the following enum (of QNetworkProtocol):

    enum Operation {
        OpListChildren = 1,
        OpMkDir = 2,
        OpRemove = 4,
        OpRename = 8,
        OpGet = 32,
        OpPut = 64
    };

So, if your protocol e.g. supports listing children and renaming them, do in your implementation of supportedOperations():

    return OpListChildren | OpRename;

The last method you have to reimplement is

    bool QNetworkProtocol::checkConnection( QNetworkOperation *op );

Here you have to return TRUE, if the connection is up and ok (this means operations on the protocol can be done). If the connection is not ok, return FALSE and start to try opening it. If you will not be able to open the connection at all (e.g. because the host is not found), emit a finished() signal and set an error code and the QNetworkProtocol::StFailed state to the QNetworkOperation pointer you get here.

Now, you never need to check before doing an operation yourself, if the connection is ok. The network architecture does this, this means using checkConnection() it looks if an operation could be done and if not, it tries it again and again for some time and only calls an operation method if the connection is ok.

Using this knowledge it should be possible to implement network protocols. Finally to be able to use it with a QUrlOperator (and so e.g. in the QFileDialog), you have to register the network protocol implementation. This can be done like this:

    QNetworkProtocol::registerNetworkProtocol( "myprot", new QNetworkProtocolFactory<MyProtocol> );

In this case MyProtocol would be a class you implemented like described here (derived from QNetworkProtocol) and the name of the protocol would be myprot. So if you want to use it, you would do something like

    QUrlOperator op( "myprot://host/path" );
    op.listChildren();

Finally as example for a network protocol implementation you could look at the implementation of QLocalFs. The network extension will also contain an example implementation of a network protocol

Error Handling

Error handling is important for both, implementing new network protocols and using them (through QUrlOperator). So first some words about error handling when using the network protocols:

As already mentioned quite some times after processing an operation has been finished the network operation and so the QUrlOperator emits the finished() signal. This has as argument the pointer to the processed QNetworkOperation. If the state of this operation is QNetworkProtocol::StFailed, the operation contains some more information about this error. Following error codes are defined in QNetworkProtocol:

  • QNetworkProtocol::NoError - No error occurred
  • QNetworkProtocol::ErrValid - The URL you are operating on is not valid
  • QNetworkProtocol::ErrUnknownProtocol - There is no protocol implementation available for the protocol of the URL you are operating on (e.g. if the protocol is http and no http implementation has been registered)
  • QNetworkProtocol::ErrUnsupported - The operation is not supported by the protocol
  • QNetworkProtocol::ErrParse - Parse error of the URL
  • QNetworkProtocol::ErrLoginIncorrect - You needed to login but the username and or password are wrong
  • QNetworkProtocol::ErrHostNotFound - The specified host (in the URL) couldn't be found
  • QNetworkProtocol::ErrListChildren - An error occurred while listing the children
  • QNetworkProtocol::ErrMkDir - An error occurred when creating a directory
  • QNetworkProtocol::ErrRemove -An error occurred while removing a child
  • QNetworkProtocol::ErrRename - An error occurred while renaming a child
  • QNetworkProtocol::ErrGet - An error occurred while getting (retrieving) data
  • QNetworkProtocol::ErrPut - An error occurred while putting (uploading) data
  • QNetworkProtocol::ErrFileNotExisting - A file which is needed by the operation doesn't exist
  • QNetworkProtocol::ErrPermissionDenied - The permission for doing the operation has been denied

QNetworkOperation::errorCode() returns then one of these codes or maybe a different one if you use an own network protocol implementation which defines additional error codes.

QNetworkOperation::protocolDetails() may also return a string which contains an error message then which could e.g. be displayed for the user.

According to this information it should be possible to react on errors.

Now, if you implement your own network protocol, you will need to tell about errors which occurred. First you always need to be able to access the QNetworkOperation which is processed at the moment. This can be done using QNetworkOperation::operationInProgress(), which returns a pointer to the current network operation or 0 if no operation is processed at the moment.

Now if and error occurred and you need to handle it, do


    if ( operationInProgress() ) {
        operationInProgress()->setErrorCode( error_code_of_your_error );
        operationInProgress()->setProtocolDetails( detail ); // optional!
        emit finished( operationInProgress() );
        return;
    }

That's all. The connection to the QUrlOperator and so on is done automatically. Additionally, if the error was really bad so that no more operations can be done in the current state (e.g. if the host couldn't be found), call QNetworkProtocol::clearOperationStack() before emitting finished().

Now, as error code you should use, if possible, one of the predefined error codes of QNetworkProtocol. If this is not possible, you can add own error codes - they are just normal integers. Just be careful that the value of the error code doesn't conflict with an existing one.

Documentation about the low-level classes like QSocket, QDns, etc. will be included in the seperate network extension.

For internal use only.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文