5 minute read

웹 애플리케이션의 사용자 경험을 향상시키기 위해 웹소켓이 많이 사용되고 있습니다.

이번 글에서는 스프링 레거시 프로젝트에 웹소켓을 활용하여 채팅을 주고 받는 방법에 대해 알아보겠습니다.

0. 웹소켓이란?

웹소켓(WebSocket)은 실시간 양방향 통신을 지원하는 통신 프로토콜입니다. 기존의 HTTP 프로토콜은 클라이언트에서 서버로의 단방향 통신만을 지원하며, 서버에서 클라이언트로 메시지를 전송하기 위해서는 주기적인 폴링이나 장치를 통한 간접적인 방법을 사용해야 했습니다.

웹소켓은 이러한 한계를 극복하기 위해 설계되었습니다. 한 번의 연결로 양방향 통신이 가능하며, 클라이언트와 서버는 언제든 실시간으로 데이터를 주고받을 수 있습니다.

1. 프로젝트 설정

먼저, 스프링 레거시 프로젝트에 웹소켓을 추가하기 위해 프로젝트 설정을 시작해보겠습니다. pom.xml 파일에 다음 의존성을 추가합니다.

<!-- 웹소켓 모듈 등록 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

2. 웹소켓 서버 파일 구현

  • WebscoketServer.java 파일을 생성한 다음, TextWebSocketHandler 클래스를 상속받아 웹소켓과 관련된 메소드를 구현합니다.
  public class websocketServer extends TextWebSocketHandler {
      
  }

WebSocketHandler 인터페이스를 구현하는 방법도 있지만, 채팅을 하기위해 텍스트 메시지에 관련된 TextWebSocketHandler 을 사용했습니다.

  • afterConnectionEstablished 메소드 생성
    • 클라이언트가 웹소켓에 연결되었을 때 호출되는 메소드입니다.
    • WebSocketSession 객체를 통해 사용자의 웹소켓 정보를 확인할 수 있습니다.
    • 로그를 통해 접속한 사용자의 정보를 출력합니다.
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 접속했을 때
        log.info("접속");
        log.info("session : {}", session);
    }
  • handleTextMessage 메소드 생성
    • 클라이언트로부터 텍스트 메시지를 수신했을 때 호출되는 메소드입니다.
    • TextMessage 객체를 통해 수신된 메시지의 내용을 확인할 수 있습니다.
    • 로그를 통해 메시지를 수신한 사용자의 정보와 메시지 내용을 출력하고, 동일한 메시지를 클라이언트에게 다시 전송합니다.
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 메세지를 받았을 때
        log.info("메세지 수신");
        log.info("session : {}", session);
        log.info("message : {}", message);
        session.sendMessage(message);
    }
  • afterConnectionClosed 메소드 생성
    • 클라이언트의 웹소켓 접속이 종료됐을 때 호출되는 메소드입니다.
    • CloseStatus 객체를 통해 접속 종료 상태 및 원인을 확인할 수 있습니다.
    • 로그를 통해 접속 종료한 사용자의 정보와 상태를 출력합니다.
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 접속 종료 됐을 때
        log.info("접속 종료");
        log.info("session : {}", session);
        log.info("status : {} ", status);
    }
}

3. servlet-context.xml 설정

  • servlet-context.xml 파일에 웹소켓 서버를 등록합니다.

  • 빈등록

    • websocketServer 빈을 웹소켓 서버 클래스로 등록합니다.
<beans:bean id="websocketServer" class="com.kevin.spring.websocket.server.WebsocketServer"/>
  • 웹소켓 핸들러 매핑
    • websocket:handlers 엘리먼트 안에서 websocket:mapping 엘리먼트를 사용하여 요청 경로(path)에 따라 어떤 웹소켓 핸들러를 사용할지 매핑합니다.
    • allowed-origins="*"는 모든 오리진에서의 접근을 허용함을 나타냅니다.
<websocket:handlers allowed-origins="*">
	<websocket:mapping handler="websocketChat" path="/websocketChat"/>
</websocket:handlers>

4. 웹소켓 컨트롤러 구현

