[Network] 내가 만든 웹 서비스 WireShark로 패킷 분석하기 - Spring WebSocket & STOMP
1편 - 소켓 통신 프로젝트 기능 구현 소개
2편 - 패킷 분석 내용 소개
개요
WireShark 패킷 분석을 위한 애플리케이션인 만큼, 소켓 통신을 구현하여 서버와 클라이언트, 클라이언트끼리의 통신이 잘 보이는 채팅 서비스를 구현하였다.
소켓 통신은 KAU 멋쟁이 사자처럼 고기의 소켓통신 세션을 참고하였고, SMTP 프로토콜 통신을 캡처하기 위해 이메일 전송을 추가하였다.소스코드는 해당 깃허브에서 확인 가능하다.
기능 구현은 크게 다음과 같다.
- WebSocket 통신
- 구글 SMTP 서버를 통한 메일 발송
WebSocket 통신 구현에 사용된 기술
크게 3가지로 분류된다.
- WebSocket(HTTP와 비교)
- STOMP
- Message Broker
WebSocket이란? HTTP와 다른 점은 뭘까?
HTTP는 클라이언트가 서버에 요청을 보내고, 서버가 응답을 하는 요청-응답 방식의 프로토콜이다. 이 프로토콜은 connectionless라는 특징을 통해 자원 낭비를 방지하고 효율적인 통신을 한다. 하지만 이러한 비연결성이 오히려 자원의 손실을 야기하는 경우도 있다. 바로 채팅, 주식, 게임 등 실시간 성이 반영되어야 하는 경우다
Application Layer에서 HTTP 프로토콜을 사용하면 실시간 통신에 어려움이 있다.
- 단방향 통신으로 서버가 먼저 클라이언트에게 요청을 할 수 없다.
- 요청 없이는 응답을 할 수 없다. 즉, 양방향 실시간 통신이 불가능하다.
HTTP로 실시간 통신이 아예 불가능한 것은 아니다.
- HTTP/2부터는 Stream이라는 기능으로 서버와 클라이언트가 동시에 데이터를 주고 받는 실시간 양방향 통신이 가능하다.
- HTTP Long Polling 방식을 사용하면, 클라이언트가 서버에 대해 지속적으로 연결을 유지한다.
하지만 이와 같은 극복점이 있다고 하더라도 HTTP는 주로 요청-응답 패턴에 맞추어 설계되어 있다. 따라서 이 프로토콜을 사용할 경우 오버헤드나 요청 헤더가 과도하게 주고받아지는 단점이 있다.
반면, Web Socket은 실시간 양방향 데이터 교환을 위한 프로토콜로, HTTP의 비연결성에서 벗어나 연결 상태를 유지함으로써 데이터를 교환한다.
실제로 패킷 분석 내용의 일부를 보면 , 요청 헤더를 통해 WebSocket으로 업그레이드하는 것을 확인할 수 있다.
[WebSocket 요청 패킷 일부 발췌]
STOMP(Simple Text Oriented Messaging) 프로토콜
STOMP는 사람이 읽고 쓰기 쉬운 형태의 텍스트 기반 프로토콜로, 메시지를 전송하고 수신하기 위한 프로토콜이다.
- STOMP 메시지는 프레임 단위로 구성되며, 프레임은 헤더와 바디로 이루어져 있다.
- 프레임은 크게 CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE 유형으로 나뉜다.
- 동작과정은 연결(CONNECT)- 구독(SUBSCRIBE) - 메시지 발행(SEND) - 전달(MESSAGE) - 구독 취소(UNSUBSCRIBE) - 연결 종료다.
WebSocket과 함께 사용하여 메시지의 전송, 구독, 취소 등의 작업을 구조화된 방식으로 처리할 수 있다.
- 만약 이 프로토콜을 정의 및 사용하지 않으면 어떤 형식으로 어떤 데이터를 교환할 것인지 모두 구현해야 하는 번거로움이 생긴다. 😅
해당 프로젝트에서는 JavaScript의 STOMP 클라이언트 즉, @stomp/stompjs 라이브러리를 사용하였다.
const stompClient = new StompJs.Client({
brokerURL: 'ws://localhost:8080/endpoint'
});
stompClient.activate();
위 코드처럼 StompJs.Client 객체를 생성하고, brokerURL로 WebSocket 서버의 주소를 생성한 뒤, STOMP 클라이언트를 활성화하면 WebSocket 서버와의 연결을 시작한다.
function publish() {
const destination = "/pub/chat/" + roomId;
const text = document.getElementById("text").value;
document.getElementById("text").value = "";
const message = {
senderName: myName,
text: text,
createdTime: new Date()
};
stompClient.publish({
destination: destination,
body: JSON.stringify(message)
});
}
메시지 발행을 위해 stompClient.publish 메서드를 사용하여 메시지 형식을 지정하고, JSON 형식으로 변환하여 발행한다.
이후, showChat 함수를 통해 수신된 메시지를 HTML 요쇼로 생성하여 채팅 로그에 표시한다.
Message Broker
WebSocket 메시지 브로커를 구성하기 위해 Java Spring의 @EnableWebSocketMessageBroker 애너테이션과 WebSocket 인터페이스를 사용하였다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 구독 경로 설정: 클라이언트로부터 서버로 메시지를 전달
registry.enableSimpleBroker("/sub");
// 발행 경로 설정: 서버로부터 클라이언트로 메시지를 전달
registry.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// WebSocket 엔드포인트 설정
registry.addEndpoint("/endpoint");
}
}
Spring WebSocket 설정을 통해 엔드포인트('/endpoint')를 등록하고, pub/sub 경로를 메시지 브로커가 처리하도록 한다. 클라이언트 측은 JS로 STOMP 클라이언트를 사용하여 WebSocket 연결을 설정하고, 해당 경로로 메시지를 발행 및 구독한다.
이러한 방식으로 클라이언트와 서버는 WebSocket을 통해 연결되며, 클라이언트는 STOMP 프로토콜을 사용하여 메시지 브로커와 통신한다. 클라이언트는 메시지를 발행하여 서버로 전송하고, 서버는 메시지 브로커를 통해 해당 경로를 구독하고 있는 다른 클라이언트에게 메시지를 전달한다.
이 정도가 가장 핵심적인 내용이고, 이메일 전송 구현은 해당 프로젝트의 핵심 기능이 아니기에 설명을 보태지 않았다.
2편에서 구현된 내용을 WireShark로 캡처하고, 패킷을 분석한 내용에 대해 소개하도록 한다.