검색결과 리스트
글
푸시 메시지 포맷
아이폰
2011. 4. 7. 23:39
Provider와 APNS 간에 SSL(Secure Socket Layer)를 통해 통신을 하며, 서로 간에 올바른 통신을 위한 몇 가지
요구사항이 존재한다. 다음은 Local, Remote Notification Programming Guide 문서에서 일부 발췌한 내용이다.
자세한 내용은 실제 문서를 참고하자.
요구사항이 존재한다. 다음은 Local, Remote Notification Programming Guide 문서에서 일부 발췌한 내용이다.
자세한 내용은 실제 문서를 참고하자.
1. Provider가 APNS와 통신하기 위한 요구사항
〮 Provider는 Apple Push Notification Service와 바이너리 인터페이스를 사용해 통신함
〮 TCP Socket을 사용하며, 안전한 통신채널을 위해 TLS(또는 SSL)을 사용함
〮 APNS와의 잦은 연결과 종료는 DOS 공격으로 오인받을 수 있음
〮 에러가 발생하면 APNS는 해당연결을 종료함
〮 서버에 접속해서 반복적으로 푸시전송에 실패한 클라이언트 디바이스 리스트를 조회할 수 있음 => Feedback service
2. Binary Interface and Notification Formats
〮순수 TCP 소켓을 사용하며, 최적의 성능을 위해서는 단일 연결을 맺고, 다중 통지를 보내야함
〮인터페이스는 simple 포맷, enhanced 포맷 2가지의 notification format을 지원함
〮Simple 포맷에서는 payload 데이터가 제한크기를 초과하면 통지를 거부하지만, enhanced 포맷에서는
notification 에 임의의 식별자를 할당하여, 에러가 발생하면 식별자와 연관된 에러코드를 확인할 수 있음
위에서 언급된 것처럼 2가지 종류의 메시지 형태가 존재한다. 대부분의 앱에서는 Simple 포맷으로도 충분하다. 하지만
Simple 포맷은 실제로 푸시 메시지가 전송됐는지를 보장하지 않기 때문에, 푸시 메시지에 대한 전송 신뢰성이 보장되야 할경우엔는
Enhanced 포맷을 사용하자. Enhanced 포맷에서는 메시지 전송 실패시 에러를 반환하기 때문에, Provider가 주기적으로 APNS에서
이 값을 체크하여 실패시 다시 전송을 요청할 수 있다.
Simple 포맷은 실제로 푸시 메시지가 전송됐는지를 보장하지 않기 때문에, 푸시 메시지에 대한 전송 신뢰성이 보장되야 할경우엔는
Enhanced 포맷을 사용하자. Enhanced 포맷에서는 메시지 전송 실패시 에러를 반환하기 때문에, Provider가 주기적으로 APNS에서
이 값을 체크하여 실패시 다시 전송을 요청할 수 있다.
3. Simple Format
APNS와 SSL 연결과, peer-exchage 인증을 가정한다. (SSL을 사용한 1:1 통신을 한다는 의미같다.)
메시지 포맷은 다음과 같다.