위에서 등록한 웹소켓을 사용하기 위해서는 컨트롤러에서 해당 기능을 호출할 수 있어야 합니다. 아래의 코드는 웹소켓과 관련된 기능을 제공하는 컨트롤러를 구현한 예시입니다.

@Controller
@RequestMapping("/websocket")
public class WebsocketController {
    
    @GetMapping("/websocketChat")
    public String websocketServer() {
        return "websocket/websocketChat";
    }
    
}

위의 코드에서 WebsocketController 클래스는 /websocket 경로를 기본으로 가지며, 다음의 기능을 수행합니다.

  • websocketServer 메소드
    • /websocket/websocketChat 경로에 GET 요청이 오면, 웹소켓의 기본 기능을 제공하는 페이지로 이동합니다.

이 컨트롤러를 통해 websocketChat.jsp 페이지로 이동할 수 있습니다.

5. 채팅 뷰페이지 구현

간단한 HTML과 JavaScript를 사용하여 웹소켓에 접속하고 메시지를 주고받는 websocketChat.jsp 페이지를 만들어 보았습니다.

  • HTML 과 JavaScript
htmlCopy code<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Websocket Chat</title>
</head>
<body>
    <!-- 웹소켓 클라이언트 스크립트 -->
    <script>
        var socket; // 소켓을 담아놓을 변수

        // 웹소켓 연결 함수
        function connect(){
            var url = "ws://localhost:8888/spring/websocketChat";
            socket = new WebSocket(url);

            // 연결 성공 시
            socket.onopen = function(){
                console.log("연결 성공");
            };

            // 연결 종료 시
            socket.onclose = function(){
                console.log("연결 종료");
            };

            // 에러 발생 시
            socket.onerror = function(e){
                console.log("에러가 발생했습니다.");
                console.log(e);
            };

            // 메시지 수신 시
            socket.onmessage = function(message){
                console.log("메시지 도착");
                console.log(message);
                var div = document.createElement("div");
                div.textContent = message.data;
                document.querySelector("#chatArea").appendChild(div);
            };
        }

        // 웹소켓 연결 종료 함수
        function disconnect(){
            socket.close(); // 소켓 닫기
        }

        // 메시지 전송 함수
        function send(){
            var text = document.getElementById("chat").value;
            socket.send(text);
            document.getElementById("chat").value = "";
        }
    </script>

    <!-- 웹소켓 클라이언트 UI -->
    <h1>Websocket Chat</h1>

    <!-- 연결 및 종료 버튼 -->
    <button onclick="connect();">접속</button>
    <button onclick="disconnect();">종료</button>

    <hr>

    <!-- 메시지 입력 및 전송 버튼 -->
    <input type="text" id="chat">
    <button onclick="send();">전송</button>

    <!-- 채팅 영역 -->
    <div id="chatArea"></div>
    
