詳解APNS蘋果消息推送通知

詳解APNS蘋果消息推送通知

時間:2015-06-20 09:18:13      閱讀:1149      評論:0      收藏:0      [點我收藏+]java

標籤:蘋果   apns   ios   push   app   ios

蘋果消息通知什麼?

以下圖如示git

技術分享

就是APP的服務端在用戶未打開APP進程時,還能發通知給用戶APP的服務技術github

相關概念

Provider:提供推送的第三方服務系統安全

Device:蘋果設備,例如iphone和ipad等app

APNS:蘋果推送消息服務,屬於蘋果的服務iphone

APP:安裝在蘋果設備上的應用程序ide

DeviceToken:設備的標識,用於肯定接收通知的設備及APP網站

Payload:推送消息的傳輸形式ui

整體示意圖

技術分享

從上圖能夠看出Provider與APNS之間是要創建鏈接的,APNS和Device之間也是要創建鏈接的,這兩個鏈接都是加密的,採用的TLS的方式,加密用的證書是在蘋果官方網站上購買生成的,這個證書分兩種,一種是「開發者證書」,使用開發者證書的APP能夠發佈在APPSTORE上面,開發者證書的使用費是每一年99美圓,另外一種是「企業證書」,使用企業證書的APP只能發在企業內部市場,不能發在APPSTORE上,企業證書的年費是299美圓,企業證書對安裝人數是沒有限制的,但開發者證書對APP的安裝人數是有限制的,但從APPSTORE上安裝則沒有限制。

安全鏈接的創建方式如圖:

技術分享

若是獲取肯定接收者的DeviceToken?

Provider如何取得DeviceToken呢?具體如圖所示:

技術分享


獲取是由APP經過註冊服務後取得DeviceToken,而後傳給Provider。因爲這個DeviceToken是會變的,因此最好是APP在每次啓動時都能註冊一下,獲取最新的DeviceToken

IOS下的註冊獲取DeviceToken的代碼官方給出以下:

- (void)applicationDidFinishLaunching:(UIApplication *)app {// other setup tasks here....
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];
}
// Delegation methods
- (void)application:(UIApplication *)app
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const void *devTokenBytes = [devToken bytes];
self.registered = YES;
[self sendProviderDeviceToken:devTokenBytes]; // custom method
}
- (void)application:(UIApplication *)app
didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}

如何發送通知消息?

Provider和APNS之間是一個安全的Socket鏈接,傳輸的是規定格式的二進制數據,以下形式

技術分享

固然,這個就是TCP報文,然而具體的報文又有好幾種,有發送普通消息的傳輸形式,有獲取錯誤響應的傳輸形式,還有獲取反饋的報文形式等,對於普通的發消息的報文格式如圖如示:

技術分享


最前面1個字節是命令類型,2-3字節是DeviceToken的長度,後面是DeviceToken的具體值,後面是消息的長度和消息的具體內容。發完消息後,在關閉Socket前還能夠查看錯誤的響應,以便確認消息是否成功地發給APNS,錯誤響應的報文以下:

技術分享

命令爲8的表示是錯誤響應的報文,Status是錯誤碼,Identifier是用於定位具體的哪條推送消息,這個是加強型通知裏的傳入的值,首先看下蘋果都有哪些返回錯誤碼:

技術分享

再看下Identifier傳入使用的加強型的通知報文格式:

技術分享


官方給出發送普通消息的C++代碼以下:

static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength){
    bool rtn = false;
    if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength){
	uint8_t command = 0; /* command number */
	char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint16_t) +
	DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
	/* message format is, |COMMAND|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD| */
	char *binaryMessagePt = binaryMessageBuff;
	uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
	uint16_t networkOrderPayloadLength = htons(payloadLength);
	/* command */
	*binaryMessagePt++ = command;
	/* token length network order */
	memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
	binaryMessagePt += sizeof(uint16_t);
	/* device token */
	memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
	binaryMessagePt += DEVICE_BINARY_SIZE;
	/* payload length network order */
	memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
	binaryMessagePt += sizeof(uint16_t);
	/* payload */
	memcpy(binaryMessagePt, payloadBuff, payloadLength);
	binaryMessagePt += payloadLength;
	if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt - binaryMessageBuff)) > 0)
            rtn = true;
    }
    return rtn;
}

發送加強的通知消息的代碼:

static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength){
    bool rtn = false;
    if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength){
	uint8_t command = 1; /* command number */
	char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint16_t) + DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
	/* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|*/
	char *binaryMessagePt = binaryMessageBuff;
	uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234;
	uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); // expire
	message if not delivered in 1 day
	uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
	uint16_t networkOrderPayloadLength = htons(payloadLength);
	/* command */
	*binaryMessagePt++ = command;
	/* provider preference ordered ID */
	memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID,
	sizeof(uint32_t));
	binaryMessagePt += sizeof(uint32_t);
	/* expiry date network order */
	memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t));
	binaryMessagePt += sizeof(uint32_t);
	/* token length network order */
	memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
	binaryMessagePt += sizeof(uint16_t);
	/* device token */
	memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
	binaryMessagePt += DEVICE_BINARY_SIZE;
	/* payload length network order */
	memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
	binaryMessagePt += sizeof(uint16_t);
	/* payload */
	memcpy(binaryMessagePt, payloadBuff, payloadLength);
	binaryMessagePt += payloadLength;
	if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt - binaryMessageBuff)) > 0)
	    rtn = true;
  }
  return rtn;
}

角標(Badge)是什麼?

還有一個重要的概念是角標,以下:

技術分享

這個角標是推送消息裏傳的,能夠指定,這個要看具體的業務怎麼定了,用於提醒用戶未讀消息個數。

結尾

瞭解了以上原理和過程,相信用什麼語言均可以寫Provider程序了,本人基於java語言也簡單寫了個demo,能夠支持多個應用的推送,等完善了就能夠放github了

相關文章
相關標籤/搜索