【转】手把手教你实现自定义的应用层协议

原文: https://my.oschina.net/u/2245781/blog/1622414

报错了,

在Linux系统中,/usr/include/ 是C/C++等的头文件放置处,

-------------------------------------------------------

1.简述

  • 互联网上充斥着各种各样的网络服务,在对外提供网络服务时,服务端和客户端需要遵循同一套数据通讯协议,才能正常的进行通讯;就好像你跟台湾人沟通用闽南语,跟广东人沟通就用粤语一样。
  • 实现自己的应用功能时,已知的知名协议(http,smtp,ftp等)在安全性、可扩展性等方面不能满足需求,从而需要设计并实现自己的应用层协议。

2.协议分类

2.1按编码方式

  • 二进制协议
    比如网络通信运输层中的tcp协议。
  • 明文的文本协议
    比如应用层的http、redis协议。
  • 混合协议(二进制+明文)
    比如苹果公司早期的APNs推送协议。

2.2按协议边界

  • 固定边界协议
    能够明确得知一个协议报文的长度,这样的协议易于解析,比如tcp协议。
  • 模糊边界协议
    无法明确得知一个协议报文的长度,这样的协议解析较为复杂,通常需要通过某些特定的字节来界定报文是否结束,比如http协议。

3.协议优劣的基本评判标准

  • 高效的
    快速的打包解包减少对cpu的占用,高数据压缩率降低对网络带宽的占用。
  • 简单的
    易于人的理解、程序的解析。
  • 易于扩展的
    对可预知的变更,有足够的弹性用于扩展。
  • 容易兼容的
    • 向前兼容,对于旧协议发出的报文,能使用新协议进行解析,只是新协议支持的新功能不能使用。
    • 向后兼容,对于新协议发出的报文,能使用旧协议进行解析,只是新协议支持的新功能不能使用。

4.自定义应用层协议的优缺点

4.1优点

  • 非知名协议,数据通信更安全,黑客如果要分析协议的漏洞就必须先破译你的通讯协议。
  • 扩展性更好,可以根据业务需求和发展扩展自己的协议,而已知的知名协议不好扩展。

4.2缺点

  • 设计难度高,协议需要易扩展,最好能向后向前兼容。
  • 实现繁琐,需要自己实现序列化和反序列化。

5.动手前的预备知识

5.1大小端

计算机系统在存储数据时起始地址是高地址还是低地址。

  • 大端
    从高地址开始存储。
  • 小端
    从低地址开始存储。
  • 图解

  • 判断
    这里以c/c++语言代码为例,使用了c语言中联合体的特性。
#include <stdint.h>
#include <iostream>
using namespace std;

bool bigCheck()
{
    union Check
    {
        char a;
        uint32_t data;
    };

    Check c;
    c.data = 1;

    if (1 == c.a)
    {
        return false;
    }

    return true;
}

int main()
{
    if (bigCheck())
    {
        cout << "big" << endl;
    }
    else
    {
        cout << "small" << endl;
    }
    return 0;
}

5.2网络字节序

顾名思义就是数据在网络传送的字节流中的起始地址的高低,为了避免在网络通信中引入其他复杂性,网络字节序统一是大端的。

5.3本地字节序

本地操作系统的大小端,不同操作系统可能采用不同的字节序。

5.4内存对象与布局

任何变量,不管是堆变量还是栈变量都对应着操作系统中的一块内存,由于内存对齐的要求程序中的变量并不是紧凑存储的,例如一个c语言的结构体Test在内存中的布局可能如下图所示。

struct Test
{
    char a;
    char b;
    int32_t c;
};

5.5序列化与反序列化

  • 将计算机语言中的内存对象转换为网络字节流,例如把c语言中的结构体Test转化成uint8_t data[6]字节流。
  • 将网络字节流转换为计算机语言中的内存对象,例如把uint8_t data[6]字节流转化成c语言中的结构体Test。

6.一个例子

6.1 协议设计

本协议采用固定边界+混合编码策略。

  • 协议头
    8字节的定长协议头。支持版本号,基于魔数的快速校验,不同服务的复用。定长协议头使协议易于解析且高效。
  • 协议体
    变长json作为协议体。json使用明文文本编码,可读性强、易于扩展、前后兼容、通用的编解码算法。json协议体为协议提供了良好的扩展性和兼容性。
  • 协议可视化图

