Smack 4.1.0 beta-1上的文件传输失败

时间:2015-01-28 22:00:42

标签: java xmpp openfire smack

我正在处理涉及两个Smack客户端的应用程序,其中一个客户端尝试将jar文件发送到另一个客户端。我把它写成桌面应用程序的一部分,而不是Android应用程序(我认为这是区别的,因为StackOverflow上的大多数Smack查询都与Android有关)。我使用Openfire 3.9.3作为XMPP服务器。对于所有代码示例,我使用以下Smack库(版本4.1.0-beta 1):smack-java7,smack-tcp,smack-extensions和smack-sasl-provided。以下是 OutgoingFileTransfer 的代码(请注意,所有代码示例信息,例如Smack用户名已被匿名化):

// LOG is of type org.apache.logging.log4j.Logger
// transferManager is the FileTransferManager for this XMPPTCPConnection
private void sendJarFile(final String to)
{
    LOG.info("Sending " + jarFileName + " to " + to);
    OutgoingFileTransfer jarTransfer = 
            transferManager.createOutgoingFileTransfer(to);
    final File jarFile = new File(jarFileName);
    try
    {
        jarTransfer.sendFile(jarFile, "The current jar file");
        while (!jarTransfer.isDone())
        {
            LOG.info("File transfer status: " + jarTransfer.getStatus());
            Thread.sleep(500);
        }
        LOG.info("File transfer to " + to + " is done");
        // Now that the file transfer is done check for errors
        // or exceptions
        FileTransfer.Error error = jarTransfer.getError();
        if (error != null)
        {
            LOG.error(error.getMessage());
        }
        Exception exception = jarTransfer.getException();
        if (exception != null)
        {
            if (exception instanceof XMPPException.XMPPErrorException)
            {
                XMPPException.XMPPErrorException errorException = 
                        (XMPPException.XMPPErrorException)exception;
                XMPPError xmppError = errorException.getXMPPError();
                LOG.error(xmppError);
                LOG.error("Descriptive text: " + xmppError.getDescriptiveText());
                LOG.error("Condition: " + xmppError.getCondition());
                LOG.error("Type: " + xmppError.getType());
            }
        }
    }
    // For now, just catching and logging exceptions. Exception handling
    // will be added in top-level classes
    catch (SmackException e)
    {
        LOG.error("Exception trying to send jar file", e);
    }
    catch (InterruptedException e)
    {
        // Do nothing
    }
    catch (Exception e)
    {
        LOG.error("Exception trying to send jar file", e);
    }
}

此代码的输出为:

  

将test.jar发送到receiver @ local_openfire / Smack

     

14:09:43.748 INFO - 文件传输状态:初始

     

14:09:44.249 INFO - 文件传输状态:协商流

     

14:09:44.751 INFO - 文件传输状态:协商流

     

//此消息会持续几秒钟,直到最后

     

14:09:53.805 INFO - 文件传输状态:协商流

     

14:09:54.308 INFO - 文件传输到receiver @ local_openfire / Smack   已完成

     

14:09:54.309错误 - org.jivesoftware.smack.packet.XMPPError@30e95075

     

14:09:54.310错误 - 描述性文字:null

     

14:09:54.310 ERROR - 条件:服务不可用

     

14:09:54.310错误 - 输入:取消

对于 IncomingFileTransfer ,代码为:

