it-swarm.com.de

GCM XMPP Server mit Smack 4.1.0

Ich versuche, das bereitgestellte Beispiel hier für Smack 4.1.0 anzupassen. und ein wenig verwirrt.

Insbesondere habe ich Probleme zu verstehen, was die GcmPacketExtension jetzt erweitern sollte, wie der Konstruktor funktionieren sollte und wie der Providermanager.addExtensionProvider aktualisiert werden sollte, um mit ihm in Verbindung zu stehen.

Ich bin mir sicher, dass dies schon jemand gemacht hat, aber ich kann keine Beispiele finden und scheine mich nur anhand der Dokumentation im Kreis zu bewegen.

Jede Hilfe wäre sehr dankbar, ich bin sicher, die Antwort ist sehr einfach!

Aktueller Code (wird kompiliert, aber nicht ausgeführt):

    static {

    ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new  ExtensionElementProvider<ExtensionElement>() {
        @Override
        public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException,
        IOException {
            String json = parser.nextText();
            return new GcmPacketExtension(json);
        }
    });
}

und:

private static final class GcmPacketExtension extends DefaultExtensionElement   {

    private final 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,
                StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
    }

    public Stanza toPacket() {
        Message message = new Message();
        message.addExtension(this);
        return message;
    }
}

Aktuelle Ausnahme:

Exception in thread "main" Java.lang.NoClassDefFoundError: de/measite/minidns/DNSCache
at Java.lang.Class.forName0(Native Method)
at Java.lang.Class.forName(Unknown Source)
at org.jivesoftware.smack.SmackInitialization.loadSmackClass(SmackInitialization.Java:213)
at org.jivesoftware.smack.SmackInitialization.parseClassesToLoad(SmackInitialization.Java:193)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.Java:163)
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.Java:148)
at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.Java:116)
at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.Java:96)
at org.jivesoftware.smack.provider.ProviderManager.<clinit>(ProviderManager.Java:121)
at SmackCcsClient.<clinit>(SmackCcsClient.Java:58)
Caused by: Java.lang.ClassNotFoundException: de.measite.minidns.DNSCache
at Java.net.URLClassLoader$1.run(Unknown Source)
at Java.net.URLClassLoader$1.run(Unknown Source)
at Java.security.AccessController.doPrivileged(Native Method)
at Java.net.URLClassLoader.findClass(Unknown Source)
at Java.lang.ClassLoader.loadClass(Unknown Source)
at Sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at Java.lang.ClassLoader.loadClass(Unknown Source)
... 10 more
16
B.A.

OK, also habe ich es geschafft, es nach viel Lesen und Schmerzen zum Laufen zu bringen. Hier ist also eine SEHR grobe Server-Implementierung, die tatsächlich funktioniert. Offensichtlich nicht für die Produktion und zögern Sie nicht, alles zu korrigieren, was falsch ist. Ich sage nicht, dass dies der beste Weg ist, aber es funktioniert. Es wird eine Nachricht gesendet und Nachrichten empfangen, diese werden jedoch nur im Protokoll angezeigt.

import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.DefaultExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.roster.Roster;

import Java.io.IOException;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.UUID;
import Java.util.logging.Level;
import Java.util.logging.Logger;


import javax.net.ssl.SSLSocketFactory;

/**
 * Sample Smack implementation of a client for GCM Cloud Connection Server. This
 * code can be run as a standalone CCS client.
 *
 * <p>For illustration purposes only.
 */
public class SmackCcsClient {

    private static final Logger logger = Logger.getLogger("SmackCcsClient");

    private static final String GCM_SERVER = "gcm.googleapis.com";
    private static final int GCM_PORT = 5235;

    private static final String GCM_ELEMENT_NAME = "gcm";
    private static final String GCM_NAMESPACE = "google:mobile:data";

