QFTP源码剖析(二)——QFtpPI

QFtpPI类是对FTP协议的封装,所有的客户端命令、服务器的响应处理都在此类中执行。
这篇文章将挑选几个比较重要的函数进行说明。

1. void QFtpPI::connectToHost(const QString &host, quint16 port)

连接主机的操作。

void QFtpPI::connectToHost(const QString &host, quint16 port)
{
    emit connectState(QFtp::HostLookup);
#ifndef QT_NO_BEARERMANAGEMENT
    //copy network session down to the socket & DTP
    commandSocket.setProperty("_q_networksession", property("_q_networksession"));
    dtp.setProperty("_q_networksession", property("_q_networksession"));
#endif
    commandSocket.connectToHost(host, port);
}

2. bool QFtpPI::sendCommands(const QStringList &cmds)

通过QFtpPrivate类调用sendCommands函数压入到pendingCommands容器中,之后会立即调用一次startNextCmd函数,进行第一条命令的发送。

/*
将命令序列\a cmd发送到FTP服务器。当命令完成后,finished()信号就会发出。
发生错误时发出Error()信号。
如果队列中有待处理的命令,则此函数返回false和CMDS未添加到队列中;否则返回true。
*/
bool QFtpPI::sendCommands(const QStringList &cmds)
{
    if (!pendingCommands.isEmpty())
        return false;

    if (commandSocket.state() != QTcpSocket::ConnectedState || state != Idle) {
        emit error(QFtp::NotConnected, QFtp::tr("Not connected"));
        return true; // there are no pending commands
    }

    pendingCommands = cmds;
    startNextCmd();
    return true;
}

3. bool QFtpPI::startNextCmd()

  1. 从pendingCommands中取出下一条FTP命令,若堆栈中已经不存在待处理的命令,将发射finished信号。
  2. 区分PORT和PASV,这取决于是否应该尝试扩展传输连接命令EPRT和EPSV。
  3. 将State设置为Idle。
  4. 调用write函数发送命令数据。
bool QFtpPI::startNextCmd()
{
    if (waitForDtpToConnect)
        // don't process any new commands until we are connected
        return true;

#if defined(QFTPPI_DEBUG)
    if (state != Idle)
        qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d", state);
#endif
    if (pendingCommands.isEmpty()) {
        currentCmd.clear();
        emit finished(replyText);
        return false;
    }
    currentCmd = pendingCommands.first();

    // PORT and PASV are edited in-place, depending on whether we
    // should try the extended transfer connection commands EPRT and
    // EPSV. The PORT command also triggers setting up a listener, and
    // the address/port arguments are edited in.
    QHostAddress address = commandSocket.localAddress();
    if (currentCmd.startsWith(QLatin1String("PORT"))) {
        if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) {
            int port = dtp.setupListener(address);
            currentCmd = QLatin1String("EPRT |");
            currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2');
            currentCmd += QLatin1Char('|') + address.toString() + QLatin1Char('|') + QString::number(port);
            currentCmd += QLatin1Char('|');
        } else if (address.protocol() == QTcpSocket::IPv4Protocol) {
            int port = dtp.setupListener(address);
            QString portArg;
            quint32 ip = address.toIPv4Address();
            portArg += QString::number((ip & 0xff000000) >> 24);
            portArg += QLatin1Char(',') + QString::number((ip & 0xff0000) >> 16);
            portArg += QLatin1Char(',') + QString::number((ip & 0xff00) >> 8);
            portArg += QLatin1Char(',') + QString::number(ip & 0xff);
            portArg += QLatin1Char(',') + QString::number((port & 0xff00) >> 8);
            portArg += QLatin1Char(',') + QString::number(port & 0xff);

            currentCmd = QLatin1String("PORT ");
            currentCmd += portArg;
        } else {
            // No IPv6 connection can be set up with the PORT
            // command.
            return false;
        }

        currentCmd += QLatin1String("\r\n");
    } else if (currentCmd.startsWith(QLatin1String("PASV"))) {
        if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended)
            currentCmd = QLatin1String("EPSV\r\n");
    }

    pendingCommands.pop_front();
