스프링 채팅기능(2023.07.25)

2023. 7. 25. 20:20SPRING

(1) 웹 소켓이란?

- 소켓은 웹에서 동작하는 Socket을 의미한다. 웹은 기본적으로 비연결통신이다. 한번 수신하고 끝내는 식이다. 전화처럼 계속 연결되어있고, 실시간으로 소통하는 것이 아니라 문자 한번 보내면 연결은 끝난다. 웹은 이런 식으로 비연결형 통신이다. 

- 웹은 일회성 연결만 가능하다.

- 소켓은 계속 연결되어 있다. 마치 종이컵 두 개를 가지고, 전화할 수 있는 것이 웹소켓이다. 채팅할 때도 필요하고, 실시간 데이터가 관리되어야 하는 빗썸과 같은 가상자산 플랫폼에서는 소켓을 많이 활용한다.

- 웹소켓을 활용하면 서버가 클라한테 마음대로 데이터를 받으라 할 수 있다. 

- 클라이언트가 서버한테 "정보 좀 주세요~" 하지 않아도 서버가 알아서 데이터를 보내줄 수 있다. 이게 엄청난 장점.

- 소켓을 사용하지 않는다면, ajax를 사용해야 하지만 그건 비효율적이다.

- 양방향으로 실시간으로 소통 가능하려면 .... 서버 측에서도 메시지를 보낼 수 있어야 한다.

 

(2) 스프링에서 웹소켓 사용

- pom.xml 수정을 해줘야함

- dependency 추가

- dependency를 추가한 다음에, 라이브러리를 사용해야함

- 라이브러리를 사용하는 방법은 bean을 등록해서 사용하거나 import 해서 사용할 수 있음(스프링 자체의 버전과 동일하게 맞춰줘야함)

1.상속

2.메소드 오버라이딩

- KhChat 클래스 만들어서 라이브러리를 먼저 활용하여 코드 작성함.

- bean으로 등록하긴 할건데, KhChat 빈 등록함. -> servletContext에 담기

- 맵핑(해당 bean이 언제 동작할지 맵핑 설정)

 

 

(3) 실습 코드

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅 메인</title>
</head>
<body>
	<h1>채팅</h1>
	
	
	<input type="text" id="msg">
	<button onclick="f01();">전송</button>
	
	
	<hr>
	
	<div id="result"></div>
	
	<script type="text/javascript">
		const resultDiv = document.querySelector("#result");
		
		//웹소켓 만들기
		//()안에 연결할 경로 지정하기
		//http:// 는 비연결형 통신방식이기 때문에 이 경로를 사용하면 안됨
		//그래서 웹소켓 프로토콜을 사용해야 한다.
		//그래서 앞에 ws:를 다는 것이다.
		let ws = new WebSocket("ws://200.200.200.3:8888/app/test");
		
		//웹소켓이 오픈되었을때 
		ws.onopen = funcOpen;
		
		//웹소켓 연결이 끝났을때
		ws.onclose = funcClose;
		
		//웹소켓 에러가 날때
		ws.onerror = funcError;
		
		//웹소켓 메시지 받았을때
		ws.onmessage = funcMessage;
		
		
		//웹소켓 오픈되었을때 실행되는 함수
		function funcOpen() {
			console.log("소켓 연결됨");
		}
		
		//웹소켓 닫았을때 실행되는 함수
		function funcClose() {
			console.log("소켓 닫힘");
		}
		
		//웹소켓 에러가 날 때 실행되는 함수
		function funcError() {
			console.log("소켓 에러남");
		}
		
		
		//웹소켓 메시지 받았을때 실행되는 함수
		function funcMessage(event) {
			console.log("소켓 통해서 메시지 받음");
			
			//받은 데이터를 파싱하기
			const data = JSON.parse(event.data);
			resultDiv.innerHTML += '<div>'+"<strong>["+ data.nick +"]</strong>"+"<span>"+ data.msg+"</span>"+"<sub>"++"</sub>"+'</div>';
		}
		
	
		
		//버튼 클릭시 웹소켓 메시지 보내기
		function f01() {
			const userMsg = document.querySelector("#msg").value;
			
			ws.send(userMsg);
		}
	</script>