6.2 协议实现

talk is easy,just code it,使用c/c++语言来实现。

6.2.1c/c++语言实现

  • 使用结构体MyProtoHead来存储协议头
  • /*
        协议头
     */
    struct MyProtoHead
    {
        uint8_t version;    //协议版本号
        uint8_t magic;      //协议魔数
        uint16_t server;    //协议复用的服务号,标识协议之上的不同服务
        uint32_t len;       //协议长度(协议头长度+变长json协议体长度)
    };
  • 使用开源的Jsoncpp类来存储协议体
    https://sourceforge.net/proje...
  • 协议消息体
  • /*
        协议消息体
     */
    struct MyProtoMsg
    {
        MyProtoHead head;   //协议头
        Json::Value body;   //协议体
    };
  • 打包类
  • /*
        MyProto打包类
     */
    class MyProtoEnCode
    {
    public:
        //协议消息体打包函数
        uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len);
    private:
        //协议头打包函数
        void headEncode(uint8_t * pData, MyProtoMsg * pMsg);
    };
  • 解包类
  • code is easy,just run it.
    typedef enum MyProtoParserStatus
    {
        ON_PARSER_INIT = 0,
        ON_PARSER_HAED = 1,
        ON_PARSER_BODY = 2,
    }MyProtoParserStatus;
    /*
        MyProto解包类
     */
    class MyProtoDeCode
    {
    public:
        void init();
        void clear();
        bool parser(void * data, size_t len);
        bool empty();
        MyProtoMsg * front();
        void pop();
    private:
        bool parserHead(uint8_t ** curData, uint32_t & curLen,
            uint32_t & parserLen, bool & parserBreak);
        bool parserBody(uint8_t ** curData, uint32_t & curLen,
            uint32_t & parserLen, bool & parserBreak);
    
    private:
        MyProtoMsg mCurMsg;                     //当前解析中的协议消息体
        queue<MyProtoMsg *> mMsgQ;              //解析好的协议消息队列
        vector<uint8_t> mCurReserved;           //未解析的网络字节流
        MyProtoParserStatus mCurParserStatus;   //当前解析状态
    };

    6.2.2打包(序列化)

    void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg)
    {
        //设置协议头版本号为1
        *pData = 1;
        ++pData;
    
        //设置协议头魔数
        *pData = MY_PROTO_MAGIC;
        ++pData;
    
        //设置协议服务号,把head.server本地字节序转换为网络字节序
        *(uint16_t *)pData = htons(pMsg->head.server);
        pData += 2;
    
        //设置协议总长度,把head.len本地字节序转换为网络字节序
        *(uint32_t *)pData = htonl(pMsg->head.len);
    }
    
    uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len)
    {
        uint8_t * pData = NULL;
        Json::FastWriter fWriter;
    
        //协议json体序列化
        string bodyStr = fWriter.write(pMsg->body);
        //计算协议消息序列化后的总长度
        len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size();
        pMsg->head.len = len;
        //申请协议消息序列化需要的空间
        pData = new uint8_t[len];
        //打包协议头
        headEncode(pData, pMsg);
        //打包协议体
        memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size());
    
        return pData;
    }

    6.2.3解包(反序列化)

    bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen,
        uint32_t & parserLen, bool & parserBreak)
    {
        parserBreak = false;
        if (curLen < MY_PROTO_HEAD_SIZE)
        {
            parserBreak = true; //终止解析
            return true;
        }
    
        uint8_t * pData = *curData;
        //解析版本号
        mCurMsg.head.version = *pData;
        pData++;
        //解析魔数
        mCurMsg.head.magic = *pData;
        pData++;
        //魔数不一致,则返回解析失败
        if (MY_PROTO_MAGIC != mCurMsg.head.magic)
        {
            return false;
        }
        //解析服务号
        mCurMsg.head.server = ntohs(*(uint16_t*)pData);
        pData+=2;
        //解析协议消息体的长度
        mCurMsg.head.len = ntohl(*(uint32_t*)pData);
        //异常大包,则返回解析失败
        if (mCurMsg.head.len > MY_PROTO_MAX_SIZE)
        {
            return false;
        }
    
        //解析指针向前移动MY_PROTO_HEAD_SIZE字节
        (*curData) += MY_PROTO_HEAD_SIZE;
        curLen -= MY_PROTO_HEAD_SIZE;
        parserLen += MY_PROTO_HEAD_SIZE;
        mCurParserStatus = ON_PARSER_HAED;
    
        return true;
    }
    
    bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen,
        uint32_t & parserLen, bool & parserBreak)
    {
        parserBreak = false;
        uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE;
        if (curLen < jsonSize)
        {
            parserBreak = true; //终止解析
            return true;
        }
    
        Json::Reader reader;    //json解析类
        if (!reader.parse((char *)(*curData),
            (char *)((*curData) + jsonSize), mCurMsg.body, false))
        {
            return false;
        }
    
        //解析指针向前移动jsonSize字节
        (*curData) += jsonSize;
        curLen -= jsonSize;
        parserLen += jsonSize;
        mCurParserStatus = ON_PARSER_BODY;
    
        return true;
    }
    
    bool MyProtoDeCode::parser(void * data, size_t len)
    {
        if (len <= 0)
        {
            return false;
        }
    
        uint32_t curLen = 0;
        uint32_t parserLen = 0;
        uint8_t * curData = NULL;
    
        curData = (uint8_t *)data;
        //把当前要解析的网络字节流写入未解析完字节流之后
        while (len--)
        {
            mCurReserved.push_back(*curData);
            ++curData;
        }
    
        curLen = mCurReserved.size();
        curData = (uint8_t *)&mCurReserved[0];
    
        //只要还有未解析的网络字节流,就持续解析
        while (curLen > 0)
        {
            bool parserBreak = false;
            //解析协议头
            if (ON_PARSER_INIT == mCurParserStatus ||
                ON_PARSER_BODY == mCurParserStatus)
            {
                if (!parserHead(&curData, curLen, parserLen, parserBreak))
                {
                    return false;
                }
    
                if (parserBreak) break;
            }
    
            //解析完协议头,解析协议体
            if (ON_PARSER_HAED == mCurParserStatus)
            {
                if (!parserBody(&curData, curLen, parserLen, parserBreak))
                {
                    return false;
                }
    
                if (parserBreak) break;
            }
    
            if (ON_PARSER_BODY == mCurParserStatus)
            {
                //拷贝解析完的消息体放入队列中
                MyProtoMsg * pMsg = NULL;
                pMsg = new MyProtoMsg;
                *pMsg = mCurMsg;
                mMsgQ.push(pMsg);
            }
        }
    
        if (parserLen > 0)
        {
            //删除已经被解析的网络字节流
            mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen);
        }
    
        return true;
    }

    7.完整源码与测试

