• 18071阅读
  • 0回复

【转帖】QT网络模块 [复制链接]

上一主题 下一主题
离线XChinux
 

只看楼主 倒序阅读 楼主  发表于: 2005-06-30
QT网络模块- -转自:http://wendy924.blogchina.com/1419192.html

                       

网络模块这个模块是 Qt 企业版 的一部分。


简介
使用 QUrlOperator 和 QNetworkOperation 实现网络协议的多种操作
实现自定义网络协议
错误处理

简介
使用网络模块提供的类,可以更容易、便捷地构建网络应用程序。为了便于理解,我们将Qt网络模块提供的类分为三个集合。第一个集合中为基本的网络类,包括: QSocket, QServerSocket, QDns, 等。使用这些类实现 TCP/IP 套接字编程将更为便捷。第二个集合中,QNetworkProtocol和QNetworkOperation ,用于实现网络的抽象层;QUrlOperator 用于实现特定协议的操作。最后是一些“被动”类。 比如 QUrl 和 QUrlInfo 等实现 URL 解析或类似功能的类。

第一个集合 (QSocket, QServerSocket, QDns, QFtp, 等) 包含在 Qt 的“网络模块”中。

QSocket 等类并直接关联到QNetwork等类上面,而是用于实现网络协议,这些网路协议才“直接地”关联到 QNetwork等类上。比如:QFtp class (实现 FTP 协议) “用到”了 QSockets(关联关系)。但是实现协议并非一定要“用到” QSockets ,比如:QLocalFs (一个通过网络协议的方式访问本地文件系统的协议的实现) 用到了QDir 类,而不是 QSocket。使用 QNetworkProtocols你可以实现任何一个符合层次结构且可通过 URL 访问的协议。举个例子,你可以实现一个通过串口连接读取数字照相机里图片的协议。


通过 QUrlOperator and QNetworkOperation 实现网络协议的多种操作
仅仅是使用现有的网络协议的实现,解析 URL 并完成操作非常的简单。比如:从一个 FTP 服务器上下载文件到本地可以通过下面的代码实现:


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

只有这些代码就够了!当然还必须有一个可用的 FTP Server。 详见后面的内容。

你还可以实现像创建目录、删除文件、重命名等这样的功能。比如:通过一个私有帐号访问 FTP 服务器并创建一个目录:


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

还是那样的简单。所有可使用的功能,请查看 QUrlOperator 类的文档。

因为所有的操作都是异步完成的,即点用一个方法,在操作被处理前即返回。所以你不可能通过返回值得知操作失败或成功的信息。返回值为一个指向指针 QNetworkOperation的指针。

关于操作的所有信息都包含在 QNetworkOperation 类里。举个例子L发发 QNetworkOperation 返回操作的状态。是用它你可以得到任何时间操作的状态。你还可以得到你传给 QUrlOperator 方法的参数,操作的类型和其他很多 QNetworkOperation 对象涉及的内容。更多细节请查看 QNetworkOperation 类的文档。

稍后你可以得到 QUrlOperator 发出的信号,同志你操作运行的进程 。当你调用很多方法处理QUrlOperator的一个 URL 的时候,这些操作将被放入队列中。所以我们无法知道 QUrlOperator 正在处理的是哪一个。不过你可以通过最后一个参数,得到一个指向正在处理的、发送这信号信号的 QNetworkOperation 对象的指针。

部分操作在开始时发送一个 start() 信号 (取决于它这样做是否有意义),后边的处理过程中不同的操作发送或不发送各种信号,最后 所有操作在操作结束之后发送一个 finished() 信号. 这里操作结束可以是操作成功完成或操作失败了。若想知道运行的结果,可是使用从finished()信号得到的 QNetworkOperation 指针。 检查 QNetworkOperation::state() 的值,如果等于 QNetworkProtocol::StDone ,则操作成功结束;如果等于 QNetworkProtocol::StFailed ,则操作失败。

这里,和 QUrlOperator::finished( QNetworkOperation * ) 关联的槽可以使这样的:


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
  }
}


前面提到,操作进行的过程中还发送其他的信号。让我们看一个列出子操作清单的例子(读取FTP服务器上目录列表):


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() );
  }
}

这些例子演示了如何使用 QUrlOperator and QNetworkOperations。网络扩展部分将提供更多这方面的例子。


实现自定义网络协议
QNetworkProtocol提供一个实现网络协议的基类和一个动态登记或清除网络协议的架构。如果你使用这个架构,你不需要考虑异步运行的程序的编码问题,因为这些都为你做好了,封装起来了。

