웹소켓이란, 서버와 클라이언트 간에 Socket Connection을 유지하여 언제든
양방향 통신이 가능하도록 하는 기술로 전이중 채널을 이용한실시간 서비스에 사용되는 기술이다.
서버와 클라이언트 간의 통신은 대부분 HTTP를 통해서 이루어졌으며, HTTP는 req/res 기반의 stateless protocol이다. 매 요청마다 연결을 수립하고 응답이 간 이후에는 연결을 끊기 때문에 실시간 서비스에는 용이하지 않다. (polling, ajax 등의 방식으로 어느정도의 일부 해결은 가능)
비연결성 (connectionless) : 연결을 맺고 요청을 하고 응답을 받으면 연결을 끊어버린다.무상태성 (stateless) : 서버가 클라이언트의 상태를 가지고 있지 않는다.단방향 통신 : 요청 - 응답이러한 특징을 가지고 있어 채팅과 같은 실시간 통신에는 적합하지 않음.
stateful 프로토콜로 클라이언트와 한번 연결을 수립한 이후에 계속 같은 라인을 사용해서 통신하기 때문에 HTTP, TCP 연결 트래픽을 피할 수 있고 실시간 서비스에 사용하기 용이하다. 독자적인 프로토콜로 이루어지면서, 장시간 접속을 전제로양방향 통신이 가능한 기술이다.
연결지향, 상태유지, 양방향 통신의 특징으로 실시간 통신에 적합한 기술이다.
서버와 클라이언트 간의 WebSocket연결은 HTTP 프로토콜을 이용하여 이루어 지고, 정상적으로 연결이 수립된 이후에는 WebSocket 연결이 이루어지고 일정 시간이 지나면 HTTP 연결은 자동으로 끊어진다.
간단한 메시지를 전송하기 위한 프로토콜로
메시지 브로커와publisher-subscriber방식을 사용한다. 메시지의 발행자와 구독자가 존재하고 메시지를 보내는 사람과 받는 사람이 구분되어 있다. 브로커는 발행자가 보낸 메시지를 구독자에게 전달해준다.frame 기반 프로토콜command, header, body로 이루어져 있다.
이러한 STOMP를 웹소켓과 함께 사용하면 frame의 구조가 정해져 있기 때문에 통신에 용이하다. 또한 핸들러도 따로 만들어 줄 필요가 없고 Spring Security도 사용할 수 있다는 장점이 있다.

수신자는 /topic 경로로 구독을 하고 있고, 발신자는 /app를 통하면 가공해서 보내게 되고 /topic으로 보내면 바로 수신자에게 메시지를 보내게 된다.
@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();
}
@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()) + "!");
}
}
// 처음 웹소켓 연결 수립하는 과정
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에 저장되어 있는 채팅방에 대하여 실시간 통신이 필요했기 때문에 핸들러를 직접 구현하는 방식을 선택하였다.
알림을 받은 직원은 현재 페이지에 따라서 반응이 다르게 나와야 한다.
이와 같은 해결을 위해서, 처음에는 하나의 웹소켓 세션을 연결하여 페이지마다 공유하도록 설정하였지만, 특정 채팅방을 닫을 때마다 웹소켓 연결이 해제되면서 전체적으로 연결이 끊기는 문제가 발생하였다. 아래의 자료들 참고하기
WebSocket 이란?
[10분 테코톡] ✨ 아론의 웹소켓&스프링
websocket 개념 및 이미지 참고