Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

J.one_DevNote

{Spring Boot} - Web Socket 채팅 만들기 본문

Spring Boot

{Spring Boot} - Web Socket 채팅 만들기

중엔 2022. 7. 7. 13:55

Web Socket이란?

기존 단방향 통신이었던 HTTP와 달리 양방향 통신을 제공하기 위해 개발된 프로토콜이다. Web Socket은 HandShake 과정을 통해 커넥션을 생성하며, 이 요청은 방화벽 설정이 필요없이 80, 443 포트를 이용하여 양방향 통신을 하게된다. 또한 HTTP규격을 그대로 유지할 수 있기 때문에 HTTP인증, CORS 등을 동일하게 적용할 수 있다는 장점이 있다.

HTTP vs AJAX vs WebSocket

HTTP는 URL을 요청을 통한 Request/Response 형식으로 서버와 상호 작용을 합니다.AJAX는 XMLHttpRequest객체를 통해 웹서버에 요청하고, 서버는 XML, JSON, Text형식으로 응답하여줍니다.Web Socket은 요청을 응답한후 연결을 끊지않고 그대로 유지하며 데이터를 전송합니다.

 

Web Socket 이전에 사용된 방식

  • Polling : 클라이언트가 HTTP Request를 서버로 계속 요청해 이벤트 내용을 전달 받는 방식

  • Long Polling : 클라이언트가 서버로 HTTP Request 요청을 하고 연결을 유지하다가 서버에서 클라이언트로 전달할 이벤트가 있다면 Response하고 종료한다.

  • Streaming : Long Polling 과 마찬가지로 클라이언트가 서버로 Reqeust요청을 보내고, 서버에서는 연결을 끊지않고 계속 Response를 보내주는 방식

왜 WebSocket을 사용하는가?

기존 HTTP방식은 요청에 의한 응답을 받아 새로운 화면을 보여주었고, Ajax는 요청에 의한 응답을 JSON형식으로 응답하여 DOM의 일부분을 바꿀 수 있게 해주었다. 하지만 사용자들은 점점 더 긴밀히 상호작용하는 웹서비스를 원했고, Long Polling, Stream등과 같은 다양한 방법으로 서비스되었지만 이 방식들 역시 단방향 방식을 유지하고있다.

하지만 WebSocket은 서버에서 데이터를 받을 수 있을 뿐만아니라 클라이언트도 호출할 수 있기 때문데 기존의 Ajax와는 차이를 보인다. 예를들어 Ajax로 채팅프로그램을 만든다면 10초에 한번씩 호출하여 화면을 갱신해줄수는 있지만, 웹소켓을 사용한다면 실시간으로 상호작용하는 채팅프로그램을 만들 수 있는것이다.

주의점

  • HTTP와 달리 연결을 계속 유지해야하며, 연결이 비정상적으로 끊어졌을 경우를 대비해야한다
  • 연결을 계속 유지하는것 자체가 트래픽이 많은 서버에게 부담을 줄 수 있다
  • 오래된 브라우저는 지원하지 않는다.(**지원하지 않는 브라우저들이 많기 때문에 Socket.io(nodejs)/ SockJS(Spring)를 많이 사용한다)

WebSocket을 사용한 예시 

  • 구글 Doc에서 여러 명이 동시에 접속해 수정하는 Tool
  • 증권거래 정보 사이트/어플
  • 화상채팅 사이트/어플
  • 위치기반 사이트/어플
  • 등등

Spring Boot - WebSocket을 이용하여 채팅프로그램 만들기

1. pom.xml에 dependency추가

		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-websocket</artifactId>
		    <version>5.3.18</version>
		</dependency>

2. 소켓 핸들러 생성

package com.ex.toypj;

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

import org.springframework.stereotype.Component;
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;

@Component
public class WebSocketHandler extends TextWebSocketHandler{
    private static List<WebSocketSession> list = new ArrayList<>();
    
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("payload : " + payload);

        for(WebSocketSession sess: list) {
            sess.sendMessage(message);
        }
	}
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		// TODO Auto-generated method stub

        list.add(session);

        System.out.println(session + " 클라이언트 접속");
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		// TODO Auto-generated method stub

		System.out.println(session + " 클라이언트 접속 해제");
        list.remove(session);
	}
}

3. 핸들러를 이용해 소켓을 활성화하기 위해서 Config를 작성한다.

setAllowedOrigins는 도메인 설정

package com.ex.toypj.util;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import com.ex.toypj.WebSocketHandler;

import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final WebSocketHandler websockethandler = new WebSocketHandler();

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    
        registry.addHandler(websockethandler, "ws/socketChat").setAllowedOrigins("*");
    }
}

4. 채팅 컨트롤러 생성

