안드로이드 및 스프링 부트를 사용한 자바 웹소켓 프로그래밍

PubNub Developer Relations - Jan 17 - - Dev Community

웹소켓 프로토콜은 양방향 통신을 위해 클라이언트와 서버 간의 상시 연결을 제공합니다. 이는 멀티플레이어 게임, 사물 인터넷 애플리케이션, 채팅 앱과 같이 실시간 연결이 필요한 애플리케이션에 유용합니다. 이 튜토리얼의 사용 사례는 실시간 통신의 대표적인 예인 채팅 애플리케이션입니다. 이 튜토리얼에서는 스프링 부트를 사용하여 웹소켓 서버에 연결할 간단한 안드로이드 클라이언트를 설정하겠습니다.

자바 웹소켓 클라이언트

안드로이드 클라이언트의 경우 귀여운 동물 이미지 버튼 4개가 포함된 간단한 데모 앱을 만들어 보겠습니다. Java 구현에 들어가기 전에 Kotlin이 Android 개발에 널리 사용되고 있다는 점에 주목할 필요가 있습니다. 시작하려면 Android Studio에서 기본 액티비티인 JavaWebSocketClient를 사용하여 새 프로젝트를 초기화합니다. 이 앱에는 이 GitHub 저장소에서 찾을 수 있는 경량 WebSocket 클라이언트 라이브러리를 사용하겠습니다. 이 라이브러리를 사용하려면 앱 디렉토리의 build.gradle 파일에 추가해야 합니다. 종속성에 다음을 추가하고 프로젝트를 동기화합니다:

dependencies { 
    // Add this
    implementation 'tech.gusavila92:java-android-websocket-client:<latest-version>'    
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ...
}
Enter fullscreen mode Exit fullscreen mode

매니페스트 파일에 인터넷 액세스 권한을 포함해야 합니다:

<uses-permission android:name="android.permission.INTERNET" />
Enter fullscreen mode Exit fullscreen mode

클라이언트를 서버에 연결

MainActivity.java로 이동하여 다음 패키지를 가져와서 onCreate()를 설정합니다:

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import java.net.URI;
import java.net.URISyntaxException;
import tech.gusavila92.websocketclient.WebSocketClient;

public class MainActivity extends AppCompatActivity {
  private WebSocketClient webSocketClient;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.animal_sounds);
    createWebSocketClient();
  }
}
Enter fullscreen mode Exit fullscreen mode

그런 다음, createWebSocketClient() 메서드를 새로 만듭니다:

private void createWebSocketClient() {
    URI uri;
    try {
      // Connect to local host
      uri = new URI("ws://10.0.2.2:8080/websocket");
    }
    catch (URISyntaxException e) {
      e.printStackTrace();
      return;
    }

    webSocketClient = new WebSocketClient(uri) {
      @Override
      public void onOpen() {
        Log.i("WebSocket", "Session is starting");
        webSocketClient.send("Hello World!");
      }

      @Override
      public void onTextReceived(String s) {
        Log.i("WebSocket", "Message received");
        final String message = s;
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            try{
              TextView textView = findViewById(R.id.animalSound);
              textView.setText(message);
            } catch (Exception e){
                e.printStackTrace();
            }
          }
        });
      }

      @Override
      public void onBinaryReceived(byte[] data) {
      }

      @Override
      public void onPingReceived(byte[] data) {
      }

      @Override
      public void onPongReceived(byte[] data) {
      }

      @Override
      public void onException(Exception e) {
        Log.e("WebSocket", e.getMessage());
      }

      @Override
      public void onCloseReceived() {
        Log.i("WebSocket", "Closed ");
      }
    };

    webSocketClient.setConnectTimeout(10000);
    webSocketClient.setReadTimeout(60000);
    webSocketClient.enableAutomaticReconnection(5000);
    webSocketClient.connect();
  }
Enter fullscreen mode Exit fullscreen mode

많은 작업이 필요해 보이지만 실제로는 이 메서드에서 네 가지 핵심 작업을 수행합니다:

  1. 로컬 호스트 "ws://10.0.2.2:8080/websocket"에 대한 새 WebSocket 연결을 시작합니다.

  2. 연결이 열리면 서버에 메시지를 보냅니다.

  3. 서버에서 보낸 메시지를 앱에 표시합니다.

  4. 시간 초과 및 자동 재연결 설정.