#include <stdint.h>
#include <stdio.h>
#include <queue>
#include <vector>
#include <iostream>
#include <string.h>
#include <jsoncpp/json/json.h>
#include <arpa/inet.h>
using namespace std;

const uint8_t MY_PROTO_MAGIC = 88;
const uint32_t MY_PROTO_MAX_SIZE = 10 * 1024 * 1024; //10M
const uint32_t MY_PROTO_HEAD_SIZE = 8;

typedef enum MyProtoParserStatus
{
    ON_PARSER_INIT = 0,
    ON_PARSER_HAED = 1,
    ON_PARSER_BODY = 2,
}MyProtoParserStatus;

/*
    协议头
 */
struct MyProtoHead
{
    uint8_t version;    //协议版本号
    uint8_t magic;      //协议魔数
    uint16_t server;    //协议复用的服务号,标识协议之上的不同服务
    uint32_t len;       //协议长度(协议头长度+变长json协议体长度)
};

/*
    协议消息体
 */
struct MyProtoMsg
{
    MyProtoHead head;   //协议头
    Json::Value body;   //协议体
};

void myProtoMsgPrint(MyProtoMsg & msg)
{
    string jsonStr = "";
    Json::FastWriter fWriter;
    jsonStr = fWriter.write(msg.body);

    printf("Head[version=%d,magic=%d,server=%d,len=%d]\n"
        "Body:%s", msg.head.version, msg.head.magic,
        msg.head.server, msg.head.len, jsonStr.c_str());
}
/*
    MyProto打包类
 */
class MyProtoEnCode
{
public:
    //协议消息体打包函数
    uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len);
