FCM 주제 메시징을 사용하면 게시/구독 모델을 기반으로 특정 주제를 구독하는 여러 기기에 메시지를 보낼 수 있습니다. 필요에 따라 주제 메시지를 작성하면 FCM에서 라우팅을 처리하여 올바른 기기에 정확히 전송합니다.
예를 들어 지역 조수 예보 앱 사용자는 '조류 특보' 주제를 구독하여 해당 지역에서 바다낚시에 최적인 상태에 관한 알림을 수신할 수 있습니다. 스포츠 앱 사용자는 응원하는 팀의 경기 실황 자동 업데이트를 구독할 수 있습니다.
주제와 관련된 주의사항은 다음과 같습니다.
- 주제 메시징은 날씨 등 공개적으로 제공되는 정보에 가장 적합합니다.
- 주제 메시지는 지연 시간이 아닌 처리량을 위주로 최적화됩니다. 기기 한 대나 적은 수의 기기에 빠르고 안전하게 전송하려면 주제가 아닌 등록 토큰으로 메시지를 타겟팅하세요.
- 사용자를 기준으로 여러 기기에 메시지를 보내야 하는 경우 기기 그룹 메시징을 사용하는 것이 좋습니다.
- 주제 메시징에서 각 주제에 지원하는 구독에는 제한이 없습니다. 그러나 FCM은 다음 영역에 제한을 두고 있습니다.
- 하나의 앱 인스턴스는 최대 2,000개까지 주제를 구독할 수 있습니다.
- 일괄 가져오기를 사용하여 앱 인스턴스를 구독하는 경우 각 요청은 1,000개의 앱 인스턴스로 제한됩니다.
- 새 구독의 빈도는 프로젝트별로 비율이 제한됩니다. 단기간에 지나치게 많은 구독 요청을 보내는 경우 FCM 서버는
429 RESOURCE_EXHAUSTED
('할당량 초과') 응답을 보냅니다. 지수 백오프로 다시 시도하세요.
클라이언트 앱에서 주제 구독
클라이언트 앱에서 기존 주제를 구독하거나 새 주제를 만들 수 있습니다. 클라이언트 앱에서 Firebase 프로젝트에 아직 없는 새 주제 이름을 구독하면 FCM에서 이 이름으로 새 주제가 만들어지고, 이후에 다른 클라이언트에서 그 주제를 구독할 수 있습니다.
주제를 구독하려면 애플리케이션의 기본 스레드에서 구독 메서드를 호출합니다. FCM은 스레드 안전을 지원하지 않습니다. 최초 구독 요청이 실패하면 FCM이 자동으로 다시 시도합니다. 구독을 완료할 수 없는 경우 구독에서 오류가 발생하며 아래 표시된 것처럼 완료 핸들러에서 확인할 수 있습니다.
Swift
Messaging.messaging().subscribe(toTopic: "weather") { error in print("Subscribed to weather topic") }
Objective-C
[[FIRMessaging messaging] subscribeToTopic:@"weather" completion:^(NSError * _Nullable error) { NSLog(@"Subscribed to weather topic"); }];
이 호출로 인해 FCM 백엔드로 비동기 요청이 전송되고 클라이언트가 지정된 주제를 구독하게 됩니다. subscribeToTopic:topic
을 호출하기 전에 클라이언트 앱 인스턴스에서 이미 didReceiveRegistrationToken
콜백을 통해 등록 토큰을 받은 상태인지 확인합니다.
앱이 시작될 때마다 FCM은 요청된 모든 주제가 구독되었는지 확인합니다. 구독을 취소하려면 unsubscribeFromTopic:topic
을 호출합니다. 그러면 FCM이 백그라운드에서 주제를 구독 취소합니다.
서버에서 주제 구독 관리
Firebase Admin SDK를 사용하면 서버 측에서 기본적인 주제 관리 작업을 수행할 수 있습니다. 등록 토큰을 알고 있으면 서버 로직을 사용하여 클라이언트 앱 인스턴스에서 일괄 구독하거나 구독 취소할 수 있습니다.
클라이언트 앱 인스턴스에서 기존 주제를 구독하거나 새 주제를 만들 수 있습니다. API를 사용하여 클라이언트 앱에서 Firebase 프로젝트에 아직 없는 새 주제를 구독하면 FCM에서 이 이름으로 새 주제가 만들어지고, 이후에 다른 클라이언트에서 그 주제를 구독할 수 있습니다.
이러한 등록 토큰 목록을 Firebase Admin SDK 구독 메서드에 전달하여 해당 기기에서 주제를 구독할 수 있습니다.
Node.js
// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// ...
'YOUR_REGISTRATION_TOKEN_n'
];
// Subscribe the devices corresponding to the registration tokens to the
// topic.
getMessaging().subscribeToTopic(registrationTokens, topic)
.then((response) => {
// See the MessagingTopicManagementResponse reference documentation
// for the contents of response.
console.log('Successfully subscribed to topic:', response);
})
.catch((error) => {
console.log('Error subscribing to topic:', error);
});
자바
// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
);
// Subscribe the devices corresponding to the registration tokens to the
// topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().subscribeToTopic(
registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were subscribed successfully");
Python
# These registration tokens come from the client FCM SDKs.
registration_tokens = [
'YOUR_REGISTRATION_TOKEN_1',
# ...
'YOUR_REGISTRATION_TOKEN_n',
]
# Subscribe the devices corresponding to the registration tokens to the
# topic.
response = messaging.subscribe_to_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were subscribed successfully')
Go
// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n",
}
// Subscribe the devices corresponding to the registration tokens to the
// topic.
response, err := client.SubscribeToTopic(ctx, registrationTokens, topic)
if err != nil {
log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were subscribed successfully")
C#
// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n",
};
// Subscribe the devices corresponding to the registration tokens to the
// topic
var response = await FirebaseMessaging.DefaultInstance.SubscribeToTopicAsync(
registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were subscribed successfully");
Admin FCM API를 사용하면 적합한 메서드에 등록 토큰을 전달하여 주제에서 기기를 구독 취소할 수 있습니다.
Node.js
// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// ...
'YOUR_REGISTRATION_TOKEN_n'
];
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
getMessaging().unsubscribeFromTopic(registrationTokens, topic)
.then((response) => {
// See the MessagingTopicManagementResponse reference documentation
// for the contents of response.
console.log('Successfully unsubscribed from topic:', response);
})
.catch((error) => {
console.log('Error unsubscribing from topic:', error);
});
자바
// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n"
);
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().unsubscribeFromTopic(
registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were unsubscribed successfully");
Python
# These registration tokens come from the client FCM SDKs.
registration_tokens = [
'YOUR_REGISTRATION_TOKEN_1',
# ...
'YOUR_REGISTRATION_TOKEN_n',
]
# Unubscribe the devices corresponding to the registration tokens from the
# topic.
response = messaging.unsubscribe_from_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were unsubscribed successfully')
Go
// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n",
}
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
response, err := client.UnsubscribeFromTopic(ctx, registrationTokens, topic)
if err != nil {
log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were unsubscribed successfully")
C#
// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
"YOUR_REGISTRATION_TOKEN_1",
// ...
"YOUR_REGISTRATION_TOKEN_n",
};
// Unsubscribe the devices corresponding to the registration tokens from the
// topic
var response = await FirebaseMessaging.DefaultInstance.UnsubscribeFromTopicAsync(
registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were unsubscribed successfully");
subscribeToTopic()
및 unsubscribeFromTopic()
메서드는 FCM의 응답을 포함하는 객체를 반환합니다. 반환 유형의 형식은 요청에 지정된 등록 토큰 수에 관계없이 동일합니다.
인증 실패, 잘못된 토큰이나 주제 등의 오류가 있는 경우 이들 메서드에 오류가 발생합니다. 설명 및 해결 단계가 포함된 전체 오류 코드 목록은 Admin FCM API 오류를 참조하세요.
주제 메시지 수신 및 처리
FCM은 다른 다운스트림 메시지와 동일한 방식으로 주제 메시지를 전송합니다.
다음과 같이 application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
을 구현합니다.
Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics // Messaging.messaging().appDidReceiveMessage(userInfo) // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } // Print full message. print(userInfo) return UIBackgroundFetchResult.newData }
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics // [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; // ... // Print full message. NSLog(@"%@", userInfo); completionHandler(UIBackgroundFetchResultNewData); }
전송 요청 작성
주제를 만든 후에는 클라이언트 측의 클라이언트 앱 인스턴스에서 주제를 구독하거나 서버 API를 통해 주제에 메시지를 전송할 수 있습니다. FCM용 전송 요청을 처음 작성하는 경우 서버 환경 및 FCM 가이드에서 중요한 배경 및 설정 정보를 참조하세요.
백엔드의 전송 로직에서 아래와 같이 원하는 주제 이름을 지정합니다.
Node.js
// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';
const message = {
data: {
score: '850',
time: '2:45'
},
topic: topic
};
// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setTopic(topic)
.build();
// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'
# See documentation on defining a message payload.
message = messaging.Message(
data={
'score': '850',
'time': '2:45',
},
topic=topic,
)
# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Topic: topic,
}
// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";
// See documentation on defining a message payload.
var message = new Message()
{
Data = new Dictionary<string, string>()
{
{ "score", "850" },
{ "time", "2:45" },
},
Topic = topic,
};
// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"topic" : "foo-bar",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"message": {
"topic" : "foo-bar",
"notification": {
"body": "This is a Firebase Cloud Messaging Topic Message!",
"title": "FCM Message"
}
}
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
여러 주제를 조합하여 메시지를 보내려면 대상 주제를 지정하는 불리언 표현식인 조건을 지정합니다. 예를 들어 다음 조건은 TopicA
와 함께 TopicB
또는 TopicC
를 구독하는 기기로 메시지를 전송합니다.
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM은 괄호 안의 조건부터 모두 판정한 후 왼쪽에서 오른쪽으로 표현식을 판정합니다. 위 표현식에서 주제를 하나만 구독한 사용자는 메시지를 수신하지 않습니다. TopicA
를 구독하지 않은 사용자도 메시지를 수신하지 않습니다. 다음과 같은 조합으로 구독해야 메시지를 수신합니다.
TopicA
,TopicB
TopicA
,TopicC
조건식에 최대 5개의 주제를 포함할 수 있습니다.
조건으로 보내는 방법은 다음과 같습니다.
Node.js
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';
// See documentation on defining a message payload.
const message = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
condition: condition
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("$GOOG up 1.43% on the day")
.setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
.build())
.setCondition(condition)
.build();
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"
# See documentation on defining a message payload.
message = messaging.Message(
notification=messaging.Notification(
title='$GOOG up 1.43% on the day',
body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
),
condition=condition,
)
# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Condition: condition,
}
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
var message = new Message()
{
Notification = new Notification()
{
Title = "$GOOG up 1.43% on the day",
Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
},
Condition = condition,
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"condition": "'dogs' in topics || 'cats' in topics",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message",
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"notification": {
"title": "FCM Message",
"body": "This is a Firebase Cloud Messaging Topic Message!",
},
"condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
다음 단계
- 여러 기기로 메시지를 보내는 다른 방법인 기기 그룹 메시징 자세히 알아보기