@Override
public void fileTransferRequest(FileTransferRequest request)
{
    final String requestorId = request.getRequestor();
    LOG.info("FileTransferRequest from: " + requestorId);
    // Only respond to requests from the sender
    if (requestorId.contains(senderId))
    {
        final IncomingFileTransfer transfer = request.accept();
        LOG.info("FileTransferRequest accepted");
        try
        {
            final String fileName = transfer.getFileName();
            transfer.recieveFile(new File(fileName));
            LOG.info("Incoming file transfer: " + fileName);
            LOG.info("Transfer status is: " + transfer.getStatus());
            while (!transfer.isDone())
            {
                final double progress = transfer.getProgress();
                final double progressPercent = progress * 100.0;
                String percComplete = String.format("%1$,.2f", progressPercent);
                LOG.info("Transfer status is: " + transfer.getStatus());
                LOG.info("File transfer is " + percComplete + "% complete");
                Thread.sleep(1000);
            }

            // Now that the file transfer is done check for errors
            // or exceptions
            FileTransfer.Error transferError = transfer.getError();
            if (transferError != null)
            {
                LOG.error("Transfer error occurred: " + transferError.getMessage());
            }
            Exception transferException = transfer.getException();
            if (transferException != null)
            {
                LOG.error("Transfer exception occurred: " + transferException);
                if (transferException instanceof SmackException.NoResponseException)
                {
                    SmackException.NoResponseException smackException = (SmackException.NoResponseException)transferException;
                    smackException.printStackTrace();
                }
            }
            LOG.info("FileTransfer complete");
            provisioningComplete = true;
        }
        // For now just logging exceptions
        catch (SmackException e)
        {
            LOG.error("SmackException trying to receive jar file", e);
        }
        catch (InterruptedException e)
        {
            // Do nothing
        }
        catch (IOException e)
        {
            LOG.error("IOException trying to receive jar file", e);
        }

    }
    else
    {
        LOG.warn("FileTransferRequest rejected");
        try
        {
            request.reject();
        }
        catch (NotConnectedException e)
        {
            LOG.warn("NotConnectedException when rejecting FileTransferRequest");
        }
    }
}

此代码的输出为:

  

14:09:43.766 INFO - FileTransferRequest来自:   发件人@ local_openfire /拍击

     

14:09:43.767 INFO - 接受FileTransferRequest

     

14:09:43.768 INFO - 传入文件传输:test.jar

     

14:09:43.769 INFO - 转移状态为:谈判转移

     

14:09:43.770 INFO - 转移状态为:谈判流

     

14:09:43.770 INFO - 文件传输已完成0.00%

     

14:09:44.771 INFO - 转移状态是:谈判流

     

14:09:44.771 INFO - 文件传输已完成0.00%

     

14:09:45.776 INFO - 转移状态是:谈判流

     

14:09:45.776 INFO - 文件传输已完成0.00%

     

14:09:46.778 INFO - 转移状态是:谈判流

     

14:09:46.778 INFO - 文件传输已完成0.00%

     

14:09:47.782 INFO - 转移状态是:谈判流

     

14:09:47.783 INFO - 文件传输已完成0.00%

     

14:09:48.784错误 - 发生转移异常:   org.jivesoftware.smack.SmackException:执行错误

     

14:09:48.784 INFO - FileTransfer complete

此代码运行后,在接收端,我有一个名为" test.jar"的文件。在当前工作目录中,文件大小为0字节。我在不同的机器上使用发送器和接收器以及同一台机器上的发送器和接收器都尝试了这一点。我最初使用Smack 4.0.6,但切换到最新的代码库(撰写本文时为4.1.0-beta 1),希望这个bug可能已经解决了。没有这样的运气。我将不胜感激任何建议。谢谢!

更新2015年1月30日

我不再在发送方看到XMPPError。相反,发件人仍然停留在文件传输状态:协商流状态。但是,接收器会收到以下错误:

SmackException.NoResponseException:数据包回复中没有收到响应超时。超时为5000毫秒(约5秒)

我可以看到如何提升OutgoingFileTransfer类的超时,但不能查看IncomingFileTransfer的超时。

更新2015年2月2日

我使用了Smack调试工具并捕获了原始XML节。为简洁起见,我只包括那些与文件传输相关的内容(即不存在或名单数据包)。他们在这里:

<iq to="receiver@smack_server/Smack" id="NK8Lh-11" type="set" 
from="sender@smack_server/Smack">
    <si xmlns="http://jabber.org/protocol/si" id="jsi_3077759398544954943" 
    mime-type="text/plain" profile="http://jabber.org/protocol/si/profile/file-transfer">
        <file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="test.txt" 
        size="37">
            <desc>A test file</desc>
        </file>
        <feature xmlns="http://jabber.org/protocol/feature-neg">
            <x xmlns="jabber:x:data" type="form">
                <field var="stream-method" type="list-single">
                    <option>
                        <value>http://jabber.org/protocol/bytestreams</value>
                    </option>
                    <option>
                        <value>http://jabber.org/protocol/ibb</value>
                    </option>
                </field>
            </x>
        </feature>
    </si>
</iq>

<iq to="sender@smack_server/Smack" id="NK8Lh-11" type="result">
  <si xmlns="http://jabber.org/protocol/si">
    <feature xmlns="http://jabber.org/protocol/feature-neg">
      <x xmlns="jabber:x:data" type="submit">
        <field var="stream-method">
          <value>http://jabber.org/protocol/bytestreams</value>
          <value>http://jabber.org/protocol/ibb</value>
        </field>
      </x>
    </feature>
  </si>
</iq>


<iq to="receiver@smack_server/Smack" id="NK8Lh-13" type="get" from="sender@smack_server/Smack">
    <query xmlns="http://jabber.org/protocol/disco#info"/>
</iq>

<iq to="sender@smack_server/Smack" id="NK8Lh-13" type="result">
  <query xmlns="http://jabber.org/protocol/disco#info">
    <identity category="client" name="Smack" type="pc"/>
    <feature var="http://jabber.org/protocol/disco#items"/>
    <feature var="vcard-temp"/>
    <feature var="http://jabber.org/protocol/bytestreams"/>
    <feature var="http://jabber.org/protocol/ibb"/>
    <feature var="http://jabber.org/protocol/si"/>
    <feature var="http://jabber.org/protocol/xhtml-im"/>
    <feature var="jabber:x:data"/>
    <feature var="urn:xmpp:time"/>
    <feature var="jabber:iq:privacy"/>
    <feature var="http://jabber.org/protocol/si/profile/file-transfer"/>
    <feature var="urn:xmpp:ping"/>
    <feature var="jabber:iq:last"/>
    <feature var="http://jabber.org/protocol/commands"/>
    <feature var="http://jabber.org/protocol/muc"/>
    <feature var="http://jabber.org/protocol/xdata-validate"/>
    <feature var="http://jabber.org/protocol/xdata-layout"/>
    <feature var="http://jabber.org/protocol/disco#info"/>
  </query>
</iq>

<iq to="receiver@smack_server/Smack" id="NK8Lh-25" type="set" from="sender@smack_server/Smack">
    <query xmlns="http://jabber.org/protocol/bytestreams" sid="jsi_3077759398544954943" mode="tcp">
        <streamhost jid="sender@smack_server/Smack" host="ipv6_addr1" port="7778"/>
        <streamhost jid="sender@smack_server/Smack" host="ipv4_addr1" port="7778"/>
        <streamhost jid="sender@smack_server/Smack" host="ipv6_addr2" port="7778"/>
        <streamhost jid="proxy.smack_server" host="ipv4_addr2" port="7777"/>
    </query>
</iq>

<iq to="receiver@smack_server/Smack" id="NK8Lh-26" type="set" from="sender@smack_server/Smack">
    <open xmlns="http://jabber.org/protocol/ibb" block-size="4096" sid="jsi_3077759398544954943" stanza="iq"/>
</iq>

从我阅读规范的最佳信息来看,看起来一切都在按照预期进行。发送方发送初始SI请求,接收方以支持的协议(即字节流和IBB)进行响应,然后发送方向接收方查询所有迪斯科项目,接收方响应特征列表,发送方随后发送各种流主机,然后发送方通过IBB发送数据块。从那里,接收器获取SmackException:执行方法中的错误,这是由SmackException.NoResponseException引起的,其中包含5秒内未收到响应的消息。看起来这个问题在这一点上基本上被忽视了,但是希望有人确实检查出来,我真的很感激任何帮助。谢谢!