이제 클라이언트를 서버에 연결했으니 서버로 메시지를 보내는 방법을 설정해 보겠습니다.

서버에 메시지 보내기

MainActivity.java에서 sendMessage()에 다음을 추가합니다:

public void sendMessage(View view) {
   Log.i("WebSocket", "Button was clicked");

   // Send button id string to WebSocket Server
   switch(view.getId()){
     case(R.id.dogButton):
       webSocketClient.send("1");
       break;

     case(R.id.catButton):
       webSocketClient.send("2");
       break;

     case(R.id.pigButton):
       webSocketClient.send("3");
       break;

     case(R.id.foxButton):
       webSocketClient.send("4");
       break;
   }
 }
Enter fullscreen mode Exit fullscreen mode

버튼을 누르면 버튼 ID가 서버로 전송됩니다. 이 메서드는 내 Java 웹소켓 프로그래밍 리포지토리에서 얻을 수 있는 animal_sounds.xml 파일에서 호출됩니다. 값이 있는 디렉터리 아래에 있는 strings.xml 파일을 수정해야 XML 파일에 오류가 발생하지 않습니다.

클라이언트 측에서 마지막으로 해야 할 일은 드로어블 디렉토리에 동물 이미지를 추가하는 것입니다. 이미지는 Freepik(www.flaticon.com) 에서 만든 것입니다.

에뮬레이터에서 안드로이드 앱을 실행합니다:

서버가 설정되지 않았기 때문에 지금은 아무 일도 일어나지 않습니다. 지금 바로 설정해 봅시다!

스프링 부트 웹소켓 서버

서버에는 최소한의 구성으로 프로덕션급 Spring 애플리케이션을 쉽게 만들 수 있는 Spring Boot를 사용하겠습니다. 프로젝트를 빠르게 부트스트랩하기 위해 스프링 이니셜라이저를 사용하겠습니다. Gradle 프로젝트를 생성하지만 원하는 경우 Maven 프로젝트를 생성할 수도 있습니다. 아래 스크린샷과 같이 Initializr를 구성하고 WebSocket을 종속 요소로 추가하세요:

프로젝트를 생성하여 zip 파일을 다운로드합니다. 파일의 압축을 풀고 src 디렉토리로 이동한 후 JavaWebSocketServerApplication.java 파일이 나올 때까지 하위 디렉터리를 계속 클릭합니다.

package com.example.javawebsocketserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JavaWebSocketServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(JavawebsocketserverApplication.class, args);
  }
}
Enter fullscreen mode Exit fullscreen mode

WebSocketHandler.java와 WebSocketConfiguration.java 디렉토리에 두 개의 파일을 추가합니다.

웹소켓 메시지 처리

서버에 도착하는 수신 메시지를 처리해야 합니다. 이를 위해 WebSocketHandler.java에서 핸들 텍스트 메시지() 메서드를 구현하는 클래스를 상속합니다. 파일에 다음 코드를 추가합니다:

package com.server.javawebsocketserver;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import java.io.IOException;

public class WebSocketHandler extends AbstractWebSocketHandler {
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
        String msg = String.valueOf(message.getPayload());
        // Send back a unique message depending on the id received from the client
        switch(msg){
            case("1"):
                Log.i("WebSocket", "Dog button was pressed");
                session.sendMessage(new TextMessage("Woooof"));
                break;

            case("2"):
                Log.i("WebSocket", "Cat button was pressed");
                session.sendMessage(new TextMessage("Meooow"));
                break;

            case("3"):
                Log.i("WebSocket", "Pig button was pressed");
                session.sendMessage(new TextMessage("Bork Bork"));
                break;

            case("4"):
                Log.i("WebSocket", "Fox button was pressed");
                session.sendMessage(new TextMessage("Fraka-kaka-kaka"));
                break;

            default:
                Log.i("WebSocket", "Connected to Client");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

메서드 내에서 메시지 페이로드의 문자열 값을 가져와서 스위치 표현식을 사용하여 각 대소문자 값으로 메시지 값을 확인합니다. 동물 소리가 포함된 고유한 메시지가 클라이언트로 전송됩니다.

웹소켓 요청 처리 구성

WebSocketConfiguration.java에서 WebSocketConfigurer 인터페이스를 구현하고 파일에 다음 코드를 추가합니다:

package com.server.javawebsocketserver;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new WebSocketHandler(), "/websocket");
    }
}
Enter fullscreen mode Exit fullscreen mode

