【问题标题】:building, authenticating, and parsing with the Smack library使用 Smack 库构建、验证和解析
【发布时间】:2016-11-05 04:37:01
【问题描述】:

我正在尝试使用 smack 库连接到 Firebase Cloud Messaging。 我对 Smack API 了解不多。阅读 Firebase 文档后,我看到连接必须经过身份验证,并且“应用服务器”和云连接服务器之间有一系列来回响应。根据文档,我必须创建一个 Sasl Plain 身份验证。我不知道如何实现这一点。但是在阅读了其他 StackOverFlow 用户的一些帖子后,我发现我必须创建一个身份验证类。具体来说,我正在查看“Gtalk XMPP SASL 身份验证失败使用机制 X-OAUTH2?”的答案和 cmets? CCS 和“应用服务器”之间的这些响应包含在 和 标记中。我不知道如何使用 Smack 来获取或构建这些响应。我确实设置了与 XMPP 的连接,并且我尝试将 addAsyncStanzListener 设置到我的 XMPP 连接,希望能从 CCS 获得其中一些响应。但是,什么都没有进来。“应用服务器”和 CCS 之间的响应可以在此链接中找到 https://firebase.google.com/docs/cloud-messaging/server#connecting

这里有没有人知道如何从这里开始。我认为 Smack 没有很好的文档记录,而且对 XMPP 知之甚少会使情况变得更糟。有所有这些类,数据包、扩展、IQ 类、XML 拉解析器等。

设置上的任何粗略结构都是理想的。

谢谢

