Project2021

[Spring]Spring-websocket으로 간단한 실시간 채팅 구현해보기

뱅타 2021. 5. 12. 23:22

설명

https://m.blog.naver.com/scw0531/221052774287

http통신의 특징과 한계

http통신 특징

  1. client가 server에게 자신이 받고싶은 정보를 request에 담아 전송한다.
  2. server는 client의 request에 따라서 알맞은 response로 응답한다.
  3. client는 server에게 받은 response의 데이터를 사용한다. 즉, client가 자신이 어떤 데이터를 받고싶은지 server에 요청을 해야, server가 그 요청에 맞는 데이터를 제공해 주는 방식이다.

그래서 http통신의 Stateless, Connectionless라는 가장 크고 중요한 특징이 있다.

client가 server에 request를 보내면, 서버는 클라이언트에게 response를 하고, 그 이후 연결이 끊어져 서버와 클라이언트는 독립된 상태를 유지하는 것이다.

Why websocket?

실시간 채팅 기능을 구현하기 위해서는 http통신의 기존 방법으로는 한계가 있다.

실시간 채팅은

  1. client1이 서버에 메세지를 전송하고,
  2. server는 그 메세지를 client2에게 전송하고,
  3. client2는 자신에게 온 메세지를 확인한다.

3단계로 이루어져 있다. 여기서 하나의 문제가, 기본적으로 http통신이란 client가 요청을 해야 server가 응답하는 방식이고,server가 client2에게 메세지가 도착했다는 것을 알리려 해도, client2에게서 request가 없었기 때문에server가 일방적으로 response를 할 수 없는 것.

즉, client2의 입장에서 생각해 봐도 자신의 독립된 환경에서 메세지가 도착했는지, 안했는지 판단할 수 없기 때문에server에 자신에게 온 메세지의 데이터를 전송해달라고 request를 보낼 수가 없다.

물론

  1. ajax를 사용한 비동기적 통신을 통해 주기적으로 한 페이지 안에서 server한테 자신에게 보낼 정보가 있는지 request한다.
  2. 페이지가 이동될 때마다 자신에게 온 정보가 있는지에 대한 질문을 request에 포함시킨다.

이렇게 두가지를 쓸 수 있지만 두가지 방법 다 문제가 있다.

1번은 ajax를 주기적으로 사용해야하고, 계속된 요청으로 서버에 무리가 가며, 실시간도 아니다.2번은 자신이 페이지를 이동 할 때만 묻기 때문에, 채팅이라고 하기에는 문제가 있다.(실제로 1번은 polling이란 방법으로 웹 소켓이 나오기 이전 http통신의 한계를 극복하는데 잠시 쓰였던 역사가 있다.)

이 단점을 보완하기 위해 등장한 것이 웹 소켓이다.웹 소켓은 IETF에 의해 RFC 6455로 표준화된 엄연한 표준 기술이다.웹 소켓은 http와 다르게 전 이중 통신을 지원하기 때문에, clinet의 요청이 없어도 server에서 먼저 client에게 정보를 전송한다.그렇기에 우리는 실시간 채팅 기능을 구현하기 위해서 웹 소켓을 사용해야 하는 것이다.

상당히 잘 설명되어 있어서 가지고 왔습니다.(참조사이트)

https://gnaseel.tistory.com/11

Spring-websocket 사용하기

1.dependency 추가하기

akdnei

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

각자의 버전에 맞게 사용하면 됩니다.

필자는 버전을

  • spring-websocket - 4.3.25.RELEASE
  • jackson-databind - 2.12.1

을 사용했습니다.

spring-websocket은 spring의 웹 소켓 기능을 사용할 수 있게 해주고, jackson은 json 관련 라이브러리인데, 웹 소켓뿐만 아니라 json형식으로 데이터를 주고받는 것들을 위해 추가합니다.

2. bean객체 등록 및 xsi스키마 수정

(servlet-context.xml)

xmlns:websocket="http://www.springframework.org/schema/websocket"
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">

