고수만/서버

Java에서 WebSocket 사용하기! -쉬움

JumBack2 2024. 11. 3. 13:32

 

이번에 이야기 할 내용은 Java에서 WebSocket을 사용하는 법에 대하여 이야기 해보려고 한다.

우선 WebSocket이 무엇인지 부터 간단히 이야기 해보겠다.

 

WebSocket은 웹 애플리케이션에서 서버와 클라이언트 간의 실시간 양방향 통신을 가능하게 하는 통신 프로토콜이다. HTTP와 달리, WebSocket은 연결을 한 번 설정하면 클라이언트와 서버 간에 지속적인 연결을 유지할 수 있으며, 양측에서 데이터를 주고받을 수 있다.

원래 웹 클라이언트는 요청자이고 서버는 응답을 하는데 특정 상황에서는 클라이언트의 요청 없이도 서버가 데이터를 보내야 할 때가 있다.

이때 사용 하는 것이 WebSocket 기술이고 웹 브라우저와 서버의 핸드쉐이크를 통해 http 외의 통신 프로토콜을 구축하는 것이다.

 

아래는 webSocket의 특징을 간단히 정리한 글이니 모르면 봐보도록 하자.

더보기

 

WebSocket의 특징

  1. 양방향 통신:
    • WebSocket은 클라이언트와 서버 간에 양방향 통신을 지원합니다. 즉, 클라이언트가 서버에 요청을 보내는 것뿐만 아니라, 서버도 클라이언트에 실시간으로 데이터를 보낼 수 있습니다. 이는 채팅 애플리케이션, 실시간 알림 시스템, 게임 등과 같이 실시간 데이터 교환이 중요한 애플리케이션에서 유용합니다.
  2. 지속적인 연결:
    • WebSocket은 HTTP와 달리, 연결을 설정한 후에는 클라이언트와 서버 간에 지속적인 연결을 유지합니다. 한 번의 연결 설정 이후에는 데이터 전송 시마다 새로운 연결을 설정할 필요가 없기 때문에 오버헤드가 줄어들고, 지연 시간이 감소합니다.
  3. 낮은 오버헤드:
    • HTTP 프로토콜은 매 요청마다 헤더와 메타데이터가 포함되어 오버헤드가 큽니다. 그러나 WebSocket은 연결 후 지속적으로 데이터를 주고받을 수 있기 때문에 이러한 오버헤드가 현저히 줄어듭니다.
  4. 실시간성:
    • WebSocket은 실시간 데이터 전송에 최적화되어 있습니다. 서버가 클라이언트에게 데이터를 즉시 푸시할 수 있기 때문에, 사용자 인터페이스는 실시간으로 업데이트될 수 있습니다.

WebSocket의 동작 방식

  1. 핸드셰이크 (Handshake):
    • WebSocket 연결은 먼저 HTTP 프로토콜을 사용하여 서버와 클라이언트 간의 초기 연결(핸드셰이크)을 설정합니다.
    • 클라이언트는 서버에 HTTP 요청을 보내면서, 이 요청에서 Upgrade: websocket 헤더를 포함하여 WebSocket으로 프로토콜을 업그레이드할 것을 요청합니다.
    • 서버가 이를 수락하면, HTTP 프로토콜에서 WebSocket 프로토콜로 업그레이드가 이루어지며, 지속적인 연결이 시작됩니다.
  2. 지속적인 데이터 교환:
    • 핸드셰이크가 완료된 후, 클라이언트와 서버는 자유롭게 데이터를 주고받을 수 있습니다. 이 데이터는 "프레임" 형태로 교환되며, 텍스트 데이터나 바이너리 데이터 모두 전송할 수 있습니다.
  3. 연결 종료:
    • 필요에 따라 클라이언트나 서버 측에서 연결을 종료할 수 있습니다. 연결이 종료되면, WebSocket은 더 이상 데이터를 주고받지 않으며 연결이 해제됩니다.

WebSocket의 사용 사례

  • 실시간 채팅 애플리케이션: WebSocket을 사용하면 사용자 간의 메시지를 실시간으로 전달할 수 있습니다.
  • 실시간 알림 시스템: 서버에서 발생한 이벤트를 클라이언트에 실시간으로 알릴 수 있습니다.
  • 실시간 게임: 게임에서 플레이어 간의 상호작용을 실시간으로 처리할 수 있습니다.
  • 주식 시장, 금융 데이터 스트리밍: 실시간으로 변동하는 주식 가격이나 금융 데이터를 클라이언트에게 전송할 수 있습니다.
  • 스포츠 경기 실시간 업데이트: 경기 결과나 이벤트를 실시간으로 전송하여 사용자에게 즉각적인 정보를 제공합니다.

WebSocket의 장단점

장점:

  • 실시간 양방향 통신: 클라이언트와 서버 간에 실시간으로 데이터를 주고받을 수 있습니다.
  • 낮은 오버헤드: 연결을 유지한 상태로 데이터를 주고받기 때문에 오버헤드가 적습니다.
  • 빠른 응답 시간: 실시간으로 데이터를 전송하고 수신할 수 있어 지연 시간이 줄어듭니다.

