本篇主要是基于最近帮助朋友在 Android 中使用 asmack 库实现文件的接收和发送 功能时,写了个参考示例,这里做个记录,以便于自己以后参考。
文件传输相关的XEP协议参考:
2【XEP-0095】StreamInitiation:
2【XEP-0096】SI FileTransfer:
2【XEP-0030】ServiceDiscovery:
2【XEP-0065】SOCKS5Bytestreams:
2【XEP-0066】Out of BandData:
2【XEP-0047】In-BandBytestreams:
本实例中主要采用的是SI + IBB 的方式发送和接收文件。
1,首先在连接和登录之前设置 ProviderManager,同时设置好全局的文件传输管理器(FileTransferManager) fileTransferManager,如下:
/** * 登录 IM 服务器,同时负责 连接 和 登录 */ public boolean login(String user, String password) { ConnectionConfiguration conf = new ConnectionConfiguration( IM_HOST, IM_PORT, IM_SERVICE_NAME); conf.setDebuggerEnabled(true); conf.setCompressionEnabled(false); conf.setSendPresence(true); conf.setSASLAuthenticationEnabled(false); conf.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); connection = new XMPPConnection(conf); ProviderManager pm = ProviderManager.getInstance(); // Private Data Storage pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); // Roster Exchange pm.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider()); // Message Events pm.addExtensionProvider("x", "jabber:x:event", new MessageEventProvider()); // Chat State pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); // XHTML pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); // Group Chat Invitations pm.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider()); // Service Discovery # Items pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Data Forms pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); // MUC User pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC Admin pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // MUC Owner pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // Delayed Delivery pm.addExtensionProvider("x", "jabber:x:delay", new DelayInformationProvider()); // Version try { pm.addIQProvider("query", "jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException e) { // Not sure what's happening here. } // VCard pm.addIQProvider("vCard", "vcard-temp", new VCardProvider()); // Offline Message Requests pm.addIQProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); // Offline Message Indicator pm.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); // Last Activity pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider()); // User Search pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo pm.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new MultipleAddressesProvider());// FileTransfer pm.addIQProvider("si", "http://jabber.org/protocol/si", new StreamInitiationProvider()); pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); // Privacy pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider()); pm.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); pm.addExtensionProvider("malformed-action", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.MalformedActionError()); pm.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadLocaleError()); pm.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadPayloadError()); pm.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadSessionIDError()); pm.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.SessionExpiredError()); try { connection.connect(); connection.login(user, password); } catch (XMPPException e) { e.printStackTrace(); connection = null; return false; } fileTransferManager = new FileTransferManager(connection); ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); if (sdm == null) sdm = new ServiceDiscoveryManager(connection); sdm.addFeature("http://jabber.org/protocol/disco#info"); sdm.addFeature("jabber:iq:privacy"); FileTransferNegotiator.setServiceEnabled(connection, true); return true; } |
2,发送文件,如下:
Button btnSendFile = (Button) rootView.findViewById(R.id.send_file_btn); btnSendFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(), "send file", Toast.LENGTH_SHORT).show();new SendFileTask().execute(new Uri[]{ uriFile }); } }); // 将发送文件的操作采用 AsyncTask 的方式实现(SendFileTask): public class SendFileTask extends AsyncTask<Uri, Integer, Long> { @Override protected Long doInBackground(Uri... params) { if (params.length < 1) { return Long.valueOf(-1); } Uri uriFile = params[0]; FileTransferManager ftm = MainHelloIM.getInstance().getFileTransferManager(); if (ftm != null) { OutgoingFileTransfer oft = ftm.createOutgoingFileTransfer("blue@test.cn/Spark"); try { String[] proj = { MediaStore.Images.Media.DATA }; Cursor actualp_w_picpathcursor = managedQuery(uriFile,proj,null,null,null); int actual_p_w_picpath_column_index = actualp_w_picpathcursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); actualp_w_picpathcursor.moveToFirst(); String filePath = actualp_w_picpathcursor.getString(actual_p_w_picpath_column_index); File fileToSend = new File(filePath); if (fileToSend.exists()) { oft.sendFile(fileToSend, "recv my file!"); while (!oft.isDone()) { if (oft.getStatus().equals(FileTransfer.Status.error)) { Log.e(TAG, "send failed"); } else { Log.i(TAG, "status:" + oft.getStatus() + "|progress:" + oft.getProgress()); } Thread.sleep(1000); } } } catch (XMPPException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } return Long.valueOf(0); } } |
3,接收文件,主要通过设置 文件传输监听器 来实现:
FileTransferManager ftm = MainHelloIM.getInstance().getFileTransferManager();ftm.addFileTransferListener(new FileTransferListener() { @Override public void fileTransferRequest(FileTransferRequest fileTransferRequest) { Log.i(TAG, "has file");IncomingFileTransfer transfer = fileTransferRequest.accept(); File sdDir = Environment.getExternalStorageDirectory(); String filePath = sdDir.toString() + "/" + fileTransferRequest.getFileName(); try { transfer.recieveFile(new File(filePath)); } catch (XMPPException e) { e.printStackTrace(); } } }); |
下面为完整的文件传输的消息交互流程:
/** * 前面主要遵循标准 XEP-0095 来协商采用的文件传输方式 *///----- negotiation profile and streamhhy -> blue// hhy 需要发送 p_w_picpath/png 格式的文件 bookCater.png 给 blue <iq id="VFDHn-4" to="blue@sharexun.cn/Spark" from="hhy@sharexun.cn/Smack" type="set"> <si xmlns="http://jabber.org/protocol/si" id="jsi_7839940461539037483" mime-type="p_w_picpath/png" profile="http://jabber.org/protocol/si/profile/file-transfer"> <file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="bookCater.png" size="448" > <desc>recv my 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>blue -> hhy// blue 返回的结果显示 blue 支持 bytestreams 和 ibb 两种方式传输文件 <iq id="VFDHn-4" to="hhy@sharexun.cn/Smack" from="blue@sharexun.cn/Spark" 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>//----- service discoveryhhy -> blue <iq id="VFDHn-5" to="blue@sharexun.cn/Spark" type="get"> <query xmlns="http://jabber.org/protocol/disco#info"></query> </iq>blue -> hhy <iq id="VFDHn-5" to="hhy@sharexun.cn/Smack" type="result" from="blue@sharexun.cn/Spark"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="client" name="Smack" type="pc"/> <feature var="http://jabber.org/protocol/xhtml-im"/> <feature var="http://jabber.org/protocol/muc"/> <feature var="http://jabber.org/protocol/bytestreams"/> <feature var="http://jabber.org/protocol/commands"/> <feature var="http://jabber.org/protocol/si/profile/file-transfer"/> <feature var="http://jabber.org/protocol/si"/> <feature var="http://jabber.org/protocol/ibb"/> </query> </iq>hhy -> [server] <iq id="VFDHn-6" to="sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/disco#items"></query> </iq>blue -> hhy <iq id="VFDHn-5" to="hhy@sharexun.cn/Smack" type="result" from="blue@sharexun.cn/Spark"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="client" name="Smack" type="pc"/> <feature var="http://www.xmpp.org/extensions/xep-0166.html#ns"/> <feature var="urn:xmpp:tmp:jingle"/> </query> </iq>[server] -> hhy <iq type="result" id="VFDHn-6" from="sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/disco#items"> <item jid="proxy.sharexun.cn" name="Socks 5 Bytestreams Proxy"/> <item jid="pubsub.sharexun.cn" name="Publish-Subscribe service"/> <item jid="search.sharexun.cn" name="User Search"/> <item jid="conference.sharexun.cn" name="............"/> </query> </iq>hhy -> [server proxy] <iq id="VFDHn-7" to="proxy.sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/disco#info"></query> </iq>[server proxy] -> hhy <iq type="result" id="VFDHn-7" from="proxy.sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="proxy" name="SOCKS5 Bytestreams Service" type="bytestreams"/> <feature var="http://jabber.org/protocol/bytestreams"/> <feature var="http://jabber.org/protocol/disco#info"/> </query> </iq>hhy -> [server pubsub] <iq id="VFDHn-8" to="pubsub.sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/disco#info"></query> </iq>[server pubsub] -> hhy <iq type="result" id="VFDHn-8" from="pubsub.sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="pubsub" name="Publish-Subscribe service" type="service"/> <feature var="http://jabber.org/protocol/pubsub"/> <feature var="http://jabber.org/protocol/pubsub#collections"/> <feature var="http://jabber.org/protocol/pubsub#config-node"/> <feature var="http://jabber.org/protocol/pubsub#create-and-configure"/> <feature var="http://jabber.org/protocol/pubsub#create-nodes"/> <feature var="http://jabber.org/protocol/pubsub#delete-nodes"/> <feature var="http://jabber.org/protocol/pubsub#get-pending"/> <feature var="http://jabber.org/protocol/pubsub#instant-nodes"/> <feature var="http://jabber.org/protocol/pubsub#item-ids"/> <feature var="http://jabber.org/protocol/pubsub#meta-data"/> <feature var="http://jabber.org/protocol/pubsub#modify-affiliations"/> <feature var="http://jabber.org/protocol/pubsub#manage-subscriptions"/> <feature var="http://jabber.org/protocol/pubsub#multi-subscribe"/> <feature var="http://jabber.org/protocol/pubsub#outcast-affiliation"/> <feature var="http://jabber.org/protocol/pubsub#persistent-items"/> <feature var="http://jabber.org/protocol/pubsub#presence-notifications"/> <feature var="http://jabber.org/protocol/pubsub#publish"/> <feature var="http://jabber.org/protocol/pubsub#publisher-affiliation"/> <feature var="http://jabber.org/protocol/pubsub#purge-nodes"/> <feature var="http://jabber.org/protocol/pubsub#retract-items"/> <feature var="http://jabber.org/protocol/pubsub#retrieve-affiliations"/> <feature var="http://jabber.org/protocol/pubsub#retrieve-default"/> <feature var="http://jabber.org/protocol/pubsub#retrieve-items"/> <feature var="http://jabber.org/protocol/pubsub#retrieve-subscriptions"/> <feature var="http://jabber.org/protocol/pubsub#subscribe"/> <feature var="http://jabber.org/protocol/pubsub#subscription-options"/> <feature var="http://jabber.org/protocol/pubsub#default_access_model_open"/> <feature var="http://jabber.org/protocol/disco#info"/> </query> </iq>hhy -> [server search] <iq id="VFDHn-9" to="search.sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/disco#info"></query> </iq>[server search] -> hhy <iq type="result" id="VFDHn-9" from="search.sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="directory" type="user" name="User Search"/> <feature var="jabber:iq:search"/> <feature var="http://jabber.org/protocol/disco#info"/> <feature var="http://jabber.org/protocol/rsm"/> </query> </iq>hhy -> [server conference] <iq id="VFDHn-10" to="conference.sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/disco#info"></query> </iq>[server conference] -> hhy <iq type="result" id="VFDHn-10" from="conference.sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/disco#info"> <identity category="conference" name="............" type="text"/> <identity category="directory" name="Public Chatroom Search" type="chatroom"/> <feature var="http://jabber.org/protocol/muc"/> <feature var="http://jabber.org/protocol/disco#info"/> <feature var="http://jabber.org/protocol/disco#items"/> <feature var="jabber:iq:search"/> <feature var="http://jabber.org/protocol/rsm"/> </query> </iq>//----- 首先是采用 bytestreams 方式传输文件的hhy -> [server proxy] <iq id="VFDHn-11" to="proxy.sharexun.cn" type="get"> <query xmlns="http://jabber.org/protocol/bytestreams"/> </iq>[server proxy] -> hhy <iq type="result" id="VFDHn-11" from="proxy.sharexun.cn" to="hhy@sharexun.cn/Smack"> <query xmlns="http://jabber.org/protocol/bytestreams"> <streamhost jid="proxy.sharexun.cn" host="127.0.0.1" port="7777"/> </query> </iq> hhy -> blue// 这里要求 blue 采用 proxy server 方式来传输文件 <iq id="VFDHn-12" to="blue@sharexun.cn/Spark" type="set"> <query xmlns="http://jabber.org/protocol/bytestreams" sid="jsi_7839940461539037483" mode = "tcp"> <streamhost jid="hhy@sharexun.cn/Smack" host="127.0.0.1" port="7777"/> <streamhost jid="proxy.sharexun.cn" host="127.0.0.1" port="7777"/> </query> </iq> blue -> hhy// 可以看到 blue 无法连接到 代理服务器,并返回了 404 的错误响应 <iq id="VFDHn-12" to="hhy@sharexun.cn/Smack" from="blue@sharexun.cn/Spark" type="error"> <query xmlns="http://jabber.org/protocol/bytestreams" sid="jsi_7839940461539037483" mode="tcp"> <streamhost jid="hhy@sharexun.cn/Smack" host="127.0.0.1" port="7777"/> <streamhost jid="proxy.sharexun.cn" host="127.0.0.1" port="7777"/> </query> <error code="404" type="CANCEL"> <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">Could not establish socket with any provided host</text> </error> </iq>//----- 上面方式不行,采用 IBB 方式传输hhy -> blue// 在上面无法采用代理服务器的方式后,这里协商采用 IBB 的方式来传输文件 // 并且这里提示会采用 iq stanza 来传输文件内容 <iq id="VFDHn-13" to="blue@sharexun.cn/Spark" type="set"> <open xmlns="http://jabber.org/protocol/ibb" block-size="4096" sid="jsi_7839940461539037483" stanza="iq"/> </iq>blue -> hhy// blue 确认采用此方式传输文件 <iq id="VFDHn-13" to="hhy@sharexun.cn/Smack" from="blue@sharexun.cn/Spark" type="result"/>hhy -> blue// hhy 使用 iq/data 方式来传输文件 <iq id="VFDHn-14" to="blue@sharexun.cn/Spark" type="set"> <data xmlns="http://jabber.org/protocol/ibb" seq="0" sid="jsi_7839940461539037483"> iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAAdVBMVEU0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/I0m/L///9bqfRrsPR6t/WVxPeu0fnj7/1JovPE3fq51/nZ6fyiy/jt9P2IvvbP4/v2+v54PNP4AAAAFnRSTlMABQYJCg4PEZOWmJmiqaqu7/Hy9/n8hZw2JQAAAORJREFUeF7l08luwlAMhWG3acvQeYh955sJ3v8RmxAsQErEyYoF//qTdTampRUroOJAV58lA5Xvzz3+YLA3oscSxb9ELwx3jm2FYy+SUdyIHLWvJ7rEKaoOMpHiC11hmM1+AB7DbHsQLZ+lbEix5iUnRjF3zDhmCCc3lhDsZMzdFpsQQo1gLdwLtgbHjXgUpyySQeyjiLQQ1sx1vDvSaIHL7Wgr1tjMYF3RupO1cRbnwe6Sztfq2c2Bx9wJd9M4xYY1pXvP17/bh0Nd4gkMtBg//KH2h4i2KN70+On1G6Ff64IW9Q/DwZoIf1KdbAAAAABJRU5ErkJggg== </data> </iq>blue -> hhy// 确认已经接收完成 <iq id="VFDHn-14" to="hhy@sharexun.cn/Smack" from="blue@sharexun.cn/Spark" type="result"/>hhy -> blue// hhy 关闭本次传输 <iq id="VFDHn-15" to="blue@sharexun.cn/Spark" type="set"> <close xmlns="http://jabber.org/protocol/ibb" sid="jsi_7839940461539037483"/> </iq>blue -> hhy// 本次文件传输结束 <iq id="VFDHn-15" to="hhy@sharexun.cn/Smack" from="blue@sharexun.cn/Spark" type="result"/> |