WebSocket

나성민·2024년 4월 3일

개념

웹소켓이란, 서버와 클라이언트 간에 Socket Connection을 유지하여 언제든 양방향 통신이 가능하도록 하는 기술로 전이중 채널을 이용한 실시간 서비스에 사용되는 기술이다.

HTTP

서버와 클라이언트 간의 통신은 대부분 HTTP를 통해서 이루어졌으며, HTTP는 req/res 기반의 stateless protocol이다. 매 요청마다 연결을 수립하고 응답이 간 이후에는 연결을 끊기 때문에 실시간 서비스에는 용이하지 않다. (polling, ajax 등의 방식으로 어느정도의 일부 해결은 가능)

  • 비연결성 (connectionless) : 연결을 맺고 요청을 하고 응답을 받으면 연결을 끊어버린다.
  • 무상태성 (stateless) : 서버가 클라이언트의 상태를 가지고 있지 않는다.
  • 단방향 통신 : 요청 - 응답

이러한 특징을 가지고 있어 채팅과 같은 실시간 통신에는 적합하지 않음.

WebSocket

stateful 프로토콜로 클라이언트와 한번 연결을 수립한 이후에 계속 같은 라인을 사용해서 통신하기 때문에 HTTP, TCP 연결 트래픽을 피할 수 있고 실시간 서비스에 사용하기 용이하다. 독자적인 프로토콜로 이루어지면서, 장시간 접속을 전제로 양방향 통신이 가능한 기술이다.

연결지향, 상태유지, 양방향 통신의 특징으로 실시간 통신에 적합한 기술이다.

서버와 클라이언트 간의 WebSocket연결은 HTTP 프로토콜을 이용하여 이루어 지고, 정상적으로 연결이 수립된 이후에는 WebSocket 연결이 이루어지고 일정 시간이 지나면 HTTP 연결은 자동으로 끊어진다.

STOMP (Simple Text Oriented Messaging Protocol)

간단한 메시지를 전송하기 위한 프로토콜로 메시지 브로커publisher-subscriber 방식을 사용한다. 메시지의 발행자와 구독자가 존재하고 메시지를 보내는 사람과 받는 사람이 구분되어 있다. 브로커는 발행자가 보낸 메시지를 구독자에게 전달해준다. frame 기반 프로토콜 command, header, body로 이루어져 있다.

이러한 STOMP를 웹소켓과 함께 사용하면 frame의 구조가 정해져 있기 때문에 통신에 용이하다. 또한 핸들러도 따로 만들어 줄 필요가 없고 Spring Security도 사용할 수 있다는 장점이 있다.

수신자는 /topic 경로로 구독을 하고 있고, 발신자는 /app를 통하면 가공해서 보내게 되고 /topic으로 보내면 바로 수신자에게 메시지를 보내게 된다.

실습

WebSocket을 사용하여 대화형 웹 애플리케이션 구축

참고링크 (Spring 공식 사이트)

  • 웹소켓 설정하기
 	@Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        /**
         * 내장 브로커 사용
         * prefix가 붙은 메시지를 발행 시 브로커가 처리
         */
        config.enableSimpleBroker("/topic");
        /**
         * 메시지 핸들러로 라우팅 되는 prefix
         */
        config.setApplicationDestinationPrefixes("/app");
    }
    
    // 웹소켓 연결 수립하는 엔드포인트 설정
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }
  • 위에서 라우팅 설정에 따른 매핑정보 설정
    /app/hello로 요청시에 아래 핸들러에 매핑이 된다. (/hello)
@Controller
public class GreetingController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
    }

}
  • 결과적으로, 아래의 JS코드로 연결을 수립하고 발행, 구독을 수행할 수 있다.
// 처음 웹소켓 연결 수립하는 과정
const stompClient = new StompJs.Client({
    brokerURL: 'ws://localhost:8080/gs-guide-websocket'
});

// topic/greetings 구독하기, 메시지오면 showGreeting 수행
stompClient.onConnect = (frame) => {
    setConnected(true);
    console.log('Connected: ' + frame);
    stompClient.subscribe('/topic/greetings', (greeting) => {
        showGreeting(JSON.parse(greeting.body).content);
    });
};

// 에러 처리
stompClient.onWebSocketError = (error) => {
    console.error('Error with websocket', error);
};

stompClient.onStompError = (frame) => {
    console.error('Broker reported error: ' + frame.headers['message']);
    console.error('Additional details: ' + frame.body);
};

// 연결하고 아래 채팅 화면 보여주기
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    stompClient.activate();
}

function disconnect() {
    stompClient.deactivate();
    setConnected(false);
    console.log("Disconnected");
}

// 메시지 보내기
function sendName() {
    stompClient.publish({
        destination: "/app/hello",
        body: JSON.stringify({'name': $("#name").val()})
    });
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
    $("form").on('submit', (e) => e.preventDefault());
    $( "#connect" ).click(() => connect());
    $( "#disconnect" ).click(() => disconnect());
    $( "#send" ).click(() => sendName());
});

공항관리 프로젝트에서는 DB에 저장되어 있는 채팅방에 대하여 실시간 통신이 필요했기 때문에 핸들러를 직접 구현하는 방식을 선택하였다.

  • 특정 채팅방에 대하여 채팅 보내기
  • 해당 채팅방에 속해있는 직원들 사번 DB에서 가져오기
  • 채팅방 소속원 & 로그인한 직원 => 실시간 알림 보내기

알림을 받은 직원은 현재 페이지에 따라서 반응이 다르게 나와야 한다.

  • 채팅방 목록 : 가장 최근 메시지를 갱신해줘야 한다.
  • 특정 채팅방 : 채팅방에 들어갔을 때, 실시간으로 메시지 갱신
  • 이외 페이지에서는 우측 상단에 토스트 메시지로 알림 메시지를 받게 된다.

이와 같은 해결을 위해서, 처음에는 하나의 웹소켓 세션을 연결하여 페이지마다 공유하도록 설정하였지만, 특정 채팅방을 닫을 때마다 웹소켓 연결이 해제되면서 전체적으로 연결이 끊기는 문제가 발생하였다. 아래의 자료들 참고하기

내가 이전에 올렸던 게시물들

웹소켓 사용시 마주한 문제점
웹소켓 이용방법

참고

WebSocket 이란?
[10분 테코톡] ✨ 아론의 웹소켓&스프링
websocket 개념 및 이미지 참고

0개의 댓글