Chanho_Park

[WebSoket] 스프링 웹소켓 사용하여 실시간 채팅 구현 본문

Spring

[WebSoket] 스프링 웹소켓 사용하여 실시간 채팅 구현

Chanho_Park 2022. 11. 18. 14:49
728x90

 

저번 프로젝트에서 스프링버전이 낮아 사용하지 못해서

ajax으로 reload 시켜서 새로고침으로 실시간 되는 것처럼 진행했었는ㄷ네

 

이번 프로젝트에는 스프링을 5.x.x 버전 으로 올려서 웹소켓 사용이 가능하게 되었다.

 

먼저 

 

 

http 통신

  • Client 가 server에게 받고 싶은 정보를 request에 담아 전송
  • server는 client의 request에 따라 response로 응답
  • Client 는 server에게 받은 response의 데이터를 사용                                                                                                      ( Client가 자신이 어떤 데이터를 받고싶은지 server에 요청해야 데이터를 제공해주는 방식 )

 

실시간 채팅이라하면 1번이 서버에 메세지를 전송 server는 2에게 전송 2는 그 메세지를 받는 것이다.

 

기본적으로 http 통신이란 client 가 요청을 해야 server가 응답하는 방식

server 가 메세지를 도착했다는 것을 알리려해도 2번의 request가 없기 때문에 response가 없음

 

 

그래서 저는 저번에 그런 2번의 request가 없었기에 request를 다시해서 오는 새로고침을 통해 reload하여 요청을 했었다.

그 요청을 받고 response 가 왔기 때문에 실시간 통신처럼 보이는 것이었다.

 

 

웹소켓은 http 와 다르게 전 이중 통신을 지원, 1번의 요청이 없어도 server에서 먼저 1번에게 정보를 전송한다.

 

 

[ 구현 ]

 

먼저 Spring의 버전은 3.x.x 이상이어야 에러가 안나기 때문에 업그레이드 하는 것을 추천

 

버전 확인 법 pom.xml 에 맨 위쪽에 존재

 

먼저 pom.xml 에 의존성 추가 해준다.

Spring-websocket은 웹소켓 기능을 사용할 수 있게 해줌.

Jackson-databind는 웹소켓이 json형식으로 데이터를 주고 받기 때문에 추가

 

<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>${jackson.version}</version>
    </dependency>

 

 

 

그 다음 bean 객체 등록 및 xsi 스키마 수정

src-main-webapp-WEB-INF-spring-appServlet에 있는 Servlet-context.xml

 

 

밑 부분에 Namespaces 를 눌러

 

 

 

websocket을 클릭하여 추가해준다.(알림창이 뜰 경우 무시)

 

다시 namespaces 옆 source 를 누르고 아래와 같은 코드 추가

 

* handler="echoHandler"와 id = "echoHandler"부분을 일치시켜 맵핑

* class="com.sp.ex.EchoHandler" 부분의 class는 실제적으로 어떤 클래스에서 웹소켓을 컨트롤할 것인지에 대한 명세

 

그렇기에 class 부분은 EchoHandler.java를 넣을 패키지로 지정

<websocket:handlers>
		<websocket:mapping handler="echoHandler" path="/echo" />
		<websocket:sockjs />
	</websocket:handlers>
    
	<beans:bean id="echoHandler" class="com.spring.echohandler.EchoHandler"></beans:bean>

그런 다음 package 를 생성해주고 

채팅을 할 jsp or html 로 갈 Controller를 작성해주고

그 통신을 가운데서 연결해줄 EchoHandler를 작성

 

 

* EchoController 작성

package com.spring.echohandler;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class EchoController {
	
	@RequestMapping("/chat")
	public String echo() {
		System.out.println("여긴옴?");
		return "Chat/Chat";
	}
}

* EchoHandler 작성

package com.spring.echohandler;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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());
    }
}

*  TextWebSocketHandler 클래스를 상속받아서 사용함

* 클라이언트가 접속할 때, 메세지를 보낼 때, 접속을 끊었을 때 각각 override된 메소드가 동작

 

 

 

 

* JSP 작성

 

Controller에서 return "/Chat/Chat" 으로 하였으니 WEB-INF 의 Chat폴더 안의 Chat.jsp 생성하여 작성

 

 

<%@ 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>

<!-- 소켓 라이브러리 추가해주는 코드 -->
<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:8080/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>

 

테스트를 할 때에는 다른 edge나 explorer 를 틀어서 확인하는 것이 좋음

브라우저 유형마다 한개의 session 이 가능하기 때문

 

이걸 이용하여 다른 사이트에도 있는 실시간채팅구현 가능

 

728x90