局限性: 因为编写一个适用于所有网络协议的基类是非常困难的, 所以我们现在描述的这个架构并非万能,而是为适用于所有的层次结构(比如:文件系统)而设计的。所以所有可以用层次关系描述,可以通过 URL 访问的,都可以实现为网络协议,并使用 Qt 方便的调用。这个并不仅仅局限于文件系统。

要想实现一个网络协议,需要创建一个继承自 QNetworkProtocol的类。

其他的类,使用网络协议的实现操作这个类。所以,你需要重载以下的保护方法:


  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 );

关于重载这些方法: 作为参数,我们总可以得到一个指向 QNetworkOperation 对象的参数。这个QNetworkOperation对象保存了操作的所有当前信息。如果你想开始一个操作,设置状态为 QNetworkProtocol::StInProgress。如果你想终止一个操作,设置其状态为 QNetworkProtocol::StDone ,如果发生异常,将以 QNetworkProtocol::StFailed 的状态结束。如果发生异常,你必须支应一个错误号 (参阅 QNetworkOperation::setErrorCode() ) 如果你了解其中的细节(比如一个错误信息),你应该把这个错误信息保存到这个操作指针中。(参阅 QNetworkOperation::setProtocolDetail() )。另一方面,你可以通过这个 QNetworkOperation 指针得到所有操作的信息 (操作类型、参数等) 更多关于可以得到哪些参数的信息请参阅 QNetworkOperation 类文档。

重载这些方法,还有一点非常重要:在合适的时间发送合适的信号:通常,在操作结束的时候发送 finished() 信号 (不管是成功的完成了操作还是异常退出) 这个网络架构依赖于恰当的发送 finished() 信号!所以处理这个的时候格外注意!还有其他一特殊的操作发送的特殊的信号:

operationListChildren 触发的信号:
start() 在开始监听子操作前触发
newChildren() 新的自操作开始被读入
operationMkDir触发的信号:
createdDirectory() 目录创建后触发
newChild() (or newChildren()) 新的目录背创建后触发 (新目录即新的子操作)
operationRemove触发的信号:
removed() 子操作被移出后触发
operationRename触发的信号:
itemChanged() 子操作被重命名后触发
operationGet触发的信号:
data() 每当新的数据被读入的时候触发
dataTransferProgress() 每当新数据被读入以验证当前数据输入流量时触发
operationPut触发的信号:
dataTransferProgress() 每当新数据被读入以验证当前数据写出流量时触发尽管当前操作被调用的时候你已经了解了将要操作的全部数据,但建议不要一次性写出全部数据,而是一步一步的完成,以免造成交互界面的停顿(假死),而且这种方式还可以使用户可视的看到处理的过程。
记住,务必在操作结束的时候触发 finished() 信号!

现在,通过这么一种方式,我们得到了我们正在处理的操作对象: QNetworkOperation 。下面一个 QNetworkOperation的参数和哪些参数需要在哪些方法中设置的列表:

( 要想得到你将操作的URL,使用 QNetworkProtocol::url() 方法, 这个方法返回一个指向 URL operator 的指针。使用这个指针,你可以得到路径、主机、文件名过滤器等其他 URL 信息 )


在 operationListChildren:
Nothing.
在 operationMkDir 中:
QNetworkOperation::arg( 0 ) 将要被创建的目录名
在 operationRemove 中:
QNetworkOperation::arg( 0 ) 将要被删除的目录名. 通常这是一个相对路径。但也可以是绝对路径, 可通过 QUrl( op->arg( 0 ) ).fileName() 得到文件名。
在 operationRename 中:
QNetworkOperation::arg( 0 ) 将要被重命名的文件名
QNetworkOperation::arg( 1 ) 将要被重命名为的文件名
在 operationGet 中:
QNetworkOperation::arg( 0 ) 将要获得文件的完整路径。
在 operationPut 中:
QNetworkOperation::arg( 0 ) 数据将要存储到的文件的完整路径
QNetworkOperation::rawArg( 1 ) 将要存储到 QNetworkOperation::arg( 0 ) 的数据
小结:如果你重载这些方法,你必须触发特定的信号并且 务必 在操作结束后触发 finished() 信号,不管操作成功与否。你还需要在处理过程中设定 QNetworkOperation 的状态,同时可以得到当前操作的参数。

但是不大可能你实现的协议支持所有这些操作。你只需要重载你的协议感兴趣的方法。另外,你还需要通过重载


  int QNetworkProtocol::supportedOperations() const;

指出你的协议支持那些方法。
这些被重载的方法都返回一个整型数值。这个整形数值由枚举型变量( 依从 QNetworkProtocol )相与得到。合法的枚举型变量取值:


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

