Question

I've implemented the new GCM CCS for bi-directional messages between an Android application and a web server. Downstream messages (web-device) work perfectly. Unfortunately, upstream messages (device-web) are not received on the server. They do appear to be sent on the client side (see Android app log message below), but nothing is being received on the server.

D/GCM﹕ GcmService start Intent { act=com.google.android.gcm.intent.SEND flg=0x10 pkg=com.google.android.gms cmp=com.google.android.gms/.gcm.GcmService (has extras) } com.google.android.gcm.intent.SEND

I presume nothing is wrong on the Android side, but rather on the server side. The thing is, I cannot manage to figure out what is wrong, because the connection is still alive and I do receive some messages from GCM server, like ACKs. So why are normal messages not received ? Does anybody have any idea ?

Some other details worth mentioning are that the web server being used is Glassfish and that I start the XMPP connection inside a Servlet. Some snippets below.

EDIT: As I've stated in the answer, the major problem that prevented any message being received on the server has been resolved. However, quite a lot of messages are still not being received (approx. 50%).

For example, I'm sending 2 messages immediately one after another on a background thread, every time a user makes a change in the Android app (presses a button, so there's a minimum couple of seconds between every batch of 2). Sometimes I receive both messages on the server, sometimes I receive only 1 of them, sometimes nothing even happens... This is a serious problem, especially for apps that rely on this technology at their core. Can anybody be of further assistance in order to troubleshoot this down ?

More info: I'm pretty sure this is not client related, as every message is being sent, like you see in the logcat log above and I also receive the "event:sent" GCM broadcast after a while (not immediately though, maybe like 5 mins). So it must be something GCM-based or server-based.

public class CcsServlet extends HttpServlet
{
    private static Logger logger = Logger.getLogger(CcsServlet.class.getName());


    public void init(ServletConfig config) throws ServletException
    {
        CcsManager ccsManager = CcsManager.getInstance();
        try
        {
            ccsManager.connect();
        }
        catch (Exception e)
        {
            logger.warning("Cannot connect to CCS server.");
            e.printStackTrace();
        }
    }
}


public class CcsManager
{
    private static XMPPConnection connection;
    private static Logger logger = Logger.getLogger(CcsManager.class.getName());


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

    private static CcsManager sInstance = null;
    private static final String USERNAME = "xxxxxxxxxx" + "@gcm.googleapis.com";
    private static final String PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";




        public CcsManager()
        {
            // Add GcmPacketExtension
            ProviderManager.getInstance().addExtensionProvider(
                    GcmPacketExtension.GCM_ELEMENT_NAME,
                    GcmPacketExtension.GCM_NAMESPACE, new PacketExtensionProvider()
                    {
                        public PacketExtension parseExtension(XmlPullParser parser) throws Exception
                        {
                            String json = parser.nextText();
                            return new GcmPacketExtension(json);
                        }
                    });
        }

        public static CcsManager getInstance()
        {
            if (sInstance == null)
                sInstance = new CcsManager();
            return sInstance;
        }

/**
 * Connects to GCM Cloud Connection Server
 */
public void connect() throws IOException, XMPPException
{
    ConnectionConfiguration config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
    config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);
    config.setReconnectionAllowed(true);
    config.setRosterLoadedAtLogin(false);
    config.setSendPresence(false);
    config.setSocketFactory(SSLSocketFactory.getDefault());
    config.setDebuggerEnabled(false);
    connection = new XMPPConnection(config);
    connection.connect();
    connection.addConnectionListener(new ConnectionListener()
    {
        public void reconnectionSuccessful()
        {
            logger.info("Reconnecting..");
        }

        public void reconnectionFailed(Exception e)
        {
            logger.log(Level.INFO, "Reconnection failed.. ", e);
        }

        public void reconnectingIn(int seconds)
        {
            logger.log(Level.INFO, "Reconnecting in %s secs", seconds);
        }

        public void connectionClosedOnError(Exception e)
        {
            logger.info("Connection closed on error.");
        }

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

    // Handle incoming packets
    connection.addPacketListener(new PacketListener()
    {
        public void processPacket(Packet packet)
        {
            logger.log(Level.INFO, "Received: " + packet.toXML());
            Message incomingMessage = (Message) packet;
            GcmPacketExtension gcmPacket =
                    (GcmPacketExtension) incomingMessage.getExtension(GcmPacketExtension.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()
    {
        public void interceptPacket(Packet packet)
        {
            logger.log(Level.INFO, "Sent: {0}",  packet.toXML());
        }
    }, new PacketTypeFilter(Message.class));

    connection.login(USERNAME, PASSWORD);
}
    }
Était-ce utile?

La solution

It appears that the web application was being deployed twice on the server. This caused the servlet that creates the XMPP connection to be initialized once, then destroyed, then initialized again. This sequence probably wasn't a good thing for the connection to GCM (dho..).

Just make sure that the servlet is initialized only once. Check this by using loggers in place inside its init() and destroy() methods. If it is indeed called more than once, try assigning the web module to a specific virtual server for deployment. I believe this differs from web server to web server. For Glassfish, you need to do this from within the admin console (localhost:4848).

This solves the bigger problem. Another problem I encountered was that the upstream messaging was not reliable at all. Sometimes multiple consecutive upstream messages from the same device worked flawlessly, only to try another one that wasn't pushed at all to the server. Haven't figured out any pattern to this problem... I'll get back if I find anything else.

EDIT: Apparently there was an issue when using the implementation on a local server. Moved to a remote server (staging VPS) and the issue appears to be gone. Every message is received by the server. I'll be back if the issue persists, but I doubt it. I presume the local issue was due to either my ISP, or heck, even my local Wi-Fi connection. I don't have a complete answer as to what exactly caused this, but at least it works perfectly on the staging server.

Autres conseils

Posting another solution here because I was stuck on this same issue.

I built a simple Android app that uses the GCM API to send upstream messages to the server, and to receive downstream (push) messages from the server.

For the server side, I used a java program based on the accepted answer to this question ... GCM XMPP Server using Smack 4.1.0 which uses the open source Smack Java XMPP library.

Downstream messaging (server to device) was rock solid - nothing was getting dropped.

But not all upstream messages were being received on the server side ... about 20-30% were being dropped. I also checked using Wireshark and the packets were definitely not coming into the network card on the server side.

The issue in my case was with the call to gcm.send() in the Android App ... the Message ID string really needs to be unique for every upstream message, whereas I was just passing a constant value.

With a constant message ID, it resulted in upstream messages failing whenever I tried to send them too close to each other, such as 250ms apart. If I sent them with a spacing of 1000ms (1 second) or more it worked. Very hard to diagnose this issue given that it worked most of the time.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top