private:
    //协议头打包函数
    void headEncode(uint8_t * pData, MyProtoMsg * pMsg);
};

void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg)
{
    //设置协议头版本号为1
    *pData = 1;
    ++pData;

    //设置协议头魔数
    *pData = MY_PROTO_MAGIC;
    ++pData;

    //设置协议服务号,把head.server本地字节序转换为网络字节序
    *(uint16_t *)pData = htons(pMsg->head.server);
    pData += 2;

    //设置协议总长度,把head.len本地字节序转换为网络字节序
    *(uint32_t *)pData = htonl(pMsg->head.len);
}

uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len)
{
    uint8_t * pData = NULL;
    Json::FastWriter fWriter;

    //协议json体序列化
    string bodyStr = fWriter.write(pMsg->body);
    //计算协议消息序列化后的总长度
    len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size();
    pMsg->head.len = len;
    //申请协议消息序列化需要的空间
    pData = new uint8_t[len];
    //打包协议头
    headEncode(pData, pMsg);
    //打包协议体
    memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size());

    return pData;
}

/*
    MyProto解包类
 */
class MyProtoDeCode
{
public:
    void init();
    void clear();
    bool parser(void * data, size_t len);
    bool empty();
    MyProtoMsg * front();
    void pop();
private:
    bool parserHead(uint8_t ** curData, uint32_t & curLen,
        uint32_t & parserLen, bool & parserBreak);
    bool parserBody(uint8_t ** curData, uint32_t & curLen,
        uint32_t & parserLen, bool & parserBreak);

private:
    MyProtoMsg mCurMsg;                     //当前解析中的协议消息体
    queue<MyProtoMsg *> mMsgQ;              //解析好的协议消息队列
    vector<uint8_t> mCurReserved;           //未解析的网络字节流
    MyProtoParserStatus mCurParserStatus;   //当前解析状态
};

void MyProtoDeCode::init()
{
    mCurParserStatus = ON_PARSER_INIT;
}

void MyProtoDeCode::clear()
{
    MyProtoMsg * pMsg = NULL;

    while (!mMsgQ.empty())
    {
        pMsg = mMsgQ.front();
        delete pMsg;
        mMsgQ.pop();
    }
}

bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen,
    uint32_t & parserLen, bool & parserBreak)
{
    parserBreak = false;
    if (curLen < MY_PROTO_HEAD_SIZE)
    {
        parserBreak = true; //终止解析
        return true;
    }

    uint8_t * pData = *curData;
    //解析版本号
    mCurMsg.head.version = *pData;
    pData++;
    //解析魔数
    mCurMsg.head.magic = *pData;
    pData++;
    //魔数不一致,则返回解析失败
    if (MY_PROTO_MAGIC != mCurMsg.head.magic)
    {
        return false;
    }
    //解析服务号
    mCurMsg.head.server = ntohs(*(uint16_t*)pData);
    pData+=2;
    //解析协议消息体的长度
    mCurMsg.head.len = ntohl(*(uint32_t*)pData);
    //异常大包,则返回解析失败
    if (mCurMsg.head.len > MY_PROTO_MAX_SIZE)
    {
        return false;
    }

    //解析指针向前移动MY_PROTO_HEAD_SIZE字节
    (*curData) += MY_PROTO_HEAD_SIZE;
    curLen -= MY_PROTO_HEAD_SIZE;
    parserLen += MY_PROTO_HEAD_SIZE;
    mCurParserStatus = ON_PARSER_HAED;

    return true;
}

bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen,
    uint32_t & parserLen, bool & parserBreak)
{
    parserBreak = false;
    uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE;
    if (curLen < jsonSize)
    {
        parserBreak = true; //终止解析
        return true;
    }

    Json::Reader reader;    //json解析类
    if (!reader.parse((char *)(*curData),
        (char *)((*curData) + jsonSize), mCurMsg.body, false))
    {
        return false;
    }

    //解析指针向前移动jsonSize字节
    (*curData) += jsonSize;
    curLen -= jsonSize;
    parserLen += jsonSize;
    mCurParserStatus = ON_PARSER_BODY;

    return true;
}