    private static final String YOUR_PROJECT_ID = "<your ID here>";
    private static final String YOUR_API_KEY = "<your API Key here>"; // your API Key
    private static final String YOUR_PHONE_REG_ID = "<your test phone's registration id here>";
    
    
    static {

        ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new  ExtensionElementProvider<ExtensionElement>() {
            @Override
            public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException,
            IOException {
                String json = parser.nextText();
                return new GcmPacketExtension(json);
            }
        });
    }

    private XMPPTCPConnection connection;

    /**
     * Indicates whether the connection is in draining state, which means that it
     * will not accept any new downstream messages.
     */
    protected volatile boolean connectionDraining = false;

    /**
     * Sends a downstream message to GCM.
     *
     * @return true if the message has been successfully sent.
     */
    public boolean sendDownstreamMessage(String jsonRequest) throws
            NotConnectedException {
        if (!connectionDraining) {
            send(jsonRequest);
            return true;
        }
        logger.info("Dropping downstream message since the connection is draining");
        return false;
    }

    /**
     * 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 nextMessageId() {
        return "m-" + UUID.randomUUID().toString();
    }

    /**
     * Sends a packet with contents provided.
     */
    protected void send(String jsonRequest) throws NotConnectedException {
        Stanza request = new GcmPacketExtension(jsonRequest).toPacket();
        connection.sendStanza(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 properly process upstream messages.
     */
    protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
        // PackageName of the application that sent this message.
        String category = (String) jsonObject.get("category");
        String from = (String) jsonObject.get("from");
        @SuppressWarnings("unchecked")
        Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
        payload.put("ECHO", "Application: " + category);

        // Send an ECHO response back
        String echo = createJsonMessage(from, nextMessageId(), payload,
                "echo:CollapseKey", null, false);

        try {
            sendDownstreamMessage(echo);
        } catch (NotConnectedException e) {
            logger.log(Level.WARNING, "Not connected anymore, echo message is not sent", e);
        }
        
    }

    /**
     * Handles an ACK.
     *
     * <p>Logs a INFO message, but subclasses could override it to
     * properly handle ACKs.
     */
    protected void handleAckReceipt(Map<String, Object> jsonObject) {
        String messageId = (String) jsonObject.get("message_id");
        String from = (String) jsonObject.get("from");
        logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",messageId: " + messageId);
    }

    /**
     * Handles a NACK.
     *
     * <p>Logs a INFO message, but subclasses could override it to
     * properly handle NACKs.
     */
    protected void handleNackReceipt(Map<String, Object> jsonObject) {
        String messageId = (String) jsonObject.get("message_id");
        String from = (String) jsonObject.get("from");
        logger.log(Level.INFO, "handleNackReceipt() from: " + from + ",messageId: " + messageId);
    }

    protected void handleControlMessage(Map<String, Object> jsonObject) {
        logger.log(Level.INFO, "handleControlMessage(): " + jsonObject);
        String controlType = (String) jsonObject.get("control_type");
        if ("CONNECTION_DRAINING".equals(controlType)) {
            connectionDraining = true;
        } else {
            logger.log(Level.INFO, "Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.",
                    controlType);
        }
    }

    /**
     * Creates a JSON encoded GCM message.
     *
     * @param to RegistrationId of the target device (Required).
     * @param messageId Unique messageId for which CCS sends 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.
     */
        protected 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 senderId Your GCM project number
     * @param apiKey API Key of your project
     */
    public void connect(String senderId, String apiKey)
            throws XMPPException, IOException, SmackException {
        XMPPTCPConnectionConfiguration config =
                        XMPPTCPConnectionConfiguration.builder()
                        .setServiceName(GCM_SERVER)
                     .setHost(GCM_SERVER)
                     .setCompressionEnabled(false)
                     .setPort(GCM_PORT)
                     .setConnectTimeout(30000)
                     .setSecurityMode(SecurityMode.disabled)
                     .setSendPresence(false)
                     .setSocketFactory(SSLSocketFactory.getDefault())
                    .build();
        
        connection = new XMPPTCPConnection(config);
        
        //disable Roster as I don't think this is supported by GCM
        Roster roster = Roster.getInstanceFor(connection);
        roster.setRosterLoadedAtLogin(false);

        logger.info("Connecting...");
        connection.connect();

        connection.addConnectionListener(new LoggingConnectionListener());

        // Handle incoming packets
        connection.addAsyncStanzaListener(new MyStanzaListener() , new MyStanzaFilter() );

        // Log all outgoing packets
        connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter() );

        connection.login(senderId + "@gcm.googleapis.com" , apiKey);
        
    }
    
    private class MyStanzaFilter implements StanzaFilter
    {
    
                        @Override
                        public boolean accept(Stanza arg0) {
                                // TODO Auto-generated method stub
                                if(arg0.getClass() == Stanza.class )
                                        return true;
                                else 
                                {
                                        if(arg0.getTo()!= null)
                                                if(arg0.getTo().startsWith(YOUR_PROJECT_ID) )
                                                        return true;
                                
                                }
                                
                                return false;
                        }
    }
    
    private class MyStanzaListener implements StanzaListener{
                
        @Override
        public void processPacket(Stanza 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
                    handleUpstreamMessage(jsonObject);

                    // Send ACK to CCS
                    String messageId = (String) jsonObject.get("message_id");
                    String from = (String) jsonObject.get("from");
                    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 if ("control".equals(messageType.toString())) {
                      // Process control message
                      handleControlMessage(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, "Failed to process packet", e);
            }
        }
    
    }
    
    private class MyStanzaInterceptor implements StanzaListener
    {
        @Override
        public void processPacket(Stanza packet) {
                logger.log(Level.INFO, "Sent: {0}", packet.toXML());
        }
        
    }
    

    public static void main(String[] args) throws Exception {
        
        SmackCcsClient ccsClient = new SmackCcsClient();

        ccsClient.connect(YOUR_PROJECT_ID, YOUR_API_KEY);
        
        // Send a sample hello downstream message to a device.
        String messageId = ccsClient.nextMessageId();
        Map<String, String> payload = new HashMap<String, String>();
        payload.put("Message", "Ahha, it works!");
        payload.put("CCS", "Dummy Message");
        payload.put("EmbeddedMessageId", messageId);
        String collapseKey = "sample";
        Long timeToLive = 10000L;
        String message = createJsonMessage(YOUR_PHONE_REG_ID, messageId, payload,
                collapseKey, timeToLive, true);

        ccsClient.sendDownstreamMessage(message);
        logger.info("Message sent.");
        
        //crude loop to keep connection open for receiving messages
        while(true)
        {;}
    }

    /**
     * XMPP Packet Extension for GCM Cloud Connection Server.
     */
    private static final class GcmPacketExtension extends DefaultExtensionElement   {

        private final 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,
                    StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
        }

        public Stanza toPacket() {
            Message message = new Message();
            message.addExtension(this);
            return message;
        }
    }

    private static final class LoggingConnectionListener
            implements ConnectionListener {

        @Override
        public void connected(XMPPConnection xmppConnection) {
            logger.info("Connected.");
        }
        

        @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.info("Connection closed on error.");
        }

        @Override
        public void connectionClosed() {
            logger.info("Connection closed.");
        }

                @Override
                public void authenticated(XMPPConnection arg0, boolean arg1) {
                        // TODO Auto-generated method stub
                        
                }
    }
}