#if defined(QFTPPI_DEBUG)
    qDebug("QFtpPI send: %s", currentCmd.left(currentCmd.length() - 2).toLatin1().constData());
#endif
    state = Waiting;
    commandSocket.write(currentCmd.toLatin1());
    qDebug() << "qftp currentCmd : " << currentCmd.toLatin1();
    return true;
}

4. bool QFtpPI::processReply()

这个函数用于处理来自FTP服务器的应答,主要是针对响应码的处理。如果应答已被处理,则返回true;如果应答必须被处理,则返回false稍后再处理。
以下整理了一些常见的FTP响应码信息:

1xx - 信息响应:这些响应码表示一些信息性的消息,通常用于指示某个操作正在进行中或将要发生。

响应码信息
100服务器准备就绪,等待客户端继续请求
110重新启动标记应答
120服务不久即将就绪
125数据连接已打开,正在开始传输数据
150打开数据连接,准备开始传输

2xx - 成功响应:这些响应码表示操作成功完成。

响应码信息
200命令成功
202命令失败
211系统状态
212目录状态目录状态
213文件状态
214帮助信息
215名字系统类型
220服务就绪,可以执行新用户的请求
221服务关闭控制连接,可以退出登录
225数据连接打开,无传输正在进行
226关闭数据连接,请求的文件操作成功
227进入被动模式
230用户登录
250请求的文件操作完成
257创建”PATHNAME”

3xx - 重定向响应:这些响应码表示需要进一步的操作以完成请求。

响应码信息
331用户名正确,需要口令
332登录时需要帐户信息
350下一步命令

4xx - 临时错误响应:这些响应码表示客户端的请求包含错误,但可能在稍后的请求中得到解决。

响应码信息
421不能提供服务,关闭控制连接
425不能打开数据连接
426关闭连接,中止传输
450请求的文件操作未执行
451中止请求的操作:有本地错误
452未执行请求的操作:系统存储空间不足

5xx - 永久错误响应:这些响应码表示客户端的请求包含错误,且错误是永久性的,无法在当前情况下解决。

响应码信息
500格式错误,命令不可识别
501参数语法错误
502命令未实现
503命令顺序错误
504此参数下的命令功能未实现
530未登录
551请求操作中止:页类型未知
552请求的文件操作中止,存储分配溢出
bool QFtpPI::processReply()
{
#if defined(QFTPPI_DEBUG)
//    qDebug("QFtpPI state: %d [processReply() begin]", state);
    if (replyText.length() < 400)
        qDebug("QFtpPI recv: %d %s", 100 * replyCode[0] + 10 * replyCode[1] + replyCode[2], replyText.toLatin1().constData());
    else
        qDebug("QFtpPI recv: %d (text skipped)", 100 * replyCode[0] + 10 * replyCode[1] + replyCode[2]);
#endif

	// 响应码组合
    int replyCodeInt = 100 * replyCode[0] + 10 * replyCode[1] + replyCode[2];

    // 进程226回复(“关闭数据连接”)只有当数据连接是真正关闭的,以避免短读DTP
    if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(QLatin1String("RETR")))) {
        if (dtp.state() != QTcpSocket::UnconnectedState) {
            waitForDtpToClose = true;
            return false;
        }
    }

    switch (abortState) {
    case AbortStarted:
        abortState = WaitForAbortToFinish;
        break;
    case WaitForAbortToFinish:
        abortState = None;
        return true;
    default:
        break;
    }

    // get new state
    static const State table[5] = {
        /* 1yz   2yz      3yz   4yz      5yz */
        Waiting, Success, Idle, Failure, Failure
    };
    switch (state) {
    case Begin:
        if (replyCode[0] == 1) {
            return true;
        } else if (replyCode[0] == 2) {
            state = Idle;
            emit finished(QFtp::tr("Connected to host %1").arg(commandSocket.peerName()));
            break;
        }
        // reply codes not starting with 1 or 2 are not handled.
        return true;
    case Waiting:
        if (static_cast<signed char>(replyCode[0]) < 0 || replyCode[0] > 5)
            state = Failure;
        else
#if defined(Q_OS_IRIX) && defined(Q_CC_GNU)
        {
            // work around a crash on 64 bit gcc IRIX
            State *t = (State *) table;
            state = t[replyCode[0] - 1];
        }
#else
            if (replyCodeInt == 202)
                state = Failure;
            else
                state = table[replyCode[0] - 1];
#endif
        break;
    default:
        // ignore unrequested message
        return true;
    }