bool MyProtoDeCode::parser(void * data, size_t len)
{
    if (len <= 0)
    {
        return false;
    }

    uint32_t curLen = 0;
    uint32_t parserLen = 0;
    uint8_t * curData = NULL;

    curData = (uint8_t *)data;
    //把当前要解析的网络字节流写入未解析完字节流之后
    while (len--)
    {
        mCurReserved.push_back(*curData);
        ++curData;
    }

    curLen = mCurReserved.size();
    curData = (uint8_t *)&mCurReserved[0];

    //只要还有未解析的网络字节流,就持续解析
    while (curLen > 0)
    {
        bool parserBreak = false;
        //解析协议头
        if (ON_PARSER_INIT == mCurParserStatus ||
            ON_PARSER_BODY == mCurParserStatus)
        {
            if (!parserHead(&curData, curLen, parserLen, parserBreak))
            {
                return false;
            }

            if (parserBreak) break;
        }

        //解析完协议头,解析协议体
        if (ON_PARSER_HAED == mCurParserStatus)
        {
            if (!parserBody(&curData, curLen, parserLen, parserBreak))
            {
                return false;
            }

            if (parserBreak) break;
        }

        if (ON_PARSER_BODY == mCurParserStatus)
        {
            //拷贝解析完的消息体放入队列中
            MyProtoMsg * pMsg = NULL;
            pMsg = new MyProtoMsg;
            *pMsg = mCurMsg;
            mMsgQ.push(pMsg);
        }
    }

    if (parserLen > 0)
    {
        //删除已经被解析的网络字节流
        mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen);
    }

    return true;
}

bool MyProtoDeCode::empty()
{
    return mMsgQ.empty();
}

MyProtoMsg * MyProtoDeCode::front()
{
    MyProtoMsg * pMsg = NULL;
    pMsg = mMsgQ.front();
    return pMsg;
}

void MyProtoDeCode::pop()
{
    mMsgQ.pop();
}

int main()
{
    uint32_t len = 0;
    uint8_t * pData = NULL;
    MyProtoMsg msg1;
    MyProtoMsg msg2;
    MyProtoDeCode myDecode;
    MyProtoEnCode myEncode;

    msg1.head.server = 1;
    msg1.body["op"] = "set";
    msg1.body["key"] = "id";
    msg1.body["value"] = "9856";

    msg2.head.server = 2;
    msg2.body["op"] = "get";
    msg2.body["key"] = "id";

    myDecode.init();
    pData = myEncode.encode(&msg1, len);
    if (!myDecode.parser(pData, len))
    {
        cout << "parser falied!" << endl;
    }
    else
    {
        cout << "msg1 parser successful!" << endl;
    }

    pData = myEncode.encode(&msg2, len);
    if (!myDecode.parser(pData, len))
    {
        cout << "parser falied!" << endl;
    }
    else
    {
        cout << "msg2 parser successful!" << endl;
    }

    MyProtoMsg * pMsg = NULL;
    while (!myDecode.empty())
    {
        pMsg = myDecode.front();
        myProtoMsgPrint(*pMsg);
        myDecode.pop();
    }

    return 0;
}

7.2运行测试

$ sudo apt-get install libjsoncpp-dev libjsoncpp1                       //安装json开发库
$ g++ -o myprotest myprotest.cpp  -ljsoncpp
$ ./myprotest
msg1 parser successful!
msg2 parser successful!
Head[version=1,magic=88,server=1,len=47]
Body:{"key":"id","op":"set","value":"9856"}
Head[version=1,magic=88,server=2,len=32]
Body:{"key":"id","op":"get"}

8.总结

不到350行的代码向我们展示了一个自定义的应用层协议该如何实现,当然这个协议是不够完善的,还可以对其完善,比如对协议体进行加密加强协议的安全性等。

原文地址:https://www.cnblogs.com/oxspirt/p/10219253.html

时间: 2024-10-10 14:45:06

【转】手把手教你实现自定义的应用层协议的相关文章

Android 自定义ViewGroup手把手教你实现ArcMenu

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你如何使用这个开源项目,而是教你如何自己通过自定义ViewGroup写这样的效果,自定义ViewGroup也是我的痛楚,嘿嘿,希望以此可以抛砖引玉~~ 效果图: 1.实现思路 通过效果图,会有几个问题: a.动画效果如何实现 可以看出动画是从顶点外外发射的,可能有人