<!-- 웹 소켓 핸들러 -->
        <websocket:handlers>
               <websocket:mapping handler="echoHandler" path="/echo"/>
               <websocket:sockjs />
        </websocket:handlers>
        <beans:bean id="echoHandler" class="kr.or.ddit.controller.EchoHandler" />
<!-- 웹 소켓 핸들러 end -->

기존에 존재하는 servlet-context.xml 파일에 추가해도 되고

따로 websocket-context.xml 등 다른 servlet파일로 등록해 주어도 됩니다.(web.xml을 수정해야 합니다)

**class="kr.or.ddit.controller.EchoHandler" 부분의 basePackagePath는 어떤 클래스에서 웹소켓을 컨트롤할 것인지에 대한 명세이기 때문에 각자 프로젝트에 맞춰서 다르게 기입해야 합니다.

3. EchoHandler 클래스 작성

package kr.or.ddit.controller;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@RequestMapping("/echo")
public class EchoHandler extends TextWebSocketHandler{
    //세션 리스트
    private List<WebSocketSession> sessionList = new ArrayList<WebSocketSession>();

    private static Logger logger = LoggerFactory.getLogger(EchoHandler.class);

    //클라이언트가 연결 되었을 때 실행
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessionList.add(session);
        logger.info("{} 연결됨", session.getId()); 
    }

    //클라이언트가 웹소켓 서버로 메시지를 전송했을 때 실행
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.info("{}로 부터 {} 받음", session.getId(), message.getPayload());
        //모든 유저에게 메세지 출력
        for(WebSocketSession sess : sessionList){
            sess.sendMessage(new TextMessage(message.getPayload()));
        }
    }
    //클라이언트 연결을 끊었을 때 실행
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessionList.remove(session);
        logger.info("{} 연결 끊김.", session.getId());
    }
}

적당한 경로의 파일을 선택한 후 handler class를 생성해 줍니다.

4. View 작성하기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>

<!-- sockjs 라이브러리를 추가해주는 코드이다.

이 부분이 있어야 script부분에서 sockjs 관련 함수를 사용할 수 있다. -->
<script type="text/javascript"
    src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.5/sockjs.min.js"></script>

</head>
<body>
    <input type="text" id="message" />
    <input type="button" id="sendBtn" value="submit"/>
    <div id="messageArea"></div>
</body>
<script type="text/javascript">
    $("#sendBtn").click(function() {
        sendMessage();
        $('#message').val('')
    });

    let sock = new SockJS("http://localhost:80/lunchOrder/echo");
    sock.onmessage = onMessage;
    sock.onclose = onClose;
    // 메시지 전송
    function sendMessage() {
        sock.send($("#message").val());
    }
    // 서버로부터 메시지를 받았을 때
    function onMessage(msg) {
        var data = msg.data;
        $("#messageArea").append(data + "<br/>");
    }
    // 서버와 연결을 끊었을 때
    function onClose(evt) {
        $("#messageArea").append("연결 끊김");

    }
</script>
</html>

💡

이 코드가 존재해야 script에서 sockjs의 function들을 사용할 수 있습니다.

let sock = new SockJS("http://localhost:80/lunchOrder/echo");
    sock.onmessage = onMessage;
    sock.onclose = onClose;

SockJS객체를 생성하고, 그 객체가 메세지를 받고, 연결이 끊길 때 각각 어떤 함수를 호출할건지 세팅해주는 과정

**주의(이 부분을 잘못 기입해서 계속 "연결 끊김"이 출력되었습니다)

constructor의 매개변수에는 자신의 url과 EchoHandler를 맵핑한 주소를 적어주셔야 합니다.

실행 결과

사실 프로젝트 중 알람 기능 때문에 websocket을 사용해야 했는데 채팅 역시 실시간으로 하기 위해선 websocket을 사용하는 것이 바람직한듯 합니다.

 

Uploaded by Notion2Tistory v1.1.0

728x90
반응형