</body>
</html>
  • 연결 함수 (connect)

    • 클라이언트가 서버에 웹소켓으로 연결할 때 호출됩니다.

    • 지정된 경로(ws://localhost:8888/spring/basic)로 소켓을 생성하고, 연결 성공, 종료, 에러, 메시지 도착 등에 대한 콜백 함수들을 정의합니다.

  • 연결 종료 함수 (disconnect)

    • 클라이언트가 웹소켓 연결을 종료할 때 호출됩니다.

    • 현재 열려있는 소켓을 닫습니다.

  • 메시지 전송 함수 (send)
    • 클라이언트가 입력한 메시지를 소켓을 통해 서버로 전송할 때 호출됩니다.
  • UI 구성

    • 접속, 종료, 전송 버튼 및 채팅 입력 창 등의 UI가 제공됩니다.

websocketChat

6. 웹소켓 서버 로그 분석

웹소켓 서버에서 발생한 로그를 분석하여 접속, 메세지 수신, 접속 종료 등의 이벤트를 알수있습니다.

  • 접속

    • 로그내용

      INFO  : 접속 - com.kevin.spring.websocket.server.WebsocketServer (2024-01-22 15:24:39)
      INFO  : session : StandardWebSocketSession[id=80821ca7-98ea-fe30-0173-1667c3c26428, uri=ws://localhost:8888/spring/websocketChat] - com.kevin.spring.websocket.server.WebsocketServer (2024-01-22 15:24:39)
      
    • 메세지
      • INFO : 접속 - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:39)
      • 접속 이벤트가 발생했습니다.
    • 세션 정보
      • INFO : session : StandardWebSocketSession[id=80821ca7-98ea-fe30-0173-1667c3c26428, uri=ws://localhost:8888/spring/basic] - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:39)
      • 해당 세션의 정보는 StandardWebSocketSession 객체로 표시되며, 세션 ID와 접속 URI 등의 정보가 기록되어 있습니다.
  • 메세지 수신

    • 로그내용

      INFO  : 메세지 수신 - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
      INFO  : session : StandardWebSocketSession[id=80821ca7-98ea-fe30-0173-1667c3c26428, uri=ws://localhost:8888/spring/basic] - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
      INFO  : message : TextMessage payload=[안녕하세요], byteCount=15, last=true] - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
      
    • 메세지 수신
      • INFO : 메세지 수신 - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
    • 세션 정보
      • INFO : session : StandardWebSocketSession[id=80821ca7-98ea-fe30-0173-1667c3c26428, uri=ws://localhost:8888/spring/basic] - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
    • 메세지 정보
      • INFO : message : TextMessage payload=[안녕하세요], byteCount=15, last=true] - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:48)
      • 클라이언트에서 안녕하세요라는 메시지를 전송하였고, 해당 메시지가 서버에서 수신되었습니다.
      • 수신된 메시지의 내용과 길이 등의 정보가 로그에 기록되어 있습니다.
  • 접속 종료

    • 로그내용

      INFO  : 접속 종료 - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:53)
      
    • 접속 종료

      • INFO : 접속 종료 - com.kevin.spring.websocket.server.websocketServer (2024-01-22 15:24:53)
      • 클라이언트의 접속이 종료되었습니다.

7. 웹소켓 클라이언트 동작 분석

웹소켓 클라이언트에서 발생한 로그를 통해 연결, 메세지 수신, 연결 종료 등의 동작을 알수있습니다.

  • 연결성공

    • 클라이언트가 웹소켓 서버에 성공적으로 연결되었습니다

      연결성공 websocketChat:21
      
  • 메세지 수신

    • 서버에서 전송한 메세지가 클라이언트에 도착했습니다.

    • 메세지 내용 및 관련 정보가 로그에 기록되어 있습니다.

      메세지가 도착했습니다. websocketChat:34
      MessageEvent {isTrusted: true, data: '안녕하세요', origin: 'ws://localhost:8888', lastEventId: '', source: null, …} websocketChat:35 
      
  • 접속 종료

    • 클라이언트의 웹소켓 연결이 종료되었습니다.

      연결 종료 websocketChat:25
      

8. 결론

이번 포스팅에서는 스프링 레거시 프로젝트에서 웹소켓을 통한 양방향 통신을 활용하여 실시간 채팅 기능을 구현하였습니다.

프로젝트 설정에서는 pom.xml에 웹소켓 관련 의존성을 추가하였습니다. 그리고 웹소켓 서버 파일을 구현할 때는 TextWebSocketHandler 클래스를 사용하여 간단한 메소드를 구현하였습니다. 이를 통해 클라이언트가 서버에 접속하면, 메시지를 수신하면 서버에서 클라이언트로 동일한 메시지를 다시 전송하는 기본적인 웹소켓 서버를 만들었습니다.

웹소켓 서버를 servlet-context.xml에 등록하고, 클라이언트에서 해당 서버에 접속할 수 있도록 컨트롤러와 뷰페이지를 구현했습니다. 클라이언트는 HTML과 JavaScript로 간단한 UI를 구성하고, 서버에 연결하여 메시지를 주고받을 수 있었습니다.

또한, 로그를 통해 웹소켓 서버에서 발생한 이벤트와 클라이언트 동작에 대한 내용을 분석하고 기록하였습니다. 연결, 메세지 수신, 연결 종료 등의 동작에 대한 로그를 통해 서버와 클라이언트 간의 상호작용을 확인할 수 있었습니다.

다음 포스팅에서는 하나의 세션에 여러 사용자가 채팅을 사용하는 그룹 채팅과 로그인 세션을 이용한 1:1 채팅기능을 구현해보겠습니다.

감사합니다.

Comments