등록 웹소켓 핸들러 메서드를 설정하여 웹소켓 핸들러를 "/websocket" 경로로 구성합니다. 여기까지가 서버 측의 설정입니다. 이제 모든 설정이 완료되었으니 WebSocket 서버를 시작하고 앱을 실행해 봅시다!

서버와 클라이언트 간에 데이터 보내기

터미널에서 Spring Boot 프로젝트의 루트 디렉토리로 이동하여 다음 명령을 실행하여 서버를 시작합니다:

gradle bootRun
Enter fullscreen mode Exit fullscreen mode

그런 다음, 안드로이드 스튜디오에서 안드로이드 클라이언트를 실행하고 앱이 로드되면 네 개의 버튼 중 하나를 클릭합니다.

Android 앱을 가지고 놀면서 웹소켓을 통해 클라이언트에서 서버로 메시지가 어떻게 전송되는지 확인해 보세요!

Android 클라이언트를 게시/서버로 업데이트

클라이언트에서 서버로 또는 서버에서 클라이언트로 데이터를 전송하는 것은 어렵지 않으며 매우 빠르게 수행할 수 있습니다. 하지만 클라이언트 간에 데이터를 전송하려면 어떻게 해야 할까요? 서버에서 라우팅 및 메시지 브로커 로직을 구현하지 않고는 클라이언트를 직접 연결할 수 없습니다( enableSimpleBroker를 통해 메시지 브로커 레지스트리 유형으로 등록).

이 작업의 시간을 줄이기 위해 사용할 수 있는 몇 가지 도구가 있습니다. 그러한 도구 중 하나는 클라이언트 간의 실시간 양방향 연결을 설정하는 Socket.IO입니다. 이 도구는 사용하기에 좋은 오픈 소스 도구이지만 서버를 설정하고 클라이언트를 서버에 연결해야 합니다. 또 다른 도구는 Spring Boot에서 STOMP와 같은 하위 프로토콜을 사용하여 STOMP 메시지를 임베드하는 것입니다. 이 서브프로토콜은 웹소켓 위에서 작동하며 웹소켓 메시지 처리를 가능하게 합니다. 하지만 엔드포인트를 직접 설정하려면 StompEndpointRegistry 유형을 사용하여 엔드포인트를 설정한 다음 addEndPoint 메서드를 사용하여 엔드포인트를 추가해야 합니다. 서버를 수동으로 설정하지 않고도 클라이언트 간에 데이터를 안전하고 안정적으로 전송할 수 있는 더 쉬운 방법이 있을까요? PubNub을 사용하면 가능합니다.

PubNub은 TCP를 사용하는 모든 기기를 지원하는 실시간 인프라를 제공합니다. PubNub의 글로벌 데이터 스트림 네트워크를 사용하여 클라이언트에서 클라이언트로, 클라이언트에서 서버로, 또는 서버에서 클라이언트로 데이터를 100ms 이내에 스트리밍할 수 있습니다! PubNub을 사용하면 웹소켓과 유사하게 채널에 연결된 기기 간에 상시 연결이 이루어집니다. 가장 좋은 점은 PubNub는 서버가 없고 무한한 확장이 가능하기 때문에 백엔드 서버를 설정하고 서버를 유지 관리하는 것에 대해 걱정할 필요가 없다는 것입니다.

PubNub이 클라이언트에서 클라이언트로 데이터를 전송하는 프로세스를 어떻게 간소화하는지 알아보기 위해 앞서 만든 안드로이드 앱을 수정해 보겠습니다. 먼저 무료 PubNub 계정에 가입하여 무료 Pub/Sub API 키를 받으세요.

안드로이드 클라이언트 수정하기

