[FCM] Firebase Cloud Messaging

무1민·2023년 7월 29일
1

FCM

목록 보기
1/1

FCM

  • 무료로 전송할 수 있는 교차 플랫폼 메시징 솔루션
  • 모든 사용자에게 알림 메시지를 전송할 수도 있고, 그룹을 지어 메시지를 전송할 수도 있다.

사용하는 이유

FCM을 이용하면 플랫폼에 종속되지 않고 푸쉬 메시지를 전송할 수 있다.
iOS, Android, Web 각 플랫폼에서 푸쉬 메시지를 보내려면 각 플랫폼 환경별로 개발해야하는 불편함이 있는데 이를 하나로 통합하는 솔루션

이점

서버를 경유해서 실시간으로 푸쉬 메시지를 받으려면 사용자는 항상 서버에 접속해 있어야 하는데 이는 사용자 기기의 배터리 및 네트워크 리소스를 크게 낭비한다.
클라우드 메시징 서버를 중간에 둠으로써, 사용자는 낮은 배터리와 네트워크의 사용만으로도 메시지를 실시간으로 송수신 처리를 할 수 있다.
EX) A -> 어플리케이션 서버 -> 클라우드 메시징 서버 -> B

동작 원리

  • 크게 송신자, FCM Backend Server, 수신자로 구분한다.

  • 송신자는 주로 앱 서버, HTTP 프로토콜을 사용하는 서버, Firebase Console GUI 등이 될 수 있고, 수신자는 우리가 흔히 사용하는 iOS 또는 Android 운영체제를 사용하는 모바일 기기가 될 수 있다.

  • FCM Backend 서버는 실질적으로 앱 서버에서 요청을 받아서 메세지를 처리하는 서버에 해당된다.

  • FCM 클라우드 메시지의 흐름

    • HTTP 프로토콜을 사용할 경우, FCM 클라우드 메시지가 처리되는 과정을 그림으로 나타내보면 다음과 같다.
  • 앱 서버에서 FCM Backend 서버에 클라이언트 앱에 보내고자 하는 메시지를 담은 정보와, 서버의 인증 정보 클라이언트의 토큰을 담아서 HTTP POST를 요청 보낸다.

  • 요청을 받은 FCM Backend 서버는 요처을 통해 받은 메시지의 이상 유무에 따라 앱 서버에 적절한 응답을 보낸다.

  • 이후 FCM Backend 서버에서 여러가지(우선 순위, 클라이언트 앱과의 통신 가능 여부 등)을 고려하여 메세지를 클라이언트 앱에 보낸다.

  • 클라이언트 앱에서는 받은 메세지를 적절하게 처리하고 응답 메세지를 FCM Backend 서버로 보내게 된다.

    요약하면, 앱 서버에서 FCM Backend 서버에 메시지 요청을 보내고, FCM Backend 서버는 사용자 기기에서 실행되는 클라이언트 앱에 메시지를 보내게 된다.

  • 제대로 된 메시지만 만들어 주면 그것을 실제 기기로 전달하는 것은 FCM Backend에서 처리해줄 것이란 얘기다.

  • 단, 위의 플로우 대로 메시지를 전송하려면 반드시 선행되어야 하는 작업이 있는데, 메시지를 수신할 클라이언트는 자신의 정보를 FCM Backend 서버에 등록해야 한다.

    • 앱 서버는 등록된 정보를 획득해야 하며, 해당 정보로 다운 스트림 메시지를 전송한다.

FCM을 사용하기 위한 서버 환경 구성.

  • FCM을 사용하기 위해 FCM Backend 서버와 통신을 위한 어플리케이션 서버를 구축하기 위해선 다음 3가지 규칙을 지켜야 한다.

  • FCM Backend 서버에 FCM에서 지정한 형식의 메시지 요청을 보낼 수 있어야 한다.

  • 지수 백오프를 사용하여 요청을 처리하고 다시 보낼 수 있어야 한다.

    • 지수 백오프 : 요청이 실패할 때마다 다음 요청까지의 유휴시간 간격을 n배씩 늘리면서 재요청을 지연시키는 알고리즘이다.
      • 임의 지연을 사용하여 연쇄 충돌을 방지하기 위해서 사용한다.
  • 서버 승인 사용자의 인증 정보클라이언트의 등록 토큰을 안전하게 저장할 수 있어야 한다.

    • 서버 승인 사용자의 인증 정보 : 메시지를 보낼 앱 서버가 인증된 서버라는 것을 증명하는 정보
    • 클라이언트의 등록 토큰 : 메시지를 보내고자 하는 디바이스의 정보

