使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能

使用场景:

在自有用户体系中,实现用户A给用户B发送消息,通知事务的处理结果。

举个例子:添加好友。
用户A发送好友请求给用户B,用户B收到请求后处理并将处理结果发送给用户A。

需求分析

  1. 选择一个消息推送平台:
    国内推送平台:极光、个推等,国外有GCM(Google Cloud Message)。
  2. 在自己的服务器上开发一个API。
    服务器接受用户A的请求消息,并通过推送平台将消息传递给用户B。 同理,用户B将处理结果发送给用户A。

通过上面的分析,我们结合服务器实现了功能需求。

但是,如果没有后台开发人员怎么办???

文章标题说的就是这个问题的解决方案。
AWS是亚马逊的云开发平台,类似国内的阿里云。SNS则是其中的一项服务,使应用程序、用户、终端设备能够通过云端即时发送或者接受消息。
我们不需要再开发一个API处理用户之间的消息传递,因为SNS已经全部做好了。我们需要做的便是选择一个推送平台、定义自己的消息格式,调用相关的SDK方法即可。

实现步骤(Android+GCM)

这里默认你已经有了AWS开发者账号。

1. 创建平台应用程序,得到ARN。

  • 进入SNS控制面板(控制台首页搜索SNS即可找到),依次点击“应用程序”-->“创建平台应用程序”


    创建平台应用程序
  • 应用程序名称:填写项目名称即可。
  • 推送通知平台:根据自己的需求进行选择,这里我选择GCM,通过谷歌推送来实现。
  • API密钥:在相应的推送平台注册应用后得到。
  • 获取GCM平台下API密钥
    a. 建议通过Firebase集成GCM,集成完好后,我们进入Google Cloud Platform,


    Google Cloud Platform

    b. 先选择通过Firebase创建的项目,然后在左侧菜单栏选择“API和服务”-->“凭据”。
    c. 复制红框内名称为“Server Key”的密钥(Firebase默认创建的3个密钥,分别作用于Browser、Server、Android)。
    因为我们想让AWS来处理消息传递,AWS属于Server一类。
    将获得的密钥粘贴到“创建应用平台程序”窗口的“API密钥”一栏,点击右下角“创建平台应用程序”,稍后界面显示如下:


    得到ARN

2. 将设备终端注册到ARN(代码实现),得到终端节点。
实现原理:集成GCM后,会有一个服务专门获取或更新token,我们只需要将这个token注册到ARN下即可。