Android 手把手教您自定义ViewGroup(一)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38339817 , 本文出自:[张鸿洋的博客] 最近由于工作的变动,导致的博客的更新计划有点被打乱,希望可以尽快脉动回来~ 今天给大家带来一篇自定义ViewGroup的教程,说白了,就是教大家如何自定义ViewGroup,如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,那么你可以好好看看这篇博客. 1.概述 在写代码之前,我必须得问几个问题: 1.ViewG

手把手教你用ViewPager自定义实现Banner轮播

手把手教你用ViewPager自定义实现Banner轮播 欢迎大家关注Android开源网络框架NoHttp:https://github.com/Y0LANDA/NoHttp 我们在实际开发中,很多App都会在做一个广告轮播器(可能是图片,可能是其他View),很多同学都是使用别人封装好的或者直接使用ViewPager自己来改,但是有人可能并不理解里面的原理,或者有人遇到了手势滑动冲突.我们今天就用150行代码实现一自定义的广告轮播器并不干扰原来View滑动事件. 本例代码源码及Demo传送门

Android--Android 手把手教您自定义ViewGroup(一)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38339817 , 本文出自:[张鸿洋的博客] 最近由于工作的变动,导致的博客的更新计划有点被打乱,希望可以尽快脉动回来~ 今天给大家带来一篇自定义ViewGroup的教程,说白了,就是教大家如何自定义ViewGroup,如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,那么你可以好好看看这篇博客 1.概述 在写代码之前,我必须得问几个问题: 1.ViewGr

手把手教你实现Spring ioc

手把手教你实现Spring ioc "Don't call us, we'll call you(不要联系我,我会主动联系你)" 这是好莱坞很经典的一句话,应用在ioc(控制反转)领域,发现理解起来相得益彰--你作为用户不需要控制业务实体的生成,交给我容器来控制,这就是控制反转.不过,这样理解起来也有点麻烦,套用面向对象大师Martin Fowler的说法更为贴切: "Dependency Inversion(依赖注入)" 当容器中所有的实体bean 都被管理起来的

Android消息推送:手把手教你集成小米推送

前言 在Android开发中,消息推送功能的使用非常常见. 为了降低开发成本,使用第三方推送是现今较为流行的解决方案. 今天,我将手把手教大家如何在你的应用里集成小米推送 该文档基于小米推送官方Demo,并给出简易推送Demo 看该文档前,请先阅读我写的另外两篇文章: 史上最全解析Android消息推送解决方案 Android推送:第三方消息推送平台详细解析 目录 1. 官方Demo解析 首先,我们先对小米官方的推送Demo进行解析. 请先到官网下载官方Demo和SDK说明文档 1.1 Demo

手把手教你写Sublime中的Snippet

手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜欢上的..Sublime Text 2使用心得 现在介绍一下Snippet, Snippets are smart templates that will insert text for you and adapt it to their context. Snippet 是插入到文本中的智能模板并

手把手教你清除WIN7的C盘垃圾

WIN7系统用着用着C盘会变得越来越大,可用空间变得越来越小,磁盘清理,和安全卫士怎么清也清不出这些系统深度的垃圾.我们可以手动删除,释放C盘空间. 这样一清理下来,结果我的C盘就释放了近10个GB的空间. 以下的方法针不能说对每一个人都有效,因为每个人在电脑上用的软件也不一样,如果你也用下面的这些软件那么可以和我一起试试. 1.休眠文件 hiberfil.sys : 该文件在C盘根目录为隐藏的系统文件,隐藏的这个hiberfil.sys文件大小正好和自己的物理内存是一致的,当你让电脑进入休眠状

极光IM如何使用,手把手教你自制IM应用

极光IM如何使用,现在让我来手把手教你一步一步自制IM应用.  STEP 1: 注册开发者账号,登陆极光portal.(极光IM) 拥有极光的开发者账号后,你就可以在极光Portal上创建并管理应用. STEP 2: 创建应用.(极光IM) 在我们的Web上创建应用.会要求你填写应用名(自定义)和包名(自定义,但很重要,后续配置工程中需要用到). 确认之后会自动生成一个AppKey. STEP 3: 导入Demo工程到开发环境.(极光IM)      以AndroidStudio的工程为例,像导