본문 바로가기

3.구현/Java or Kotlin

[java/kotlin] Spring Boot에서 웹소켓 사용하기

들어가기

웹소켓(websocket)은 브라우저와 웹서버 간에 통신을 할 수 있는 채널을 만드는 기술이다. 기존 HTTP을 사용하는 경우 데이터를 송수신할 때마다 매번 연결하고 끊어지는 작업이 반복된다. 웹소켓은 계속 연결된 상태로 유지되기 때문에 데이터를 더 효율적으로 처리할 수 있다. 웹소켓도 사용하기 위한 자신만의 규약이 있지만 Spring Boot에서 웹소켓을 쉽게 사용하는 방법이 있다.

작성자: http://ospace.tistory.com/ (ospace114@empal.com)

웹소켓이란?

웹소켓은 양방향 통신하는 프로토콜(RFC6455)이다. 웹소켓은 80과 443위에서 HTTP 프로토콜과 호환되도록 동작한다. 기존 HTTP 폴링에 비해 부하가 낮고 푸시 방식의 실시간 데이터 전송가능하다.

최신 브라우저인 경우 호환성에 문제는 없다. 그렇지만 레거시 환경에서는 꼼꼼히 확인이 필요하다.

출처: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#browser_compatibility (2020/03/09)

출처: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#browser_compatibility (2024/03/25)

실제테스트 결과 제한적이지만 IE9까지 사용 가능(sock.js사용)하다.

Spring Boot

Spring 4.0 부터 웹소켓을 지원하고 있다. TextWebSocketHandler 클래스를 상속받아서 쉽게 구현가능하다.

Dependency 추가

먼저 해야할 작업이 dependency을 pom.xml에 추가해야 한다. Spring Boot에서 웹소켓 사용을 위해 spring-boot-starter-websocket를 추가한다.

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

TextWebSocketHandler 구현

다음으로 웹소켓를 처리할 클래스를 작성한다. TextWebSocketHandler 클래스를 상속해서 구현하는 클래스이다. TextWebSocketHandler 클래스는 웹소켓을 통해 텍스트 데이터를 주고받는 처리를 해준다. TextWebSocketHandler을 상속한 WebSocketConroller 클래스를 구현해보자.

public class WebSocketController extends TextWebSocketHandler {
private List<WebSocketSession> sessions = new ArrayList<WebSocketSession>();
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 연결요청시 호출
        sessions.add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 메시지 수신시 호출
        for (WebSocketSession s : sessions) {
            s.sendMessage(new TextMessage(s.getId() + “:” + message.getPayload()));
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { //연결종료시 호출
        sessions.remove(session);
    }
}

여러 클라이언트 연결세션 관리 위한 sessingList 추가했다. handleTextMessage()에 의해서 수신받은 메시지를 받아서 처리한다. TestMessage 객체를 통해 텍스트 메시지를 추출할 수 있다. 그리고 afterConnectionClosed()에 의해 연결이 끊어진 세션을 받아서 sesstionList에서 제거한다. 위의 예제는 단순 수신한 메시지에 송신한 클라이언트 세션 ID 추가해서 모든 연결된 세션에 반송한다.

환경구성

웹소켓을 처리할 클래스를 만들었다고 알아서 처리되지 않는다. 실제 동작하도록 해당 클래스를 등록해주는 설정을 해야한다. 환경구성 위해 WebsocketConfigurer 인테페이스를 구현한다.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    private WebSocketController webSocketController = new WebSocketController();

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketController, "/ws");
}

앞에서 구현한 WebSocketController 핸들러 객체를 registerWebSocketHandlers()에서 추가한다. 그리고 매핑할 URL을 “/ws”로 한다.

Client

이제 웹소켓을 사용할 클라이언트를 만들어 보자.

HTML

브라우저에서 간단한 호출화면을 만든다.

<button onclick="onConnect()">connect</button>
<button onclick="onDisconnect()">disconnect</button><br/>
<input id="message" type="text" value="hello"/>
<button onclick="onSend()">send</button><p/>

