Реализация сервера GCM CCS не получает восходящие сообщения

Я реализовал новый GCM CCS для двунаправленных сообщений между приложением Android и веб-сервером. Нисходящие сообщения (веб-устройство) работают отлично. К сожалению, восходящие сообщения (device-web) на сервер не поступают. Похоже, что они отправляются на стороне клиента (см. сообщение журнала приложения Android ниже), но сервер ничего не получает.

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

Я предполагаю, что на стороне Android все в порядке, а на стороне сервера. Дело в том, что я не могу понять, что не так, потому что соединение все еще живо, и я получаю некоторые сообщения от сервера GCM, такие как ACK. Так почему нормальные сообщения не приходят? У кого-нибудь есть идеи?

Некоторые другие детали, о которых стоит упомянуть, заключаются в том, что используемый веб-сервер — это Glassfish, и что я запускаю соединение XMPP внутри сервлета. Некоторые фрагменты ниже.

РЕДАКТИРОВАНИЕ: как я уже говорил в ответе, основная проблема, препятствовавшая получению любого сообщения на сервере, была решена. Однако довольно много сообщений до сих пор не доходит (около 50%).

Например, я отправляю 2 сообщения сразу одно за другим в фоновом потоке каждый раз, когда пользователь вносит изменения в приложение Android (нажимает кнопку, так что между каждым пакетом из 2 сообщений проходит как минимум пара секунд). Иногда я получаю оба сообщения на сервер, иногда я получаю только одно из них, иногда вообще ничего не происходит... Это серьезная проблема, особенно для приложений, в основе которых лежит эта технология. Может ли кто-нибудь помочь в устранении неполадок?

Дополнительная информация: я почти уверен, что это не связано с клиентом, так как каждое сообщение отправляется, как вы видите в журнале logcat выше, и я также получаю "event :sent" трансляция GCM через некоторое время (хотя и не сразу, может быть, около 5 минут). Так что это должно быть что-то основанное на GCM или на сервере.

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);
}
    }

person Bogdan Zurac    schedule 29.04.2014    source источник
comment
Пробовал на нескольких устройствах, та же проблема.   -  person Bogdan Zurac    schedule 30.04.2014
comment
Я также использовал GF, вызывая CcsClient из сервлета. Я должен проверить, работает ли моя установка, и если да, то чем она отличается от вашей. Один вопрос: как вы передали регистрационный идентификатор устройства на свой сервер?   -  person Wolfram Rittmeyer    schedule 30.04.2014
comment
О, спасибо, это было бы более чем полезно! Я отправил регистрационный идентификатор с помощью вызова RESTful API, ничего необычного. Почему вы спрашиваете?   -  person Bogdan Zurac    schedule 30.04.2014
comment
Я так догадался, но делаю по другому. Я уже получаю регистрационные идентификаторы, используя исходящий обмен сообщениями. Таким образом, без этого шага я не смог бы ничего отправить с сервера на устройство, потому что иначе я не знал бы регистрационные идентификаторы. Но передача их любым другим способом также должна работать. Мне просто было интересно и интересно об этом.   -  person Wolfram Rittmeyer    schedule 30.04.2014
comment
Хм. Работает как шарм с моим N5. Однако я не использую последнюю клиентскую библиотеку Google Play Services. Какой из них вы используете?   -  person Wolfram Rittmeyer    schedule 30.04.2014
comment
Я использую последнюю зависимость Gradle, которая выглядит как 4.3.23. Не могли бы вы дать мне версию, которую вы используете? Попробую тоже проверить, может в этом проблема.   -  person Bogdan Zurac    schedule 30.04.2014
comment
давайте продолжим это обсуждение в чате   -  person Wolfram Rittmeyer    schedule 01.05.2014
comment
Ты понимаешь, что теперь это работает?   -  person Bogdan Zurac    schedule 22.07.2014


Ответы (2)


Похоже, веб-приложение дважды развертывалось на сервере. Это привело к тому, что сервлет, который создает соединение XMPP, был инициализирован один раз, затем уничтожен, а затем снова инициализирован. Эта последовательность, вероятно, не очень хорошо подходила для подключения к GCM (дхо..).