단점:

  • 복잡한 구현: HTTP에 비해 상대적으로 복잡한 설정과 구현이 필요합니다.
  • 보안: 지속적인 연결이 유지되기 때문에 보안 이슈가 발생할 수 있으며, 이를 위해 WebSocket을 통한 데이터 전송에 대한 보안 고려가 필요합니다.

 

외부 의존성 추가

 

우선 WebSocket을 직접 만들 것이 아니라면 spring websocket 의존성을 추가해줘야 한다.

 

Grade

implementation 'org.springframework.boot:spring-boot-starter-websocket'

Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

 

SockJS와 Stomp.js를 WebJars로 추가하기

또한 추가적으로 클라이언트의 웹 소켓 라이브러리도 추가해줘야 한다

 

Grade

implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.3'

Maven

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>sockjs-client</artifactId>
    <version>1.5.1</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>stomp-websocket</artifactId>
    <version>2.3.3</version>
</dependency>

 

 

위 의존성이 모두 준비가 되었다면 개발을 시작해 보자.

 

Java에서 Web Socket을 사용하기 위해서는 3가지 구성이 필요하다.

  1. WebSocket Config
  2. 메시지 전송 서비스
  3. 클라이언트 측 구독

 

1. WebSocket Config

 

WebSocket Config은 Spring 어플리케이션은 WebSocket 메시지 브로커를 설정하고 WebSocket 엔드포인트를 등록해주는 역할을 하는 구성클래스이다.

메시지 브로커란 서버와 클라이언트의 메시지를 전달하는 중앙 허브 역할을 한다. 이는 STOMP(Simple Text Oriented Messaging Protocol)프로토콜을 사용하여 구현한다.

아래는 WebSocketConfig 의 예시 코드이다.

 

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    //WebSocket 메시지 브로커를 구성
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config){
        //특정 목적지 경로(prefix)로 들어오는 메시지를 구독 중인 클라이언트에게 브로드캐스트합니다.
        config.enableSimpleBroker("/topic");
        //특정 목적지로 메시지를 보낼 수 있도록 합니다.
        config.setApplicationDestinationPrefixes("/app");
    }

    //클라이언트의 엔드포인트를 등록하고 SockJS를 사용하도록 설정합니다.
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry){
        //엔드포인트를 등록하고 SockJS를 사용하도록 설정합니다.
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }
}
 

 

 

WebSocketMessageBrokerConfigurer 를 상속하고 기존 메서드를 오버라이드 하여 메시지 브로커를

구성한다. WebSocketMessageBrokerConfigurer  STOMP를 기반으로 하고 있다.

WebSocketMessageBrokerConfigurer 의STOMP 프로토콜 메서드들을 오버라이드 하여 메시징을 설정한다.

configureMessageBroker 는 서버가 클라이언트에게 메시지를 보낼 수 있도록 구성하는 부분이다.

반대로 registerStompEndpoints는 클라이언트로 부터 서버가 메시지를 받을 수 있도록 엔드포인트를 설정하는 부분이다.

 

2. 메시지 전송 서비스

 

import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
public class WebSocketService {

    private final SimpMessagingTemplate messagingTemplate;

    public WebSocketService(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    public void sendMessage(String destination, String message) {
        messagingTemplate.convertAndSend(destination, message);
    }
}

 

 

WebSocketService 는 클라이언트에 메시지를 보내는 역할을 한다. configureMessageBroker에서 메시지 브로커의 경로를 “/topic”로 설정하였기 때문에 “/topic”을 기반으로 분기를 만들어준다.

destination에 “/topic/finish” 로 만들면 “/topic/finish” 로 모든 메시지를 브로드캐스트 한다.

 

3. 클라이언트 측 구독

 

var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);

stompClient.connect({}, function (frame) {
    console.log('Connected: ' + frame);
    
    // 클라이언트가 서버로부터 메시지를 받을 경로를 구독
    stompClient.subscribe('/topic/finish', function (message) {
        console.log("Received message: " + message.body);
        // 받은 메시지를 화면에 표시하거나 다른 작업 수행
    });
});

function sendMessage() {
    stompClient.send("/app/sendMessage", {}, "Hello, WebSocket!");
}

 

 

클라이언트는 우선

<!-- SockJS 라이브러리 로드 -->
<script src="/webjars/sockjs-client/1.5.1/sockjs.min.js"></script>
<!-- Stomp.js 라이브러리 로드 -->
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>

 

으로 스크립트를 추가하여 라이브러리를 클라이언트에 로드 시킨 뒤 사용한다.

 

아까 클라이언트의 엔드포인트를 “/ws”으로 설정해놓았기에

var socket = new SockJS('/ws'); 로 소켓을 만들어주고

stomp클라이언트를 구현해준다.

이후 stompClient.subscribe('/topic/finish', function (message) 로 서버 측 경로를 구독해주면

메시지가 발행되었을 때 수신할 수 있게 된다.

 

이후  console.log("Received message: " + message.body); 있는 부분에 커스텀하게 클라이언트 측 코드를 만들면 완성이다!