#if defined(QFTPPI_DEBUG)
//    qDebug("QFtpPI state: %d [processReply() intermediate]", state);
#endif

    // special actions on certain replies
    emit rawFtpReply(replyCodeInt, replyText);
    if (rawCommand) {
        rawCommand = false;
    } else if (replyCodeInt == 227) {
        // 227进入被动模式(h1,h2,h3,h4,p1,p2)
		// rfc959没有精确地定义这个响应,并给出
		// 两个使用括号的例子
		// 它们丢失了。我们需要扫描地址和主机
		// 信息。
        QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"));
        if (addrPortPattern.indexIn(replyText) == -1) {
#if defined(QFTPPI_DEBUG)
            qDebug("QFtp: bad 227 response -- address and port information missing");
#endif
            // this error should be reported
        } else {
            QStringList lst = addrPortPattern.capturedTexts();
            QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
            quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt();
            waitForDtpToConnect = true;
            dtp.connectToHost(host, port);
        }
    } else if (replyCodeInt == 229) {
        // 229 Extended Passive mode OK (|||10982|)
        int portPos = replyText.indexOf(QLatin1Char('('));
        if (portPos == -1) {
#if defined(QFTPPI_DEBUG)
            qDebug("QFtp: bad 229 response -- port information missing");
#endif
            // this error should be reported
        } else {
            ++portPos;
            QChar delimiter = replyText.at(portPos);
            QStringList epsvParameters = replyText.mid(portPos).split(delimiter);

            waitForDtpToConnect = true;
            dtp.connectToHost(commandSocket.peerAddress().toString(),
                              epsvParameters.at(3).toInt());
        }

    } else if (replyCodeInt == 230) {
        if (currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count() > 0 &&
                pendingCommands.first().startsWith(QLatin1String("PASS "))) {
            // no need to send the PASS -- we are already logged in
            pendingCommands.pop_front();
        }
        // 230 User logged in, proceed.
        emit connectState(QFtp::LoggedIn);
    } else if (replyCodeInt == 213) {
        // 213 File status.
        if (currentCmd.startsWith(QLatin1String("SIZE ")))
            dtp.setBytesTotal(replyText.simplified().toLongLong());
    } else if (replyCode[0] == 1 && currentCmd.startsWith(QLatin1String("STOR "))) {
        dtp.waitForConnection();
        dtp.writeData();
    }

    // react on new state
    switch (state) {
    case Begin:
        // should never happen
        break;
    case Success:
        // success handling
        state = Idle;
    // no break!
    case Idle:
        if (dtp.hasError()) {
            emit error(QFtp::UnknownError, dtp.errorMessage());
            dtp.clearError();
        }
        startNextCmd();
        break;
    case Waiting:
        // do nothing
        break;
    case Failure:
        // If the EPSV or EPRT commands fail, replace them with
        // the old PASV and PORT instead and try again.
        if (currentCmd.startsWith(QLatin1String("EPSV"))) {
            transferConnectionExtended = false;
            pendingCommands.prepend(QLatin1String("PASV\r\n"));
        } else if (currentCmd.startsWith(QLatin1String("EPRT"))) {
            transferConnectionExtended = false;
            pendingCommands.prepend(QLatin1String("PORT\r\n"));
        } else {
            emit error(QFtp::UnknownError, replyText);
        }
        if (state != Waiting) {
            state = Idle;
            startNextCmd();
        }
        break;
    }
#if defined(QFTPPI_DEBUG)
//    qDebug("QFtpPI state: %d [processReply() end]", state);
#endif
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值