본문 바로가기

Spring Boot

[Spring Boot] 간단한 실시간 웹소켓 채팅 구현하기

728x90

소켓이란?

소켓(Socket)은 네트워크 상에서 서로 다른 프로그램이 데이터를 송수신하기 위한 종단점입니다.
소켓은 IP 주소와 포트 번호를 기반으로 통신하며, 클라이언트와 서버 간의 연결을 유지하거나 메시지를 교환하는 데 사용됩니다.

소켓의 주요 개념

  • IP 주소: 네트워크 상에서 컴퓨터를 식별하는 고유 주소.
  • 포트 번호: 특정 애플리케이션(프로세스)을 식별하는 번호.
  • 소켓 연결: 클라이언트와 서버가 데이터를 주고받기 위해 소켓을 통해 이루어지는 연결.

 

 

웹소켓(WebSocket)이란?

기존 HTTP 프로토콜은 요청-응답 기반으로 작동하여 실시간 양방향 통신이 어렵습니다.
웹소켓(WebSocket)은 이러한 한계를 극복하기 위해 만들어진 프로토콜로,

  • 서버와 클라이언트 간에 지속적인 연결을 유지하며,
  • 실시간으로 양방향 데이터를 주고받을 수 있도록 지원합니다.

웹소켓의 특징

  1. 양방향 통신: 서버와 클라이언트가 서로 데이터를 주고받을 수 있음.
  2. 연결 지속성: 연결이 유지되는 동안 추가적인 핸드셰이크 없이 데이터 교환.
  3. 효율성: 요청 없이 서버가 데이터를 푸시(Push) 가능.

 

 

스프링에서 웹소켓 구현하기

스프링 프레임워크는 STOMP 프로토콜을 사용하여 간단하게 웹소켓 기반의 실시간 통신을 구현할 수 있습니다.
아래는 스프링을 활용한 실시간 채팅 애플리케이션 예제입니다.

 

 

1. 프로젝트 설정

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
}

 

2. 웹소켓 설정

WebSocketConfig 클래스에서 웹소켓 엔드포인트와 메시지 브로커를 설정합니다.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic"); // 구독 주소
        config.setApplicationDestinationPrefixes("/app"); // 메시지 송신 주소
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws") // WebSocket 엔드포인트
                .setAllowedOriginPatterns("*") // CORS 설정
                .withSockJS(); // SockJS 지원
    }
}

 

 

3. 채팅 컨트롤러

클라이언트의 메시지를 처리하고, 구독자에게 메시지를 전달합니다.

@Controller
public class ChatController {

    @MessageMapping("/chat/{roomName}") // 클라이언트 메시지 처리
    @SendTo("/topic/{roomName}") // 구독 경로로 메시지 전송
    public String sendMessage(String message) {
        return message;
    }
}

 

 

4. HTML 클라이언트 예제

채팅방에 연결하고 메시지를 주고받는 클라이언트 코드입니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat Rooms</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client/dist/sockjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/stompjs/lib/stomp.min.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        .container {
            width: 50%;
            margin: auto;
            padding: 20px;
            text-align: center;
        }
        input, button {
            margin: 5px;
        }
        .messages {
            border: 1px solid #ccc;
            padding: 10px;
            margin-top: 10px;
            height: 200px;
            overflow-y: auto;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>Chat Rooms</h1>
    <input id="roomInput" type="text" placeholder="Enter room name" />
    <button onclick="joinRoom()">Join Room</button>
    <br>
    <input id="messageInput" type="text" placeholder="Enter your message" />
    <button onclick="sendMessage()">Send</button>
    <div class="messages" id="messages"></div>
</div>

<script>
    let stompClient = null; // STOMP 클라이언트 객체를 저장할 변수
    let currentRoom = null; // 현재 사용자가 접속한 채팅방 이름을 저장할 변수

    // 서버와 WebSocket 연결을 설정하는 함수
    function connect() {
        // 1. '/ws' 엔드포인트에 SockJS를 사용하여 WebSocket 연결 생성
        const socket = new SockJS('/ws');
        
        // 2. SockJS 객체를 사용하여 STOMP 클라이언트 생성
        stompClient = Stomp.over(socket);

        // 3. STOMP 프로토콜을 사용하여 서버와 연결 시작
        stompClient.connect({}, () => {
            console.log('Connected to WebSocket'); // 연결 성공 시 로그 출력
        });
    }

    // 사용자가 특정 채팅방에 참여하는 기능을 구현하는 함수
    function joinRoom() {
        if (stompClient) { // WebSocket 연결이 활성화된 상태인지 확인
            const roomInput = document.getElementById('roomInput'); // 입력된 채팅방 이름 가져오기
            currentRoom = roomInput.value; // 현재 채팅방 이름을 변수에 저장

            if (currentRoom) { // 채팅방 이름이 유효한 경우
                // 1. STOMP 클라이언트가 해당 채팅방 주제를 구독
                //    /topic/{roomName} 경로를 구독하여 해당 방의 메시지를 실시간으로 수신
                stompClient.subscribe(`/topic/${currentRoom}`, (response) => {
                    showMessage(response.body); // 메시지가 수신되면 화면에 표시
                });

                // 2. 구독이 성공했음을 사용자에게 알림
                alert(`Joined room: ${currentRoom}`);
            }
        }
    }

    // 사용자가 메시지를 보내는 기능을 구현하는 함수
    function sendMessage() {
        if (stompClient && currentRoom) { // WebSocket 연결과 채팅방이 활성화된 경우
            const messageInput = document.getElementById('messageInput'); // 입력된 메시지 가져오기
            const message = messageInput.value; // 메시지 값 저장

            // 1. STOMP 클라이언트를 통해 메시지를 서버로 전송
            //    /app/chat/{roomName} 경로로 메시지를 송신
            stompClient.send(`/app/chat/${currentRoom}`, {}, message);

            // 2. 입력창 초기화
            messageInput.value = '';
        }
    }

    // 수신된 메시지를 화면에 표시하는 함수
    function showMessage(message) {
        const messagesDiv = document.getElementById('messages'); // 메시지 표시 영역
        const messageElement = document.createElement('div'); // 새로운 메시지 요소 생성

        // 메시지 내용을 div 요소에 추가
        messageElement.textContent = message;
        
        // 메시지를 화면에 추가
        messagesDiv.appendChild(messageElement);
    }

    // 페이지 로드 시 WebSocket 연결 자동 설정
    connect();
</script>
</div>
</body>
</html>
728x90