3 个答案:

答案 0 :(得分:3)

我已经解决了这个问题!下载Smack 4.0.6源代码并在Receiver端调试数据包解析后,我发现所有内容都正确完成。正在解析IQ和数据包,XMPPTCPConnection正确处理数据包的处理。事实证明这是一种竞争条件。我相信问题出在这里:

// transfer is of type IncomingFileTransfer, created by
// FileTransferRequet.accept()
final String fileName = transfer.getFileName();
transfer.recieveFile(new File(fileName));
LOG.info("Incoming file transfer: " + fileName);
LOG.info("Transfer status is: " + transfer.getStatus());
while (!transfer.isDone())
{
    final double progress = transfer.getProgress();
    final double progressPercent = progress * 100.0;
    String percComplete = String.format("%1$,.2f", progressPercent);
    LOG.info("Transfer status is: " + transfer.getStatus());
    LOG.info("File transfer is " + percComplete + "% complete");
    Thread.sleep(1000);
}

由于某种原因,while循环消耗了所有周期,并且XMPPTCPConnection无法及时响应Open IQ和Data IQ。所以我将进度监控移动到一个新的线程中,一切都运行良好。作为参考,我在Mac OS X上使用Java版本1.8.0_31(64位),并且还在Windows 7 Professional(64位)上的Java版本1.8.0_31(64位)上进行了测试。为了澄清,我的解决方案适用于Smack 4.0.6。我还确认它在Java 7(1.7.0_51)64位上运行正常。

答案 1 :(得分:0)

也许该示例将为您提供一些想法:

private void sendFile(String to,String file){
    /*
     * This sends a file to someone
     * @param to the xmmp-account who receives the file, the destination  
     * @param file the path from the file
     */
    File f=new File(file);
    FileTransferManager manager = new FileTransferManager(conn);
    OutgoingFileTransfer transfer =
            manager.createOutgoingFileTransfer(to);

    // Send the file
    try {
        transfer.sendFile(f,"I have a file for you?");
    } catch (XMPPException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        sendMessage(logAccount,"Sorry,couldn't deliver the file");
    }

} 

您可以在Examples for org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer中找到更多内容。

答案 2 :(得分:0)

还有另一个文件接收示例可能会有所帮助:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
public void startRecvFileListen(XMPPConnection conn){
    FileTransferManager manager = new FileTransferManager(conn);
    manager.addFileTransferListener(new FileTransferListener() {
        public void fileTransferRequest(FileTransferRequest request) {
            final IncomingFileTransfer inTransfer = request.accept();
            try {
                System.out.println("filename: "+request.getFileName());
                String filePath = "D:\\datas\\smackclient\\"+request.getFileName();
                inTransfer.recieveFile(new File(filePath));
                new Thread(){
                    @Override
                    public void run(){
                        long startTime = System.currentTimeMillis();
                        while(!inTransfer.isDone()){
                            if (inTransfer.getStatus().equals(Status.error)){
                                System.out.println(sdf.format(new Date())+"error!!!"+inTransfer.getError());
                            }else{
                                double progress = inTransfer.getProgress();
                                progress*=100;
                                System.out.println(sdf.format(new Date())+"status="+inTransfer.getStatus());
                                System.out.println(sdf.format(new Date())+"progress="+nf.format(progress)+"%");
                            }
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("used "+((System.currentTimeMillis()-startTime)/1000)+" seconds  ");
                    }
                }.start();
            } catch (XMPPException e) {
                JOptionPane.showMessageDialog(null, "failed", "error", JOptionPane.ERROR_MESSAGE);
                e.printStackTrace();
            }
        }
    });
    System.out.println(connection.getUser()+"--"+connection.getServiceName());
}