所以,如果你的协议,比如支持监听子操作和重命名自操作,你的 supportedOperations()实现应该是:


  return OpListChildren | OpRename;

最后一个你需要实现的是


  bool QNetworkProtocol::checkConnection( QNetworkOperation *op );

如果连接活动并正常(这意味着协议可以完成),返回 TRUE,否则返回 FALSE,并试图打开连接。如果确实不能打开连接(比如:服务器找不到),触发 finished() 信号,设置错误码,并将得到的 QNetworkOperation 指针的状态设置为 QNetworkProtocol::StFailed 。

如果连接正常,在实施操作前你不需要再自己检查。网络架构帮你完成这些任务。调用 checkConnection() 它将检查操作是否可以完成,它将反复重试,并且只有在连接活动的情况下才会实施操作。

使用这些知识应该可以实现网络协议了.最后, 为了能够运行使用了 QUrlOperator 的程序,(例如在 QFileDialog 中), 你还需要注册网路协议的实现.象下面这样:


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

在这里 MyProtocol 是一个如上描述的类的实现 ( 继承自 QNetworkProtocol),并且协议的名称为 myprot。 所以,如果你想使用它,需要这样做:


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

最后,网络协议实现的例子可以参看 QLocalFs的实现。 网络扩展部分也有协议实现的例子。


异常处理
异常处理无论是对实现新的协议还是使用它们都同样的重要。(通过 QUrlOperator). 首先我们先看使用网路协议时的异常处理:

正如我们反复提到的,在一个网络操作结束后 QUrlOperator 触发 finished() 信号。这时我们得到一个作为参数的指向刚刚结束操作的 QNetworkOperation 对象的指针。如果操作的状态为 QNetworkProtocol::StFailed, operation 对象中保存着出错信息。QNetworkProtocol中定义了如下错误码:


QNetworkProtocol::NoError - 没有错误发生
QNetworkProtocol::ErrValid - 非法URL。
QNetworkProtocol::ErrUnknownProtocol -对URL指定的协议,没有这个协议可用的实现。
QNetworkProtocol::ErrUnsupported - 协议不支持该操作。
QNetworkProtocol::ErrParse - URL地址解析错误。
QNetworkProtocol::ErrLoginIncorrect - 需要登录,但你提供的用户名、密码不正确。
QNetworkProtocol::ErrHostNotFound - 找不到主机。
QNetworkProtocol::ErrListChildren - 列出子操作时出错。
QNetworkProtocol::ErrMkDir - 创建目录时出错。
QNetworkProtocol::ErrRemove - 删除子节点时出错。
QNetworkProtocol::ErrRename - 重命名子节点时出错。
QNetworkProtocol::ErrGet - 获取数据时出错。
QNetworkProtocol::ErrPut - 输出数据时出错。
QNetworkProtocol::ErrFileNotExisting - 操作需要的文件不存在。
QNetworkProtocol::ErrPermissionDenied - 没有执行操作的权限。
QNetworkOperation::errorCode() 将返回其中的一个错误号。当你使用自定义的协议实现的时候,也可能返回一个你自定义的错误码。

QNetworkOperation::protocolDetails() 可能返回一个包含错误信息的字符串,可以显示出来查错。

通过这些信息我们可以了解到错误是在那部分发生的。

当你自己实现协议的时候,一定要返回必要的错误信息。首先你需要访问当前正在执行的 QNetworkOperation 。可以通过 QNetworkOperation::operationInProgress()得到,QNetworkOperation:: operationInProgress()返回一个指向当前正在执行的操作的指针,如果当前没有操作在执行,返回0。

现在,如果一个错误发生了,你需要处理它,左下面的事情:


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

仅此而已。 连接到 QUrlOperator 等工作自动完成。 另外, 如果错误非常的严重,当前的状态下已经不可能再执行别的操作(比如找不到主机), 这时需要在触发finished()前调用 QNetworkProtocol::clearOperationStack() 。

尽可能使用 QNetworkProtocol 中定义的错误码。如果确实需要的话,你可以定义自己的错误码 - 只能用 整数。注意不要和现有的错误码的值冲突。

像 QSocket, QDns, 等底层类的文档包含在单独的“网络扩展”文档中。

仅供内部使用。
[ 此贴被XChinux在2005-08-02 09:13重新编辑 ]
二笔 openSUSE Vim N9 BB10 XChinux@163.com 网易博客 腾讯微博
承接C++/Qt、Qt UI界面、PHP及预算报销系统开发业务
快速回复
限100 字节
 
上一个 下一个