Просто убедитесь, что сервлет инициализируется только один раз. Проверьте это, используя регистраторы внутри его методов init() и destroy(). Если он действительно вызывается более одного раза, попробуйте назначить веб-модуль конкретному виртуальному серверу для развертывания. Я считаю, что это отличается от веб-сервера к веб-серверу. Для Glassfish вам нужно сделать это из консоли администратора (localhost:4848).

Это решает большую проблему. Еще одна проблема, с которой я столкнулся, заключалась в том, что обмен сообщениями восходящего потока вообще не был надежным. Иногда несколько последовательных восходящих сообщений с одного и того же устройства работали безупречно, только чтобы попробовать другое, которое вообще не было отправлено на сервер. Не понял какой-либо закономерности в этой проблеме... Я вернусь, если найду что-нибудь еще.

EDIT: по-видимому, возникла проблема при использовании реализации на локальном сервере. Переехал на удаленный сервер (промежуточный VPS), и проблема, похоже, исчезла. Каждое сообщение принимается сервером. Я вернусь, если проблема не исчезнет, ​​но я сомневаюсь в этом. Я предполагаю, что локальная проблема была связана либо с моим интернет-провайдером, либо, черт возьми, даже с моим локальным подключением к Wi-Fi. У меня нет полного ответа о том, что именно вызвало это, но, по крайней мере, это отлично работает на промежуточном сервере.

person Bogdan Zurac    schedule 01.05.2014
comment
Какой хост-сервер вы используете? Я не знаю, поддерживает ли мой хост-сервер протокол XMPP. Как вы узнали, что это? Спасибо. - person Marco Altran; 25.09.2014
comment
Я использую DigitalOcean после того, как некоторое время искал лучшего провайдера с лучшими ценами. В любом случае, если у вас есть другой хост, просто позвоните ему или напишите письмо по этому вопросу. - person Bogdan Zurac; 25.09.2014
comment
Спасибо @Andrew. Я использую DailyRazor с Tomcat. Они не разрешают ни слушателей, ни входящие соединения, только выходные соединения, но они отлично работали с нисходящими сообщениями (не тестировались с восходящими сообщениями). Единственная загвоздка в том, что мне пришлось перейти на Tomcat 7 и JVM 7, потому что библиотека Smack вызывала проблемы совместимости. - person Marco Altran; 26.09.2014
comment
Могло ли это быть вызвано тем, что параметр Message ID в вызове gcm.send() вашего приложения для Android был установлен на константу? Это то, что вызвало это в моем случае. Я каждый раз передавал постоянный идентификатор сообщения вместо уникального идентификатора сообщения. Это привело к тому, что исходящие сообщения отбрасывались всякий раз, когда я пытался отправить их слишком близко друг к другу, например, 3 в секунду. Если бы я отправлял 1 в секунду или реже, он бы их не отбрасывал. Очень сложно диагностировать эту проблему, учитывая, что большую часть времени она работала. - person dodgy_coder; 29.10.2015
comment
Нет, проблема была не в этом. - person Bogdan Zurac; 29.10.2015

Публикация другого решения здесь, потому что я застрял в этой же проблеме.

Я создал простое приложение для Android, использующее GCM API для отправки восходящих сообщений на сервер и для получения нисходящих (push) сообщений с сервера.

На стороне сервера я использовал программу Java, основанную на принятом ответе на этот вопрос... Сервер GCM XMPP с использованием Smack 4.1.0, который использует Smack с открытым исходным кодом Библиотека Java XMPP.

Передача сообщений в нисходящем направлении (от сервера к устройству) была надежной — ничего не сбрасывалось.

Но не все восходящие сообщения принимались на стороне сервера... около 20-30% отбрасывались. Я также проверил с помощью Wireshark, и пакеты определенно не поступали в сетевую карту на стороне сервера.

Проблема в моем случае была связана с вызовом gcm. send() в приложении для Android... строка идентификатора сообщения действительно должна быть уникальной для каждого восходящего сообщения, тогда как я просто передавал постоянное значение.

С постоянным идентификатором сообщения это приводило к сбою исходящих сообщений всякий раз, когда я пытался отправить их слишком близко друг к другу, например, на расстоянии 250 мс. Если я отправил их с интервалом 1000 мс (1 секунда) или больше, это сработало. Очень сложно диагностировать эту проблему, учитывая, что большую часть времени она работала.

person dodgy_coder    schedule 28.10.2015