【问题讨论】:

    标签: android xmpp smack firebase-cloud-messaging


    【解决方案1】:

    这是我在 Firebase 中使用的服务器代码的 sn-p。如果您想更详细地了解,请尝试这篇博文(这是我最终弄清楚如何让一切正常工作的方法):http://www.grokkingandroid.com/xmpp-server-google-cloud-messaging/

    为了使下面的代码正常工作,您需要转到 Firebase 控制台并转到项目设置


    从这里您需要记下您的服务器密钥和发件人 ID,并将其替换为下面的代码 sn-p(它位于代码的底部附近)并将其保存在您的路径中作为 SmackCcsClient.class

    完成后,您可以通过命令 promt 编译和运行您的服务器:

    // Set up java file (replace PATH_TO_WHERE_YOUR_CLASS_IS with your own path and make sure to put the json and smack JARs there as well
    javac -d PATH_TO_WHERE_YOUR_CLASS_IS -sourcepath src -cp PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar PATH_TO_WHERE_YOUR_CLASS_IS\SmackCcsClient.java
    
    // Run 
    java -cp PATH_TO_WHERE_YOUR_CLASS_IS;PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar SmackCcsClient
    

    /*

    SmackCcsClient:

    import org.jivesoftware.smack.ConnectionConfiguration;
    import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
    import org.jivesoftware.smack.ConnectionListener;
    import org.jivesoftware.smack.PacketInterceptor;
    import org.jivesoftware.smack.PacketListener;
    import org.jivesoftware.smack.XMPPConnection;
    import org.jivesoftware.smack.XMPPException;
    import org.jivesoftware.smack.filter.PacketTypeFilter;
    import org.jivesoftware.smack.packet.DefaultPacketExtension;
    import org.jivesoftware.smack.packet.Message;
    import org.jivesoftware.smack.packet.Packet;
    import org.jivesoftware.smack.packet.PacketExtension;
    import org.jivesoftware.smack.provider.PacketExtensionProvider;
    import org.jivesoftware.smack.provider.ProviderManager;
    import org.jivesoftware.smack.util.StringUtils;
    import org.json.simple.JSONValue;
    import org.json.simple.parser.ParseException;
    import org.xmlpull.v1.XmlPullParser;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import java.io.*;
    import javax.net.ssl.SSLSocketFactory;
    
    /**
     * Sample Smack implementation of a client for GCM Cloud Connection Server.
     *
     * For illustration purposes only.
     */
    public class SmackCcsClient {
    
        static final String REG_ID_STORE = "gcmchat.txt";   
    
        static final String MESSAGE_KEY = "SM";
        Logger logger = Logger.getLogger("SmackCcsClient");
    
        public static final String GCM_SERVER = "fcm-xmpp.googleapis.com";
        public static final int GCM_PORT = 5235;
    
        public static final String GCM_ELEMENT_NAME = "gcm";
        public static final String GCM_NAMESPACE = "google:mobile:data";
    
        static Random random = new Random();
        XMPPConnection connection;
        ConnectionConfiguration config;
    
        /**
         * XMPP Packet Extension for GCM Cloud Connection Server.
         */
        class GcmPacketExtension extends DefaultPacketExtension {
            String json;
    
            public GcmPacketExtension(String json) {
                super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
                this.json = json;
            }
    
            public String getJson() {
                return json;
            }
    
            @Override
            public String toXML() {
                return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
                        GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
            }
    
            @SuppressWarnings("unused")
            public Packet toPacket() {
                return new Message() {
                    // Must override toXML() because it includes a <body>
                    @Override
                    public String toXML() {
    
                        StringBuilder buf = new StringBuilder();
                        buf.append("<message");
                        if (getXmlns() != null) {
                            buf.append(" xmlns=\"").append(getXmlns()).append("\"");
                        }
                        if (getLanguage() != null) {
                            buf.append(" xml:lang=\"").append(getLanguage())
                                    .append("\"");
                        }
                        if (getPacketID() != null) {
                            buf.append(" id=\"").append(getPacketID()).append("\"");
                        }
                        if (getTo() != null) {
                            buf.append(" to=\"")
                                    .append(StringUtils.escapeForXML(getTo()))
                                    .append("\"");
                        }
                        if (getFrom() != null) {
                            buf.append(" from=\"")
                                    .append(StringUtils.escapeForXML(getFrom()))
                                    .append("\"");
                        }
                        buf.append(">");
                        buf.append(GcmPacketExtension.this.toXML());
                        buf.append("</message>");
                        return buf.toString();
                    }
                };
            }
        }
    
        public SmackCcsClient() {
            // Add GcmPacketExtension
            ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
                    GCM_NAMESPACE, new PacketExtensionProvider() {
    
                        @Override
                        public PacketExtension parseExtension(XmlPullParser parser)
                                throws Exception {
                            String json = parser.nextText();
                            GcmPacketExtension packet = new GcmPacketExtension(json);
                            return packet;
                        }
                    });
        }
    
        /**
         * Returns a random message id to uniquely identify a message.
         *
         * <p>
         * Note: This is generated by a pseudo random number generator for
         * illustration purpose, and is not guaranteed to be unique.
         *
         */
        public String getRandomMessageId() {
            return "m-" + Long.toString(random.nextLong());
        }
    
        /**
         * Sends a downstream GCM message.
         */
        public void send(String jsonRequest) {
            Packet request = new GcmPacketExtension(jsonRequest).toPacket();
            connection.sendPacket(request);
        }
    
        /**
         * Handles an upstream data message from a device application.
         *
         * <p>
         * This sample echo server sends an echo message back to the device.
         * Subclasses should override this method to process an upstream message.
         */
        public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
    
            String from = jsonObject.get("from").toString();
    
            // PackageName of the application that sent this message.
            String category = jsonObject.get("category").toString();
    
            // Use the packageName as the collapseKey in the echo packet
            String collapseKey = "echo:CollapseKey";
            @SuppressWarnings("unchecked")
            Map<String, String> payload = (Map<String, String>) jsonObject
                    .get("data");
    
    
    
                String messageText = payload.get("message_text");
                String messageFrom = payload.get("message_userfrom");
                String messageTime = payload.get("message_timestamp");
                String toUser = payload.get("message_recipient");
    
                payload.put("message_text", messageText);
                payload.put("message_userfrom", messageFrom);           
                payload.put("message_timestamp", messageTime);
    
                String message = createJsonMessage(toUser, getRandomMessageId(),
                        payload, collapseKey, null, false);
                send(message);
    
        }
    
        /**
         * Handles an ACK.
         *
         * <p>
         * By default, it only logs a INFO message, but subclasses could override it
         * to properly handle ACKS.
         */
        public void handleAckReceipt(Map<String, Object> jsonObject) {
            String messageId = jsonObject.get("message_id").toString();
            String from = jsonObject.get("from").toString();
            logger.log(Level.INFO, "handleAckReceipt() from: " + from
                    + ", messageId: " + messageId);
        }
    
        /**
         * Handles a NACK.
         *
         * <p>
         * By default, it only logs a INFO message, but subclasses could override it
         * to properly handle NACKS.
         */
        public void handleNackReceipt(Map<String, Object> jsonObject) {
            String messageId = jsonObject.get("message_id").toString();
            String from = jsonObject.get("from").toString();
            logger.log(Level.INFO, "handleNackReceipt() from: " + from
                    + ", messageId: " + messageId);
        }
    
        /**
         * Creates a JSON encoded GCM message.
         *
         * @param to
         *            RegistrationId of the target device (Required).
         * @param messageId
         *            Unique messageId for which CCS will send an "ack/nack"
         *            (Required).
         * @param payload
         *            Message content intended for the application. (Optional).
         * @param collapseKey
         *            GCM collapse_key parameter (Optional).
         * @param timeToLive
         *            GCM time_to_live parameter (Optional).
         * @param delayWhileIdle
         *            GCM delay_while_idle parameter (Optional).
         * @return JSON encoded GCM message.
         */
        public static String createJsonMessage(String to, String messageId,
                Map<String, String> payload, String collapseKey, Long timeToLive,
                Boolean delayWhileIdle) {
            Map<String, Object> message = new HashMap<String, Object>();
            message.put("to", to);
            if (collapseKey != null) {
                message.put("collapse_key", collapseKey);
            }
            if (timeToLive != null) {
                message.put("time_to_live", timeToLive);
            }
            if (delayWhileIdle != null && delayWhileIdle) {
                message.put("delay_while_idle", true);
            }
            message.put("message_id", messageId);
            message.put("data", payload);
            return JSONValue.toJSONString(message);
        }
    
        /**
         * Creates a JSON encoded ACK message for an upstream message received from
         * an application.
         *
         * @param to
         *            RegistrationId of the device who sent the upstream message.
         * @param messageId
         *            messageId of the upstream message to be acknowledged to CCS.
         * @return JSON encoded ack.
         */
        public static String createJsonAck(String to, String messageId) {
            Map<String, Object> message = new HashMap<String, Object>();
            message.put("message_type", "ack");
            message.put("to", to);
            message.put("message_id", messageId);
            return JSONValue.toJSONString(message);
        }
    
        /**
         * Connects to GCM Cloud Connection Server using the supplied credentials.
         *
         * @param username
         *            GCM_SENDER_ID@gcm.googleapis.com
         * @param password
         *            API Key
         * @throws XMPPException
         */
        public void connect(String username, String password) throws XMPPException {
            config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
            config.setSecurityMode(SecurityMode.enabled);
            config.setReconnectionAllowed(true);
            config.setRosterLoadedAtLogin(false);
            config.setSendPresence(false);
            config.setSocketFactory(SSLSocketFactory.getDefault());
    
            // NOTE: Set to true to launch a window with information about packets
            // sent and received
            config.setDebuggerEnabled(true);
    
            // -Dsmack.debugEnabled=true
            XMPPConnection.DEBUG_ENABLED = true;
    
            connection = new XMPPConnection(config);
            connection.connect();
    
            connection.addConnectionListener(new ConnectionListener() {
    
                @Override
                public void reconnectionSuccessful() {
                    logger.info("Reconnecting..");
                }
    
                @Override
                public void reconnectionFailed(Exception e) {
                    logger.log(Level.INFO, "Reconnection failed.. ", e);
                }
    
                @Override
                public void reconnectingIn(int seconds) {
                    logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
                }
    
                @Override
                public void connectionClosedOnError(Exception e) {
                    logger.log(Level.INFO, "Connection closed on error.");
                }
    
                @Override
                public void connectionClosed() {
                    logger.info("Connection closed.");
                }
            });
    
            // Handle incoming packets
            connection.addPacketListener(new PacketListener() {
    
                @Override
                public void processPacket(Packet packet) {
                    logger.log(Level.INFO, "Received: " + packet.toXML());
                    Message incomingMessage = (Message) packet;
                    GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage
                            .getExtension(GCM_NAMESPACE);
                    String json = gcmPacket.getJson();
                    try {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> jsonObject = (Map<String, Object>) JSONValue
                                .parseWithException(json);
    
                        // present for "ack"/"nack", null otherwise
                        Object messageType = jsonObject.get("message_type");
    
                        if (messageType == null) {
                            // Normal upstream data message
                            handleIncomingDataMessage(jsonObject);
    
                            // Send ACK to CCS
                            String messageId = jsonObject.get("message_id")
                                    .toString();
                            String from = jsonObject.get("from").toString();
                            String ack = createJsonAck(from, messageId);
                            send(ack);
                        } else if ("ack".equals(messageType.toString())) {
                            // Process Ack
                            handleAckReceipt(jsonObject);
                        } else if ("nack".equals(messageType.toString())) {
                            // Process Nack
                            handleNackReceipt(jsonObject);
                        } else {
                            logger.log(Level.WARNING,
                                    "Unrecognized message type (%s)",
                                    messageType.toString());
                        }
                    } catch (ParseException e) {
                        logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, "Couldn't send echo.", e);
                    }
                }
            }, new PacketTypeFilter(Message.class));
    
            // Log all outgoing packets
            connection.addPacketInterceptor(new PacketInterceptor() {
                @Override
                public void interceptPacket(Packet packet) {
                    logger.log(Level.INFO, "Sent: {0}", packet.toXML());
                }
            }, new PacketTypeFilter(Message.class));
    
            connection.login(username, password);
        }
    
        public void writeToFile(String name, String regId) throws IOException {
            Map<String, String> regIdMap = readFromFile();
            regIdMap.put(name, regId);
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
                    REG_ID_STORE, false)));
            for (Map.Entry<String, String> entry : regIdMap.entrySet()) {
                out.println(entry.getKey() + "," + entry.getValue());
            }
            out.println(name + "," + regId);
            out.close();
    
        }
    
        public Map<String, String> readFromFile() {
        Map<String, String> regIdMap = null;
        try {
            BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
            String regIdLine = "";
            regIdMap = new HashMap<String, String>();
            while ((regIdLine = br.readLine()) != null) {
                String[] regArr = regIdLine.split(",");
                regIdMap.put(regArr[0], regArr[1]);
            }
            br.close();
        } catch(IOException ioe) {
        }
            return regIdMap;
        }
    
     public static void main(String [] args) {
        final String userName = "Sender ID" + "@gcm.googleapis.com";
        final String password = "Server key";
    
        SmackCcsClient ccsClient = new SmackCcsClient();
    
        try {
          ccsClient.connect(userName, password);
        } catch (XMPPException e) {
          e.printStackTrace();
        }
    }
    }
    

    【讨论】:

    • 感谢您的信息。几天前我在看这个例子。在我的代码中实现看起来还不错。但是,我发现就与 Firebase 云消息传递的连接而言,没有身份验证。如果您查看此链接firebase.google.com/docs/cloud-messaging/server#connecting,它表示您的“应用程序服务器”和 Firebase CCS 之间存在一些通信以验证连接。它说必须有 SASL 普通机制才能工作。我看不到在上面的代码中发生了什么。 @RileyMacDonald
    • 还有,莱利。为什么我们必须通过命令提示符运行我们的服务器。你不能只从你的 IDE 编译和运行,因为服务器代码驻留在你的 IDE 中。也许是因为这就是您通过命令提示符运行代码的方式? @RileyMacDonald
    • 此服务器和 FCM 之间正在建立连接。在代码的最后,您将看到最终建立连接的位置: ccsClient.connect(userName, password);此外,我只是选择通过 CMD 运行服务器进行测试,但您可以随意实现它
    • @RileyMacDonald 哇!非常感谢你温柔的互联网灵魂。您为我节省了数小时和数小时的繁琐工作。谢谢,谢谢!
    猜你喜欢
    • 2018-02-25
    • 2011-01-26
    • 1970-01-01
    • 2019-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    相关资源
    最近更新 更多