//注册arn
private void sendRegistrationToServer(String token) {
        // Implement this method to send token to your app server.
        // 创建平台终端节点和管理设备令牌
        client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
        //retrieveEndpointArn方法是将保存在sp中的endpointArn取出,自己实现一下。
        String endpointArn = retrieveEndpointArn();
        boolean updateNeeded = false;
        boolean createNeeded = ("".equals(endpointArn));
        if (createNeeded) {
            // No platform endpoint ARN is stored; need to call createEndpoint.
            endpointArn = createEndpoint(token);
            createNeeded = false;
        }
        System.out.println("Retrieving platform endpoint data...");
        // Look up the platform endpoint and make sure the data in it is current, even if
        // it was just created.
        try {
            GetEndpointAttributesRequest geaReq =
                    new GetEndpointAttributesRequest()
                            .withEndpointArn(endpointArn);
            GetEndpointAttributesResult geaRes =
                    client.getEndpointAttributes(geaReq);
            updateNeeded = !geaRes.getAttributes().get("Token").equals(token)
                    || !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
        } catch (NotFoundException nfe) {
            // We had a stored ARN, but the platform endpoint associated with it
            // disappeared. Recreate it.
            createNeeded = true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (createNeeded) {
            createEndpoint(token);
        }
        System.out.println("updateNeeded = " + updateNeeded);
        if (updateNeeded) {
            // The platform endpoint is out of sync with the current data;
            // update the token and enable it.
            System.out.println("Updating platform endpoint " + endpointArn);
            Map attribs = new HashMap();
            attribs.put("Token", token);
            attribs.put("Enabled", "true");
            SetEndpointAttributesRequest saeReq =
                    new SetEndpointAttributesRequest()
                            .withEndpointArn(endpointArn)
                            .withAttributes(attribs);
            client.setEndpointAttributes(saeReq);
        }
    }
    /**
     * @return never null
     */
    private String createEndpoint(String token) {
        String endpointArn = null;
        try {
            System.out.println("Creating platform endpoint with token " + token);
            //创建endpointArn
            String arn = "自己的arn";
            CreatePlatformEndpointRequest cpeReq =
                    new CreatePlatformEndpointRequest()
                            .withPlatformApplicationArn(arn)
                            .withToken(token);
            CreatePlatformEndpointResult cpeRes = client
                    .createPlatformEndpoint(cpeReq);
            endpointArn = cpeRes.getEndpointArn();
            //订阅主题
//String topic = "自己的主题";
// SubscribeRequest request = new SubscribeRequest()
// .withTopicArn(topic)
// .withProtocol("application")
// .withEndpoint(endpointArn);
// SubscribeResult subscribeResult = client.subscribe(request);
// Log.i(TAG, "subscribeResult: " + subscribeResult.getSubscriptionArn());
        } catch (InvalidParameterException ipe) {
            String message = ipe.getErrorMessage();
            System.out.println("Exception message: " + message);
            Pattern p = Pattern.compile(".*Endpoint (arn:aws:sns[^ ]+) already exists with the same token.*");
            Matcher m = p.matcher(message);
            if (m.matches()) {
                // The platform endpoint already exists for this token, but with
                // additional custom data that
                // createEndpoint doesn't want to overwrite. Just use the
                // existing platform endpoint.
                endpointArn = m.group(1);
            } else {
                // Rethrow the exception, the input is actually bad.
                throw ipe;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        //将endpointArn保存在sp中,自己实现一下。
        storeEndpointArn(endpointArn);
        return endpointArn;
    }

完成以上步骤后,在程序启动后,便可以得到如下图所示内容。


终端节点
  • 令牌:GCM下发的token
  • 终端节点 ARN:SNS为每一个终端生成的唯一标识。

3. 推送消息到某一个终端节点。
在SNS控制台中,选择一个终端节点,点击“发布到终端节点”,在弹出的界面中,输入消息,点击发送即可。

4. 终端间互发消息(代码实现)。
经过以上3个步骤,每个用户启动应用程序后,都会注册到SNS,并生成各自特有的终端节点。
那么,终端之间发送消息就简单了。
用户A向用户B发送消息,只需要知道用户B的终端节点号码即可。

    /**
     * 给指定终端发送消息
     *
     * @param endpointArn 对方的终端节点
     * @param targetId 对方的id
     * @param gcmMsg 消息体
     * @return 发送成功后返回该条消息id
     */
    public static Observable<String> sendMsgToDevice(final String endpointArn, final String targetId, final GCMMsg gcmMsg) {
        return Observable.fromCallable(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String mId = SpUtil.getString(Constants.USER_ID);
                //发消息
                AmazonSNSClient client = new AmazonSNSClient(CognitoClientManager.getCredentials());
                client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
                PublishRequest request = new PublishRequest();
                request.setTargetArn(endpointArn);
                request.setMessageStructure("json");
                String msg = "{\n" +
                        "\"GCM\": \"{ \\\"data\\\": { " +
                        "\\\"category\\\": \\\"" + gcmMsg.getCategory() + "\\\", " +
                        "\\\"title\\\": \\\"" + gcmMsg.getTitle() + "\\\", " +
                        "\\\"message\\\": \\\"" + gcmMsg.getMessage() + "\\\", " +
                        "\\\"url\\\": \\\"" + gcmMsg.getUrl() + "\\\", " +
                        "\\\"data\\\": \\\"" + gcmMsg.getData() + "\\\" } }\"\n" +
                        "}";
                request.setMessage(msg);
                PublishResult result = client.publish(request);
                Log.i("SNS", "messageId: " + result.getMessageId());
                return result.getMessageId();
            }
        });
    }

至此,终端间互发消息的需求就完成了,全部借助AWS的SNS服务,无需另外开发后台API,自己只需要调用相关的SDK方法即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容