Sample Code

  • 내가 작성한 샘플 코드의 경우, FCM을 서버단에서 사용할 경우의 샘플 코드이고, 언어 및 프레임워크는 Java와 Spring이다.

  • 초기 Dependency 설정. (Firebase Admin SDK 추가)

    • Firebase 의존성 추가

      <dependency>
        <groupId>com.google.firebase</groupId>
        <artifactId>firebase-admin</artifactId>
        <version>6.8.1</version>
      
      </dependency>
    • SDK 초기화

      • SDK 초기화를 진행할 때 Firebase 인증 처리를 해줘야 하는데, 먼저 Firebase 프로젝트 생성 후, 거기 설정에서 키를 발급받아야 한다.

      • 여기선, GCP의 서비스 계정 그룹 안에 Firebase가 이미 등록되어 있어서, GCP Credential로 인증 처리를 해주었다.

        private FirebaseApp createFirebaseApp() {
           FirebaseOptions options = null;
           ClassPathResource resource = 
               new ClassPathResource(gcpCredentialFile);
           try (InputStream is = resource.getInputStream()) {
               options = new FirebaseOptions.Builder()
                       .setCredentials(GoogleCredentials.fromStream(is))
                       .setDatabaseUrl("https://[My-Database-URL]
                                                   .firebaseio.com")
                       .build();
           } catch (IOException e) {
               log.error(e.getMessage());
           }
        
           return FirebaseApp.initializeApp(options);
        }
    • 특정 기기에 대한 메시지 전송

      private void sendToToken(Push push) throws FirebaseMessagingException {
         Message message = Message.builder()
                 .setNotification(Notification.builder()
                         .setTitle("[광고] 테스트 광고")
                         .setBody("테스트 내용")
                         .build())
                 // Device를 특정할 수 있는 토큰.
                 .setToken(push.getRegistrationToken())
                 .build();
      
         String response = FirebaseMessaging.getInstance().send(message);
         log.debug("Successfully sent message: " + response);
      }
    • 여러 기기에 대한 메세지 전송

        private void multipleSendToToken(Push push) 
                                       throws FirebaseMessagingException {
       List<String> tokenList = IntStream.rangeClosed(1, 30).mapToObj(index 
               -> push.getRegistrationToken()).collect(Collectors.toList());
      
       MulticastMessage message = MulticastMessage.builder()
               .setNotification(Notification.builder()
                   .setTitle("[많은 양의 메세지] 알람입니다.")
                   .setBody("많은 알람에 당황하지 마세요.")
                   .build())
               .addAllTokens(tokenList)
               .build();
                BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message);
                if (response.getFailureCount() > 0) {
                    List<SendResponse> responses = response.getResponses();
                    List<String> failedTokens = new ArrayList<>();
                    for (int i = 0; i < responses.size(); i++) {
                        if (!responses.get(i).isSuccessful()) {
                            failedTokens.add(tokenList.get(i));
                        }
                    }

                    System.out.println("List of tokens that caused failures: " + failedTokens);
                }
            }
  • 특정 주제 구독 및 주제 구독자에게 메세지 전송

    • 구독하고자 하는 주제가 존재하지 않는다면 FCM Backend에서 새롭게 생성해주고, 존재한다면 해당 주제에 구독처리가 된다.

      • subscribeToTopic 메소드를 unsubscribeFromTopic으로 변경하면 구독 취소도 FCM Backend에 요청할 수 있다.
      • 주제의 이름이 한글이면 Invalid topic name 에러
    • 한 기기가 여러 주제에 구독했다면 구독한 주제별로 보내는 메세지를 모두 받게 된다.

        private void sendSubscribeTopic(Push push) 
                                    throws FirebaseMessagingException {
        List<String> registrationTokens = 
                    Collections.singletonList(push.getRegistrationToken());
      
        TopicManagementResponse response = FirebaseMessaging.getInstance()
                .subscribeToTopic(Collections.singletonList
                            (push.getRegistrationToken()), "Apple News");
      
        System.out.println(response.getSuccessCount() + 
                    " tokens were subscribed successfully");
      
        Message message = Message.builder()
                .setNotification(Notification.builder()
                        .setTitle("제목")
                        .setBody("내용")
                        .build())
                .setTopic("Apple News")
                .build();
      
        String response2 = FirebaseMessaging.getInstance().send(message);
        log.debug("Successfully sent message: " + response2);
      }
  • 일괄 메시지 전송

    • 하나의 메시지를 많은 사람들에게 보낼 수 있을 뿐만 아니라, 다양한 메시지를 사람들에게 일괄 전송하는 것도 가능

        private void multipleEachOtherSend(Push push) 
                            throws FirebaseMessagingException {
        List<Message> messages = Arrays.asList(
                Message.builder()
                        .setNotification(Notification.builder()
                                .setTitle("첫 번째 제목")
                                .setBody("첫 번째 내용")
                                .build())
                        .setToken(push.getRegistrationToken())
                        .build(),
                Message.builder()
                        .setNotification(Notification.builder()
                                .setTitle("두 번째 제목")
                                .setBody("두 번째 내용")
                                .build())
                        .setTopic("토픽")
                        .build()
        );
      
        BatchResponse response = FirebaseMessaging.getInstance()
                                                    .sendAll(messages);
        System.out.println(response.getSuccessCount() + 
                                    " messages were sent successfully");
      }
profile
야호

1개의 댓글

comment-user-thumbnail
2023년 7월 29일

감사합니다. 이런 정보를 나눠주셔서 좋아요.

답글 달기