</body>
</html>

 

 

java

servlet-context.xml

<!-- 웹소켓 bean 등록 -->
	<beans:bean id="khWebsocketServerBean" class="com.kh.app.websocket.server.WebsocketServer"></beans:bean>	

	<!-- 웹소켓 핸들러 맵핑 -->
	<websocket:handlers>
		<websocket:mapping handler="khWebsocketServerBean" path="/test"/>
		<!-- 웹소켓 세션에다가 httpsession을 옮기도록 도와주는 handshaker -->
		<websocket:handshake-interceptors>
			<beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"></beans:bean>
		</websocket:handshake-interceptors>
	</websocket:handlers>

 

webSocketServer.java

package com.kh.app.websocket.server;

import java.sql.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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;

import com.google.gson.Gson;

import lombok.extern.slf4j.Slf4j;


@Slf4j
public class WebsocketServer extends TextWebSocketHandler {
	//유저들의 세션을 담을 set
	private Set<WebSocketSession> sessionSet = new HashSet<WebSocketSession>();
	
	//connection이 되었을때 되고 난이후에 동작하는 메소드
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		log.info("called...afterConnectionEstablished");
		
		//sessionSet에 세션 저장(여러명이 연결)
		sessionSet.add(session);
		
		
		
		
	}
	
	//connection이 closed되고 난 이후에 동작하는 메소드
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		log.info("called...afterConnectionClosed");
		
		//sessionsSet에 저장된 세션 삭제
		sessionSet.remove(session);
	}
	
	
	//텍스트 메시지를 다루는 메소드
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		log.info("called...handleTextMessage");
		
		String nick = (String)session.getAttributes().get("loginMember");
		
		//json으로 데이터를 파싱하기
		Gson gson = new Gson();
		
		//맵 만들어서, 키 밸류 형태로 데이터를 준비하기
		//만들어진 맵을 JSON 형태로 변환
		//변환된 데이터를 send하기
		
		Map<String, String> msgVo = new HashMap<>();
		msgVo.put("nick", nick);
		msgVo.put("msg", message.getPayload());
		
		//이 시각이 발신시각은 아님
		//발신시각을 받으려면 클라이언트 측에서 시간 값도 같이 보내줘야함
		//long 타입이라서 String type으로 변환
		msgVo.put("time", new Date() + "");
		
		
		String jsonStr = gson.toJson(msgVo);
		
		
		//전달받은 메세지를 모두에게 뿌려주기
		//연결된 모든 세션을 가져와서 send 해주기
		for(WebSocketSession s : sessionSet) {
			
			s.sendMessage(new TextMessage(jsonStr));
		}	
	}
}

 

pom.xml

 

<!-- 웹소켓 -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-websocket</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>
		
	
		<!-- gson -->
		<dependency>
		    <groupId>com.google.code.gson</groupId>
		    <artifactId>gson</artifactId>
		    <version>2.10.1</version>
		</dependency>

(4) 데이터 해석

INFO : com.kh.app.websocket.server.WebsocketServer - 클라이언트로부터 전달받은 메시지 : TextMessage payload=[ㅋㅋㅋ 안녕~~~!], byteCount=20, last=true]

 

- send()해서 받은 데이터를 찍어보면 이렇다. last는 이 메시지가 마지막인지를 알려준다. 엄청난 양의 데이터를 보내면 그 데이터가 여러번에 끊어서 갈 것이다. 마지막 데이터가 들어왔는지를 확인하기 위한 목적으로 'last'가 존재한다.

 

 

(5) 그룹채팅

- 서버에서는 연결된 모든 사람들을 알아야함

- 그리고 메세지를 받으면, 연결된 모든 곳에 뿌리기(broadCast)

- HTTPSESSION 정보를 WebSocket 세션이 넘기기

1. servlet-context에 handshaker를 추가해주면 된다.

 

'SPRING' 카테고리의 다른 글

스프링 설정 파일 template  (0) 2023.06.19
생명주기(Life Cycle)  (0) 2023.04.19
의존객체 선택  (0) 2023.03.23
의존객체 자동 주입  (0) 2023.03.23
스프링 설정 파일 분리  (0) 2023.03.22