package com.ex.toypj;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.ex.toypj.vo.SiteVO;

@Controller
public class MainController {
	@RequestMapping("/socketChat")
	public String webSocketChat() {
		return "/jsp/webSocketChat";
	}
}

5.  JSP및 script

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>웹소켓테스트</title>
	<link href="/css/insert.css" rel="stylesheet" />
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
	<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
</head>
<body class="text-center">
	<div style="margin: 0 auto;width:800px;height:500px;">
		<div style="width: inherit;height: 450px;border: 1px solid;overflow: auto;" id="msgArea">
			
		</div>
		<div style="width: inherit;height: 50px;">
			<div style="float: left;height: inherit;width: 700px;">
				<input type="text" style="width: inherit;height: inherit;" id="msg">
			</div>
			<div style="float: right;width: 100px;height: inherit;">
				<button type="button" style="width: inherit;height: inherit;" id="button-send">보내기</button>
			</div>
		</div>
	</div>
</body>
<script type="text/javascript">
$(document).ready(function(){
	
	const username = sessionStorage.loginId
	const websocket = new WebSocket("ws://localhost:8080/ws/socketChat");
	
	websocket.onmessage = onMessage;
	websocket.onopen = onOpen;
	websocket.onclose = onClose;
    $("#button-send").on("click", (e) => {
        send();
    });
        
    function send(){

	     let msg = document.getElementById("msg");
	
	     console.log(username + ":" + msg.value);
	     websocket.send(username + ":" + msg.value);
	     msg.value = '';
     }
   	function onClose(evt) {
        var str = username + ": 님이 방을 나가셨습니다.";
        websocket.send(str);
    }
      
    //채팅창에 들어왔을 때
    function onOpen(evt) {
        var str = username + ": 님이 입장하셨습니다.";
        websocket.send(str);
    }
    function onMessage(msg) {
        var data = msg.data;
        var sessionId = null;
        //데이터를 보낸 사람
        var message = null;
        var arr = data.split(":");

        for(var i=0; i<arr.length; i++){
            console.log('arr[' + i + ']: ' + arr[i]);
        }

        var cur_session = username;

        //현재 세션에 로그인 한 사람
        console.log("cur_session : " + cur_session);
        sessionId = arr[0];
        message = arr[1];

        console.log("sessionID : " + sessionId);
        console.log("cur_session : " + cur_session);

        //로그인 한 클라이언트와 타 클라이언트를 분류하기 위함
        if(sessionId == cur_session){
            var str = "<div>";
            str += "<div class='col-6 alert alert-secondary' style='text-align: left;'>";
            str += "<b>" + sessionId + " : " + message + "</b>";
            str += "</div></div>";
            $("#msgArea").append(str);
            $("#msgArea").scrollTop($("#msgArea")[0].scrollHeight);
        }
        else{
            var str = "<div style='display: flex;flex-direction: row-reverse;'>";
            str += "<div class='col-6 alert alert-warning' style='text-align: left;'>";
            str += "<b>" + sessionId + " : " + message + "</b>";
            str += "</div></div>";
            $("#msgArea").append(str);
            $("#msgArea").scrollTop($("#msgArea")[0].scrollHeight);
        }
    }
    
    window.onbeforeunload = function() {
    	websocket.close();
    }
})
</script>
</html>

 

결과

****socket close시 이슈 추후 해결해서 올릴 예정

 

 

채팅앱을 설계하며 배운 내용

클라이언트-서버 간의 통신 방식의 이해를 더하고 데이터가 어떻게 전달되는지 알아보기 위해 카카오톡이나 페이스북 메신저 같은 채팅방을 기반으로 채팅을 진행하는 안드로이드 앱을 만들어

forceson.github.io

 

 

[WEB] 🌐 웹 소켓 (Socket) 정리 (역사부터 차근차근)

​ 웹 개발을 처음 배우기 시작했다면 서버와 클라이언트의 통신은 모두 HTTP 프로토콜만 이용해서 이루어진다고 생각할 수 있습니다. 하지만 웹 개발을 하면서 채팅, 게임, 주식 차트 등의 실시

inpa.tistory.com

 

 

[Spring MVC] Web Socket(웹 소켓)과 Chatting(채팅)

 기존 공부 용도의 게시판(?)에 여러 기능을 추가하던 차, 관리자와 멤버 간 채팅 기능을 구현하고 싶었다. 채팅을 하려면 웹 소켓이 필요하다고 한다. 간단하게 구현하는 것은 어렵지 않으므로

dev-gorany.tistory.com

 

 

WebSocket

이 글은 Spring WebSocket(https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.htmlWebSocket 프로토콜은 표준된 방법으로 서버-클라이언트 간

velog.io

 

Comments