Command : Simple 포맷인지 Enhanced 포맷인지 구별하기 위한 값. simple 포맷은 0을 갖는다.
Token length : DeviceToken의 길이
Device Token : 바이너리 포맷으로 인코딩 되야함
Payload length : 푸시로 전달할 메시지의 길이로 256 바이트를 초과할 수 없으며,
널(null)문자로 종료해서는 안됨
* token length와 payload length 는 네트워크 오더(big endian)이어야 함
3.1 SimpleFormat을 사용하는 예제코드
Local, Remote Notification Programming Guide 문서에 나와있는 샘플 코드이다. 실제 메시지 전송을 구현해야할 경우 참고하자
Local, Remote Notification Programming Guide 문서에 나와있는 샘플 코드이다. 실제 메시지 전송을 구현해야할 경우 참고하자
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;
}
4. Enhanced Format
Enhanced Format 를 사용하여 메시지를 전송하면, APNS가 인지 불가능한 명령을 만났을 경우 연결을 종료하기 전에 에러 응답을
반환해주어 에러의 원인을 확인할 수 있다. Provider 에서는 주기적으로 APNS의 에러응답에 접근하여 결과를 확인할 수 있다.
반환해주어 에러의 원인을 확인할 수 있다. Provider 에서는 주기적으로 APNS의 에러응답에 접근하여 결과를 확인할 수 있다.
first byte: 1
identifier: notification을 식별하기 위한 임의의 값. 에러가 발생하면 APNS는 이값을 반환함
expiry : notification이 더이상 유효하지 않는 때를 나타내는 초로 표현된 시간. fixed UNIX epoch date(UTC)
...나머지 필드는 Simple Format과 동일하다.
4.1 Enhanced Format을 사용하는 예제코드
역시 실제 구현시에 참고하자.
역시 실제 구현시에 참고하자.
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;
}5. Error 응답 포맷
notification 포맷을 인식할 수 없을 경우 APNS는 연결을 종료하기 전에 error 응답을 전송해준다. error가 없다면 어떠한 값도
반환하지 않는다. 에러 응답의 포맷은 다음과 같다.
반환하지 않는다. 에러 응답의 포맷은 다음과 같다.
first byte: 8
status : 상태코드
identifier : notification을 구성할 때 사용한 식별자
상태코드
0 : 에러없음
1 : 에러 처리중
2 : device token 이 없음
3 : topic이 없음
4 : payload 없음
5 : 잘못된 token 크기
6 : 잘못된 topic 크기
7 : 잘못된 payload 크기
8 : 잘못된 token
255 : 알수없음
'아이폰' 카테고리의 다른 글
| 수동으로 UI 컨트롤에 이벤트 전달 (0) | 2011.11.13 |
|---|---|
| NSNotification (1) | 2011.04.29 |
| 푸시 메시지 포맷 (0) | 2011.04.07 |
| 애플 푸시 서비스 (0) | 2011.04.07 |
| Sprite Sheet 제작툴 Zwoptex (0) | 2011.03.16 |
| 애플리케이션 응답성 향상을 위해 동시성 사용하기 (0) | 2011.03.15 |
설정
트랙백
댓글
글
애플 푸시 서비스
아이폰
2011. 4. 7. 23:01
이 포스팅에서는 애플 푸시 서비스를 사용하는 방법에 대해서 알아보자. 작년 겨울 앱공모전을 준비하면서 "분실물 다나와"
라는 앱을 개발했다. 서울 분실물 센터에 수거되어 있는 분실물 목록을 조회할 수 있고, 등록된 분실물과 유사한 분신물 목록을
사용자 아이폰으로 알리는 앱이었다. 이때 유사 분실물 발견유무를 푸시 기능을 통해 구현하였다. 당시에 푸시와 관련된
자료가 별로 없어서(내가 못찾았겠지만...) 애플 개발자 가이드 문서를 참고하여 구현하였다. 그 때 정리한 내용을 올려본다.
(사진 캡처를 안해서 이해가 잘 안될 수도....-_-;; )
라는 앱을 개발했다. 서울 분실물 센터에 수거되어 있는 분실물 목록을 조회할 수 있고, 등록된 분실물과 유사한 분신물 목록을
사용자 아이폰으로 알리는 앱이었다. 이때 유사 분실물 발견유무를 푸시 기능을 통해 구현하였다. 당시에 푸시와 관련된
자료가 별로 없어서(내가 못찾았겠지만...) 애플 개발자 가이드 문서를 참고하여 구현하였다. 그 때 정리한 내용을 올려본다.
(사진 캡처를 안해서 이해가 잘 안될 수도....-_-;; )
1. Push Service 란?
서버에서 아이폰 애플리케이션으로 데이터를 역으로 전송할 수 있는 서비스이다.
Remote Notification을 등록하고 수신하는 애플리케이션과 푸시 서비스를 제공하는 APNS(Apple Push Notification Service),
APNS에 데이터 푸시요청을 수행할 수 있는 권한을 획득한 Provider(서버) 로 구성된다.
"분실물 다나와" 프로젝트로 예를 들면...Provider는 "분실물 다나와" 서버가 되고, Client App 은 "분실물 다나와" 아이폰 앱,
APNS는 애플에서 제공하는 푸시서버가 된다. iPhone에 전송된 푸시메시지는 iOS 를 거쳐 실제 앱에 전달된다.
2. Push 서비스 사용절차
APNS는 실제 아이폰 기기로 푸시를 전송해주는 애플측에서 제공하는 서비스이다. 푸시서비스를 이용하는 서버인 Provider는
푸시할 데이터만을 가공하여 APNS에 푸시를 요청한다. Provider가 APNS에 접속하여 푸시데이터를 전송하려 할 때, APNS는
푸시요청을 할수 있는 권한을 SSL 인증을 통해 검사하게 된다. 그래서 Provider는 사전에 이 인증을 위한 암호화 파일을 사전에
푸시할 데이터만을 가공하여 APNS에 푸시를 요청한다. Provider가 APNS에 접속하여 푸시데이터를 전송하려 할 때, APNS는
푸시요청을 할수 있는 권한을 SSL 인증을 통해 검사하게 된다. 그래서 Provider는 사전에 이 인증을 위한 암호화 파일을 사전에
가지고 있어야 한다.
① 개발자 포탈에서 애플리케이션에 대한 Push Serivce 사용권한 받기
개발자 포탈에서 App Id에 대한 Development 또는 Product 푸시 서비스 옵션을 enable 시키고, 다운받은 인증파일을 KeyCain에
등록시킨다. 이 파일을 p12 파일로 export 하여, 파일, 비밀번호와 함께 Provider 에게 사전에 전달해야 한다.
등록시킨다. 이 파일을 p12 파일로 export 하여, 파일, 비밀번호와 함께 Provider 에게 사전에 전달해야 한다.
② 아이폰 애플리케이션 실행시 APNS에 RemoteNotification 등록
아이폰 애플리케이션이 처음 구동시 APNS에 RemoteNotification 을 등록하고, 애플리케이션 DeviceToken을 수신받는다.
③ Provider에게 DeviceToken 전송
DeviceToken을 수신하면, 서버에 아이폰 UUID와 함께 전달한다. 이 토큰은 APNS에서 아이폰의 애플리케이션을 식별하는 키가
되기 때문에 Provider가 보유하고 있어야 하는 데이터이다. DeviceToken은 거의 변하지 않지만, 애플리케이션이 삭제되고 다시
설치될 때 변할 수도 있기 때문에 애플리케이션이 실행될 때마다, Provider에게 전송하여 갱신여부를 확인하는 것이 좋다. Development용과 Product용의 DeviceToken 값은 다르다는 것에 주의한다.
되기 때문에 Provider가 보유하고 있어야 하는 데이터이다. DeviceToken은 거의 변하지 않지만, 애플리케이션이 삭제되고 다시
설치될 때 변할 수도 있기 때문에 애플리케이션이 실행될 때마다, Provider에게 전송하여 갱신여부를 확인하는 것이 좋다. Development용과 Product용의 DeviceToken 값은 다르다는 것에 주의한다.
④ Provider 에서 푸시 메시지를 생성하여 APNS에 푸시 요청
Provider 에서는 ①단계에서 전달받은 인증파일을 보유하고 있고, 인증파일 비밀번호를 알고 있어야 한다.
DeviceToken와 함께 다음의 주소로 push를 요청할 수 있다. 개발환경과 실제 서비스를 위한 APNS 의 주소가 다르다.
DeviceToken와 함께 다음의 주소로 push를 요청할 수 있다. 개발환경과 실제 서비스를 위한 APNS 의 주소가 다르다.
개발환경 푸시서버 주소: gateway.sandbox.push.apple.com / 2195
제품환경 푸시서버 주소: gateway.push.apple.com / 2195
⑤ APNS 에 전송하는 메시지의 포맷
메시지는 JSON 포맷을 따르며 256바이트 이내야한다. 메시지를 구성하는 키는 다음과 같다.
단순한 메시지 포맷은 위와 같으며, 좀더 세부적으로 메시지를 지정할 수도 있다. 더 자세한 사항은
Local, Remote Notification Programming Guide 를 참고하자.
Local, Remote Notification Programming Guide 를 참고하자.
JSON 형태로 생성된 메시지 형태는 다음과 같다.
{"aps":{"sound":"default","alert":"My alert message","badge":45}}
⑥ APNS에 푸시 요청하기
Provider는 APNS가 요구하는 프로토콜을 따라 데이터를 전송해야 한다. 프로토콜은 APNS에 전송하는 Push 메시지 포맷을
참고하자. 이 메시지 포맷과 APNS 의 요구사항에 맞게 구현한 javapns라는 오픈소스 라이브러리를 사용하여 APNS에 푸시를
요청해보자
참고하자. 이 메시지 포맷과 APNS 의 요구사항에 맞게 구현한 javapns라는 오픈소스 라이브러리를 사용하여 APNS에 푸시를
요청해보자
javapns를 사용하기 위해서 아래의 의존 라이브러리를 가지고 있어야한다.
commons-lang-2.4.jar // String클래스 등의 유틸리티 기능을 제
commons-io-1.4.ja
bcprov-jdk16-145.jar // Bouncy Castle 단체의 암호화 관련 api
log4j-1.2.15.ja
javapns 오픈소스 라이브러리 : http://code.google.com/p/javapns/
Legion of the Bouncy Castle 사이트 : http://www.bouncycastle.org/java.html
다음은 javapns 라이브러리를 사용하여 Provider 에서 APNS 에 푸시를 요청하는 예이다.
String password = "1q2w3e4r";
String certicatePath = "/home/CertificateSR/Interesting_Dev_Cert_pass.p12";
String apnsAddress = "gateway.sandbox.push.apple.com";
String apnsPort = "2195";
try {
PayLoad payload = new PayLoad();
payload.addAlert(pushMessage.getAlert());
payload.addBadge(pushMessage.getBadge());
payload.addSound(pushMessage.getSound());
HashMap customMap = (HashMap) pushMessage.getCustomData();
Iterator iterator = customMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
payload.addCustomDictionary(key, customMap.get(key));
}
PushNotificationManager manager = PushNotificationManager.getInstance();
manager.addDevice("myIphone", pushMessage.getDeviceToken());
Device device = PushNotificationManager.getInstance().getDevice("myIphone");
manager.initializeConnection( apnsAddress,
Integer.parseInt(apnsPort),
certicatePath,
password,
SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
manager.sendNotification(device, payload);
manager.stopConnection();
manager.removeDevice("myIphone");
} catch (Exception e) {
e.printStackTrace();
}
⑦ 애플리케이션측에서 Push를 수신하기 위해서 UIApplicationDelegate 프로토콜의 메소드를 구현
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 배지넘버 초기화
application.applicationIconBadgeNumber = 0;
NSDictionary *aps =
[launchOptionsobjectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
// 애플리케이션을 처음실행 : RemoteNotification을 등록함
if (aps == nil) {
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeBadge)
];
} else {
// 애플리케이션이 원격 통보에 의해 실행됐음
// alert 추출
NSString *alert = [aps objectForKey:@"alert"];
// custom 데이터 추출
NSString *pushSeq = [userInfo objectForKey:@"pushSeq"];
}
return YES;
}
// RemoteNotification 등록 성공. deviceToken을 수신
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Provider에게 DeviceToken 전송
//[service registDeviceToken:[deviceToken description]];
}
// APNS 에 RemoteNotification 등록 실패
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"fail RemoteNotification Registration: %@", [error description]);
}
// 애플리케이션 실행 중에 RemoteNotification 을 수신
(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// push 메시지 추출
NSDictionary *aps = [userInfo objectForKey:@"aps"];
// alert 추출
NSString *alert = [aps objectForKey:@"alert"];
// custom 데이터 추출
NSString *pushSeq = [userInfo objectForKey:@"pushSeq"];
}
RemoteNotification이 성공적으로 등록되면, 응답으로 호출되는 didRegistForRemoteNotificationWithDeviceToken 메시지의 파라미터에
DeviceToken 값이 담겨져 온다. 이 Token의 값은 다음과 같은 형태를 갖는다. <12345678 12345678 12345678 12345678 50cfb1 3f5febbe497df>
APNS에 전송하기 하기 위해서는 애플리케이션 또는 Provider 측에서 <, >, 공백문자를 제거해야 한다는 것을 잊지 말자!!
지금까지 애플 푸시서비스에 대해 알아보았다. 사실 푸시 서비스를 이용하는 과정은 그렇게 어렵지 않다. 오히려 개발자 포탈에서
푸시 서비스 권한을 설정하고, 키체인에서 인증파일을 생성하는 과정이 번거롭고... 푸시를 받을 앱이 실행중일 때와 실행중이지
않을 때 푸시 수신 시 동작방식을 달리 처리해줘야 하는 것이 조금 번거로울 뿐이다. 다음 포스팅에서는 실제 푸시메시지 포맷에
대해서 좀더 자세히 알아보자.
푸시 서비스 권한을 설정하고, 키체인에서 인증파일을 생성하는 과정이 번거롭고... 푸시를 받을 앱이 실행중일 때와 실행중이지
않을 때 푸시 수신 시 동작방식을 달리 처리해줘야 하는 것이 조금 번거로울 뿐이다. 다음 포스팅에서는 실제 푸시메시지 포맷에
대해서 좀더 자세히 알아보자.
'아이폰' 카테고리의 다른 글
| NSNotification (1) | 2011.04.29 |
|---|---|
| 푸시 메시지 포맷 (0) | 2011.04.07 |
| 애플 푸시 서비스 (0) | 2011.04.07 |
| Sprite Sheet 제작툴 Zwoptex (0) | 2011.03.16 |
| 애플리케이션 응답성 향상을 위해 동시성 사용하기 (0) | 2011.03.15 |
| OpenGL ES 게임 프레임웍 (0) | 2011.02.14 |