별다른 기능은 없다. 연결과 접속하는 버튼 두 개와 송신할 메시지 입력하는 텍스트 박스, 그리고 송신 버튼이 있다.

Fig 01. 브라우저 화면

Javascript

이제 버튼을 클릭할 때 호출할 함수를 정의해보자. 각자 접속, 접속 종료 및 메시지 전송 기능을 가지고 있다.

var ws = undefined;
function onConnect() {
  ws = new WebSocket('ws://localhost:8080/ws');
  ws.onopen = function(e) { console.log('onopen:', e);};
  ws.onclose = function(e) { console.log('onclose:', e);};
  ws.onmessage = function(e) { console.log('onmessage:', e.data); };
  ws.onerror = function(e) { console.log('onerror:', e);};
}

function onDisconnect() {
  if(!ws) return;
  ws.close();
  ws = undefined;
}

function onSend() {
  if(!ws) return;
  var msg = document.getElementById('message');
  ws.send(msg.value);
}

자바스크립트는 WebSocket 객체를 사용해서 통신한다. 입력되는 주소는 접속할 웹소켓 서버와 앞의 URL 매핑 경로를 사용해 접속할 주소를 생성한다. 그리고 연결과 종표시, 메시지 수신, 그리고 에러 발생할데 처리할 핸들러를 등록한다.

Sock.js 사용

socket.js는 별도 라이브러리로 웹소켓을 지원하지 않은 브라우저에서 Websocket Polyfill 같은 기능을 제공한다. 이럴 경우 Spring Boot에서 수정이 필요하다. Spring boot에서 웹소켓 설정하는 WebSocketConfig 클래스에서 다음과 같이 수정이 필요하다.

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketController, "/ws").withSockJS();
}

registerWebSocketHandlers()에서 핸들러 등록시 뒤에 withSockJS()을 사용한다. 그리고 브라우저에서 사용할 때에 WebSocket 객체 대신에 SockJS 개체를 사용하면 된다.

ws = new SockJS('ws://localhost:8080/ws');

그외에는 동일하다.

Kotlin 버전

WebSocketController 클래스

import org.springframework.web.socket.CloseStatus
import org.springframework.web.socket.TextMessage
import org.springframework.web.socket.WebSocketSession
import org.springframework.web.socket.handler.TextWebSocketHandler

class WebSocketController : TextWebSocketHandler() {
    private val sessions = mutableListOf<WebSocketSession>()

    override fun afterConnectionEstablished(session: WebSocketSession) {
        sessions.add(session)
    }

    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
        for (s in sessions) {
            s.sendMessage(TextMessage("${s.id}: ${message.payload}"))
        }
    }

    override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
        sessions.remove(session)
    }
}

WebSocketConfig 클래스

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
open class WebSocketConfig : WebSocketConfigurer {
    private val webSocketController = WebSocketController()

    override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
        registry.addHandler(webSocketController, "/ws")
    }
}

결론

간단하게Spring Boot에서 웹소켓을 사용하는 방법을 살펴보았다. 생각보다는 크게 어렵지 않다. 이제부터 이를 어떻게 사용하는게 핵심이 된다. 이제 연결 세션을 가지고 있기 때문에 리소스 관리에도 신경써야 한다. 또한 테스트 해보지는 않았지만 환경에 따라 최대 연결도 제한될 수 도 있다. 그리고 기존 대비 성능도 얼마나 향상될지 확인도 필요하다. 새로운 채널이 생겼으니 신경써야할 부분도 많아졌다.

부족한 글이지만 여러분에게 도움이 되었으면 하네요. ^^ 모두 즐거운 코딩생활되세요. ospace.

참고

[1] 웹소켓, https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93

[2] websocket Browser compatibility, https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#Browser_compatibility

[3] SockJS-client, https://github.com/sockjs/sockjs-client/

반응형