Ich habe auch die folgenden externen JARs importiert: (Möglicherweise sind nicht alle erforderlich, die meisten jedoch!)

json-simple-1.1.1.jar

jxmpp-core-0.4.1.jar

jxmpp-util-cache-0.5.0-alpha2.jar

minidns-0.1.3.jar

commons-logging-1.2.jar

httpclient-4.3.4.jar

xpp3_xpath-1.1.4c.jar

xpp3-1.1.4c.jar

Für den Client habe ich das GCM-Beispielprojekt hier verwendet. (Scrollen Sie zum Ende der Seite für den Quelllink.)

Hoffe das hilft jemandem!

[23-Oct-2015] Ich bearbeite diese Antwort für andere, die Gradle verwenden. Nachfolgend sind alle Abhängigkeiten aufgeführt, die ich zum Kompilieren benötige (am Ende Ihrer build.gradle-Datei einfügen).

dependencies {
    compile 'com.googlecode.json-simple:json-simple:1.1.1'
    compile 'org.igniterealtime.smack:smack-Java7:4.1.4'
    compile 'org.igniterealtime.smack:smack-tcp:4.1.4'
    compile 'org.igniterealtime.smack:smack-im:4.1.4'
    compile 'org.jxmpp:jxmpp-core:0.5.0-alpha6'
    compile 'org.jxmpp:jxmpp-util-cache:0.5.0-alpha6'
}
27
B.A.

Für den GCM Cloud Connection Server (XMPP-Endpunkt) stellt Google zwei Referenzimplementierungen zur Verfügung.

Beide sind hier:

https://github.com/googlesamples/friendlyping/tree/master/server

Der Java-Server verwendet die Smack XMPP-Bibliothek.

Der Go-Server verwendet die go-gcm-Bibliothek von Google. - https://github.com/google/go-gcm

Der Go-Server wird auch im GCM-Playground-Beispiel verwendet - https://github.com/googlesamples/gcm-playground -, sodass der Go-Server möglicherweise von Google bevorzugt wird. Da es Go ist, kann es ohne Abhängigkeiten bereitgestellt werden, was gegenüber dem Java-Server von Vorteil ist.

1
dodgy_coder