J.one_DevNote
{Spring Boot} - Web Socket 채팅 만들기 본문
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
'Spring Boot' 카테고리의 다른 글
Spring Boot - JPA 와 Hibernate(1) (0) | 2023.01.05 |
---|---|
스프링부트에서 타임리프를 사용하는 이유? (0) | 2022.11.28 |
{Spring Boot} - Maven과 Gradle (0) | 2022.06.27 |
{Spring Boot} - api 연결하기 (0) | 2022.06.22 |
{Spring Boot} - MyBatis사용법 (0) | 2022.06.20 |