GCM CCS服务器实现未接收到上行消息。

8

我已经为Android应用程序和Web服务器之间的双向消息实现了新的GCM CCS。下行消息(Web-设备)完美地工作。不幸的是,上行消息(设备-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 服务器接收到一些消息,比如 ACKs。那么为什么不能接收正常的消息呢?有人知道吗?
值得一提的是,使用的 Web 服务器是 Glassfish,我在 Servlet 中启动 XMPP 连接。以下是一些片段。
编辑:如我在答案中所述,导致完全无法接收到任何消息的主要问题已经解决。不过,仍然有相当多的消息没有被接收到(约 50%)。
例如,每当用户在 Android 应用程序中进行更改(按下按钮),我就会立即在后台线程上发送 2 条消息,每批之间至少间隔几秒钟。有时我会在服务器上接收到这两个消息,有时我只接收到其中一个,有时甚至什么也不发生…… 这是个很大的问题,尤其是对于以此技术为核心的应用程序。有没有人能够提供进一步的帮助来解决这个问题?
更多信息:我相当确定这与客户端无关,因为每条消息都已经发送,就像你在上面的 logcat 日志中看到的那样,我也会在一段时间后(不是立即,可能是 5 分钟左右)接收到“event:sent”GCM 广播。因此,这一定是与 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);
}
    }

在多个设备上尝试,问题依旧。 - Bogdan Zurac
我也在使用GF,在servlet中调用CcsClient。我需要检查我的设置是否仍然有效,如果有效,那么你和我之间有什么不同。还有一个问题:你是如何将设备的注册ID传输到服务器的? - Wolfram Rittmeyer
哦,谢谢,那将非常有帮助!我使用 RESTful API 调用发送了注册 ID,没什么特别的。你为什么问呢? - Bogdan Zurac
1
我猜想是这样,但我做法不同。我已经通过上行消息接收到了注册 ID。所以如果没有这一步骤,我就无法从服务器向设备发送任何东西 - 因为否则不会知道注册 ID。但是使用其他方式传输它们也应该可以。我只是感到好奇并想知道这一点。 - Wolfram Rittmeyer
你意识到它现在正在工作吗? - Bogdan Zurac
显示剩余3条评论
2个回答

6
看起来Web应用程序在服务器上部署了两次。这导致创建XMPP连接的servlet初始化一次,然后被销毁,然后再次初始化。这个顺序可能对GCM(dho..)的连接不是一个好事情。
只需确保servlet只被初始化一次。通过在其init()和destroy()方法中放置日志记录器来检查。如果确实被调用多次,请尝试将Web模块分配给特定的虚拟服务器进行部署。我相信这因Web服务器而异。对于Glassfish,您需要从管理控制台(localhost:4848)中执行此操作。
这解决了更大的问题。我遇到的另一个问题是上行消息根本不可靠。有时,来自同一设备的多个连续上行消息完美地工作,只是尝试另一个根本没有推送到服务器的消息。还没有找到任何模式来解决这个问题...如果我发现其他问题,我会回来的。
编辑:显然,在本地服务器上使用该实现时存在问题。转移到远程服务器(staging VPS)后,问题似乎已经消失。每条消息都被服务器接收。如果问题仍然存在,我会回来的,但我怀疑不会。我认为本地问题要么是由于我的ISP,要么是由于我的本地Wi-Fi连接引起的。我没有完整的答案,关于到底是什么导致了这个问题,但至少在暂存服务器上它完美地工作。

你使用的是哪个服务器主机?我不知道我的服务器主机是否支持XMPP协议。你是怎么发现它支持的呢?谢谢。 - Marco Altran
1
我使用DigitalOcean,经过一段时间的搜索,找到了最好的提供商和最优惠的价格。无论如何,如果你有其他的主机,只需打电话或发送电子邮件询问此问题即可。 - Bogdan Zurac
1
谢谢@Andrew,我正在使用带有Tomcat的DailyRazor。他们不允许监听器或传入连接,只允许输出连接,但是它在下行消息方面表现出色(尚未测试上行消息)。唯一的问题是我必须升级到Tomcat 7和JVM 7,因为Smack库会导致兼容性问题。 - Marco Altran
这可能是由于您的Android应用程序中gcm.send()调用中的Message ID参数设置为常量引起的吗?这就是我遇到的情况。我每次传递的都是一个常量消息ID,而不是唯一的消息ID。这导致在我尝试发送它们太接近(例如每秒3个)时,上行消息被丢弃。如果我每秒发送1个或更少的频率,它就不会丢失它们。鉴于它大部分时间都能正常工作,这个问题很难诊断。 - dodgy_coder
不是的,那不是问题所在。 - Bogdan Zurac

2

因为我曾经遇到同样的问题,所以在此提供另一种解决方案。

我制作了一个简单的Android应用程序,使用GCM API向服务器发送上行消息并接收来自服务器的下行(推送)消息。

对于服务器端,我使用了一个基于这个问题的已接受答案的Java程序...GCM XMPP Server using Smack 4.1.0,该程序使用了开源的Smack Java XMPP library

下行消息(服务器到设备)非常稳定 - 没有任何消息丢失。

但是,并不是所有的上行消息都能被服务器端接收到...大约20-30%的消息会被丢弃。我还使用Wireshark进行了检查,数据包明显没有进入服务器端的网络卡。

我的问题出在Android应用程序中调用gcm.send()时......每个上行消息的消息ID字符串确实需要是唯一的,而我只传递了一个常量值。

使用常量消息ID会导致上行消息在我尝试将它们彼此靠近发送时失败,例如相隔250毫秒。如果我以1000毫秒(1秒)或更长的间隔发送它们,则可以正常工作。这个问题非常难以诊断,因为它大部分时间都能正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接