在網絡不穩定時,openfire容易出現掉包狀況,緣由是在客戶端掉線時,openfire並不能立刻知道客戶端已經斷線,至於要多久才能發現客戶端斷線,跟服務器端設置的Idle Connections 時間有關。默認爲360秒。java
爲解決掉包問題,xmpp協議支持消息回執,這個只需在客戶端發消息時設置要求回執就行,服務器端不須要另外設置。服務器
使用smack設置消息回執方法網絡
package com.penngo.test; import java.awt.EventQueue; public class ReceiptDialog extends JDialog { private JTextField textField; /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { ReceiptDialog dialog = new ReceiptDialog(); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the dialog. */ public ReceiptDialog() throws Exception{ setBounds(100, 100, 450, 300); getContentPane().setLayout(null); textField = new JTextField(); textField.setBounds(20, 20, 301, 22); getContentPane().add(textField); textField.setColumns(10); Connection.DEBUG_ENABLED = true; // 打開smack debug ConnectionConfiguration config = new ConnectionConfiguration("127.0.0.1", 5222);//52222 config.setSendPresence(true); final Connection connection = new XMPPConnection(config); // 自動回覆回執方法,若是對方的消息要求回執。 ProviderManager pm = ProviderManager.getInstance(); pm.addExtensionProvider(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE, new DeliveryReceipt.Provider()); pm.addExtensionProvider(DeliveryReceiptRequest.ELEMENT, DeliveryReceipt.NAMESPACE, new DeliveryReceiptRequest.Provider()); DeliveryReceiptManager.getInstanceFor(connection).enableAutoReceipts(); // 非自動回覆回執方法 // connection.addPacketListener(new PacketListener() { // public void processPacket(Packet packet) { // // 監聽消息,在檢查到對方要求回執時,客戶端手動發送回執給對方 // if(packet instanceof Message){ // Message message = (Message)packet; // PacketExtension receipt = message.getExtension(DeliveryReceiptRequest.ELEMENT, DeliveryReceipt.NAMESPACE); // if(receipt != null){ // Message receiptMessage = new Message(); // receiptMessage.setTo(message.getFrom()); // receiptMessage.setFrom(message.getTo()); // receiptMessage.addExtension(new DeliveryReceipt(message.getPacketID())); // connection.sendPacket(receiptMessage); // } // } // } // }, new PacketFilter() { // public boolean accept(Packet packet) { // return true; // } // }); connection.connect(); String domain = connection.getServiceName(); // test1登陸,發送消息給test2 // String from = "test1"; // final String to = "test2" + "@" + domain; //test2登陸,發送消息給test1 String from = "test2"; final String to = "test1" + "@" + domain; connection.login(from, "123456", "pc"); // Presence p = new Presence(Presence.Type.available); // p.setMode(Mode.chat); // p.setStatus("在線"); // connection.sendPacket(p); final Chat chat = connection.getChatManager().createChat(to, null); JButton sendButton = new JButton("發送"); sendButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Message message = new Message(); message.setFrom(connection.getUser()); message.setTo(to); message.setBody(textField.getText()); // 添加回執請求 DeliveryReceiptManager.addDeliveryReceiptRequest(message); //也能夠這樣添加回執請求 //DeliveryReceiptRequest deliveryReceiptRequest = new DeliveryReceiptRequest(); //message.addExtension(new DeliveryReceiptRequest()); System.out.println("發送=======" + message.toXML()); try{ chat.sendMessage(message); } catch(Exception ex){ ex.printStackTrace(); } } }); sendButton.setBounds(331, 19, 93, 23); getContentPane().add(sendButton); } }
運行結果,在smack debug window中查看數據session
test2發送消息給test1,消息id爲Winlh-55app
test1發送回執給test2,告訴test2消息Winlh-55已經收到dom
上邊的方法只是客戶端對客戶端的消息回執,另外也能夠在服務器端發送回執給客戶端,告訴客戶端已經收到消息ide
package com.penngo.openfire; import java.io.File; import org.dom4j.Element; import org.jivesoftware.openfire.session.Session; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.interceptor.InterceptorManager; import org.jivesoftware.openfire.interceptor.PacketInterceptor; import org.jivesoftware.openfire.interceptor.PacketRejectedException; import org.xmpp.packet.Message; import org.xmpp.packet.Packet; public class ReceiptPlugin implements Plugin, PacketInterceptor{ private XMPPServer server; private String domain; private InterceptorManager interceptorManager; public void initializePlugin(PluginManager manager, File pluginDirectory) { server = XMPPServer.getInstance(); domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); interceptorManager = InterceptorManager.getInstance(); interceptorManager.addInterceptor(this); } public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed) throws PacketRejectedException { if(packet instanceof Message && incoming == true && processed == false){ Message message = (Message)packet; String to = message.getTo().getNode(); //注意插件中Message類來自tinder.jar包, DeliveryReceipt來自smackx.jar包 // PacketExtension receipt = message.getExtension(DeliveryReceiptRequest.ELEMENT, DeliveryReceipt.NAMESPACE); Element receipt = message.getChildElement("request", "urn:xmpp:receipts"); if(receipt != null){ Message receiptMessage = new Message(); receiptMessage.setTo(message.getFrom()); receiptMessage.setFrom(message.getTo()); // Element received = receiptMessage.addChildElement(DeliveryReceipt.ELEMEN, DeliveryReceipt.NAMESPACE); Element received = receiptMessage.addChildElement("received", "urn:xmpp:receipts"); received.setAttributeValue("id", message.getID()); try{ server.getPacketDeliverer().deliver(receiptMessage); } catch(Exception e){ e.printStackTrace(); } } } } public void destroyPlugin() { interceptorManager.removeInterceptor(this); } }
xmpp消息回執協議:this
發送者message加上<request xmlns='urn:xmpp:receipts'/>要求接收者發送回執spa
<message id="e3539-31" to="test1@xxx.com" from="test2@xxx.com/pc" type="chat"><body></body><thread></thread><request xmlns='urn:xmpp:receipts'/></message>插件
接收者在收到消息後回覆一條message,並把消息的id放到<received xmlns="urn:xmpp:receipts" id="e3539-31"/>,告訴發送者已經收到
<message to="test2@xxx.com/pc" from="test1@xxx.com"><received xmlns="urn:xmpp:receipts" id="e3539-31"/></message>
開發者在使用時,也能夠根據業務須要定義本身的回執格式。