업데이트된 앱을 기존 앱과 구별하기 위해 PubNubJavaClient라는 새 안드로이드 프로젝트를 생성합니다. PubNub의 Android SDK를 사용하려면 앱 디렉토리의 build.gradle 파일에 다음을 추가하여 json을 업데이트하고 프로젝트를 동기화합니다:

dependencies {
    implementation group: 'com.pubnub', name: 'pubnub-gson', version: '6.4.5' // Add this
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ...
}
Enter fullscreen mode Exit fullscreen mode

매니페스트 파일에 다음 권한을 포함합니다:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Enter fullscreen mode Exit fullscreen mode

메인액티비티.java를 제외한 다른 모든 파일은 동일합니다. 이전 앱에서 다음 파일을 업데이트된 앱에 추가합니다: animal_sounds.xml, strings.xmldrawable 디렉터리의 이미지. 이 작업이 끝나면 MainActivity.java로 이동하여 새 코드를 추가합니다. 다음 패키지를 가져와서 onCreate()를 설정합니다:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.pubnub.api.PNConfiguration;
import com.pubnub.api.PubNub;
import com.pubnub.api.callbacks.PNCallback;
import com.pubnub.api.callbacks.SubscribeCallback;
import com.pubnub.api.models.consumer.PNPublishResult;
import com.pubnub.api.models.consumer.PNStatus;
import com.pubnub.api.models.consumer.pubsub.PNMessageResult;
import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {
  PubNub pubnub;
  TextView textView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.animal_sounds);

    initPubNub(); // Initialize PubNub
  }
Enter fullscreen mode Exit fullscreen mode

initPubNub( )를 호출하여 PubNub를 초기화합니다:

public void initPubNub(){
  PNConfiguration pnConfiguration = new PNConfiguration();
  pnConfiguration.setPublishKey("ENTER_YOUR_PUB_KEY"); // REPLACE with your pub key
  pnConfiguration.setSubscribeKey("ENTER_YOUR_SUB_KEY"); // REPLACE with your sub key
  pnConfiguration.setSecure(true);
  pubnub = new PubNub(pnConfiguration);

  // Listen to messages that arrive on the channel
  pubnub.addListener(new SubscribeCallback() {
    @Override
    public void status(PubNub pub, PNStatus status) {
    }

    @Override
    public void message(PubNub pub, final PNMessageResult message) {
      // Replace double quotes with a blank space
      final String msg = message.getMessage().toString().replace("\"", ""); 
      textView = findViewById(R.id.animalSound);

      runOnUiThread(new Runnable() {
        @Override
        public void run() {
          try{
            // Display the message on the app
            textView.setText(msg);
          } catch (Exception e){
              System.out.println("Error");
              e.printStackTrace();
          }
        }
      });
    }

    @Override
    public void presence(PubNub pub, PNPresenceEventResult presence) {
    }
  });

  // Subscribe to the global channel
  pubnub.subscribe()
    .channels(Arrays.asList("global_channel"))
    .execute();
}
Enter fullscreen mode Exit fullscreen mode

이 메서드에서는 세 가지 주요 작업을 수행합니다:

  1. PubNub 클라이언트 API를 초기화합니다. "ENTER_YOUR_PUB_KEY" 및 "ENTER_YOUR_SUB_KEY"를 자신의 Pub/Sub 키로 바꿔야 합니다.

  2. 채널에 도착하는 메시지에 대한 알림을 받도록 수신기를 설정합니다. 앞서 한 것처럼 클라이언트가 볼 수 있도록 앱에 메시지를 표시합니다.

  3. 메시지가 게시될 글로벌 채널을 구독합니다.

사용자가 버튼을 누르면 sendMessage() 메서드가 호출됩니다:

// This method is called when a button is pressed
public void sendMessage(View view) {
  // Get button ID
  switch(view.getId()){
    case(R.id.dogButton):
      publishMessage("Woooof");
      break;

    case(R.id.catButton):
      publishMessage("Meooow");
      break;

    case(R.id.pigButton):
      publishMessage("Bork Bork");
      break;

    case(R.id.foxButton):
      publishMessage("Fraka-kaka-kaka");
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

이 메서드는 앞서 수행한 작업과 유사하지만, 이제는 실제 메시지인 동물 소리를 서버가 아닌 글로벌 채널에 게시한다는 점이 다릅니다. 메시지를 게시하는 도우미 함수로 publishMessage() 를 사용합니다.

public void publishMessage(String animal_sound){
  // Publish message to the global chanel
  pubnub.publish()
    .message(animal_sound)
    .channel("global_channel")
    .async(new PNCallback<PNPublishResult>() {
      @Override
      public void onResponse(PNPublishResult result, PNStatus status) {
        // status.isError() to see if error happened and print status code if error
        if(status.isError()) {
          System.out.println("pub status code: " + status.getStatusCode());
        }
      }
    });
}
Enter fullscreen mode Exit fullscreen mode

이제 앱을 실행하는 데 필요한 모든 것이 끝났습니다!

클라이언트 간에 데이터 보내기

두 개의 에뮬레이터에서 안드로이드 앱을 실행하여 메시지가 실시간으로 표시되는지 확인하세요.

참고: 클라이언트 간의 원활한 통신을 보장하려면 두 Android 에뮬레이터에 동일한 PubNub 키가 있고 동일한 채널에 가입되어 있는지 확인하세요. 이는 실시간 통신을 위한 성공적인 웹소켓 연결을 설정하는 데 매우 중요합니다.

이제 Java에서 웹소켓을 사용하여 데이터를 전송하는 방법을 배웠으니 다른 리소스도 살펴보는 것을 잊지 마세요.

마지막으로, 문서에서 메시지 액션, 객체신호 메시지와 같이 애플리케이션에 강력한 기능을 제공하기 위해 PubNub에서 제공하는 추가 기능에 대해 알아보세요. 보다 포괄적인 이해를 위해 업데이트된 스크린샷과 리소스 링크를 확인하세요.

PubNub을 사용하면 위치 정보, 에지 메시지 버스, 엔터프라이즈 소프트웨어, 게임, 디지털 건강전자상거래에 맞는 맞춤형 솔루션을 만들 수도 있습니다.

시작하려면 계정 설정 과정을 안내하는 일반 설정 가이드를 참조하세요. 메시지 주고받기 방법에 대해 자세히 알고 싶다면 관련 가이드도 마련되어 있습니다. 자세한 내용은 메시지 보내기 가이드와 메시지 받기 가이 드를 참조하세요.

채팅 시나리오에서 메시지 보내기, 메시지 수신 및 읽지 않은 쪽지 수와 같은 고급 기능에 대해서는 메시지 보내기, 메시지 수신읽지 않은 쪽지 수 문서를 참조하세요.

Android 플랫폼에서 작업 중이며 푸시 알림을 구현하려는 경우, 저희가 도와드리겠습니다. Android 푸시 가이드푸시 보내기 가이드를 참조하여 파이어베이스 및 PubNub로 푸시 알림을 설정하는 방법에 대한 자세한 지침을 확인하세요.

이 글의 내용에 대한 제안이나 질문이 있으신가요? devrel@pubnub.com 으로 문의하세요.

펍넙이 어떤 도움을 줄 수 있나요?

이 문서는 원래 PubNub.com에 게시되었습니다.

저희 플랫폼은 개발자가 웹 앱, 모바일 앱 및 IoT 디바이스를 위한 실시간 인터랙티브를 구축, 제공 및 관리할 수 있도록 지원합니다.

저희 플랫폼의 기반은 업계에서 가장 크고 확장성이 뛰어난 실시간 에지 메시징 네트워크입니다. 전 세계 15개 이상의 PoP가 8억 명의 월간 활성 사용자를 지원하고 99.999%의 안정성을 제공하므로 중단, 동시 접속자 수 제한, 트래픽 급증으로 인한 지연 시간 문제를 걱정할 필요가 없습니다.

PubNub 체험하기

라이브 투어를 통해 5분 이내에 모든 PubNub 기반 앱의 필수 개념을 이해하세요.

설정하기

PubNub 계정에 가입하여 PubNub 키에 무료로 즉시 액세스하세요.

시작하기

사용 사례나 SDK에 관계없이 PubNub 문서를 통해 바로 시작하고 실행할 수 있습니다.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .