Spring websocket 채팅

w ho
9 min readJul 27, 2020

--

특징 :
Web Browser에서 Request를 보내면 Server는 Response를 준다. HTTP 통신의 기본적인 동작 방식이다.

하지만 Server에서 Client로 특정 동작을 알려야 하는 상황도 있다. 예를 들어 Browser로 Facebook에 접속해 있다가 누군가 친구가 글을 등록하는 경우, 혹은 Web Browser로 메신저를 구현하는 경우다. WebSocket이 있기 전에는 이를 Polling이나 Long polling 등의 방식으로 해결했었다. 하지만 WebSocket의 등장으로 Server-Client 간의 실시간 통신이 가능하게 되면서, 앞으로 Long polling은 역사의 뒤안길로 사라질 것 같다.

WebSocket이란 HTTP 환경에서 전이중 통신(full duplex, 2-way communication)을 지원하기 위한 프로토콜

HTTP 프로토콜에서 Handshaking을 완료한 후, HTTP로 동작을 하지만, HTTP와는 다른 방식으로 통신을 한다.

Http Polling : 클라이언트가 지속적으로 서버로 request를 하여 이벤트를 수신하는 방식이다. 가장 간단한 방법이지만, 지속적으로 서버에 요청을 던지기 때문에 서버의 오버헤드를 고려할 수 밖에 없는 상황이다.
Http Long Polling: polling에 비해 클라이언트는 이벤트를 받기 전까지 다음 요청을 날리지 않는다. 하지만, 원하는 이벤트를 얻기 위해 지속적으로 요청해야한다는 점에서 서버의 부담은 여전히 증가된다.
Http Streaming: 서버는 클라이언트로부터 request를 받으면, response을 주고 연결을 끊지 않고. 이벤트가 발생함에 따라 클라이언트로 전송하는 방식인데 역시나 근본적인 원인인 해결하지 못한다.

물론 위의 방식으로 원하는 데이터를 클라이언트와 서버간의 주고 받는데 문제는 없다. 하지만 HTTP 프로토콜 특성의 request-response의 지속적인 수행 그리고 그에 따른 중복적인 패킷전달(http-header) 문제로 인해 속도 저하 및 오버헤드는 근본적으로 문제가 있게 된다.

쉽게 말하면, 내가 원하는 데이터에 비해 동반되는 데이터들이 너무 많고, 지속적으로 이 데이터를 포함해야 하며 맺고 끊는 연결을 계속하는 등 리소스의 낭비가 크다는 점이다.

그래서 웹 브라우저 환경에서 tcp 통신처럼 연결 지향 프로토콜이 필요했는데 이를 해결하기 위해, 2011년 Websocket 프로토콜이 탄생하게 되었다.

그렇다면, Websocket과 TCP는 어떤 차이가 있는 걸까?

1. 웹소켓은 연결 요청에 대해 http를 통해 switching 및 Handshaking이 이루어진다. (웹소켓 프로토콜 분석하기)

2. TCP는 Binary 데이터만 주고 받을 수 있지만, Websocket은 Binary 데이터 뿐만 아니라 Text 데이터를 주고 받을 수 있다

탄생 배경과 정의 그리고 특성으로 미루어 보아, WebSocket은 HTTP와 TCP의 특성을 섞어 놓은 프로토콜이며

결국 핵심은 웹 브라우저 환경에서 연결지향 통신하기 위한 기술이라는 점이다.

spring에서 제공하는 websoket을 한번 적용해보기
우선적으로 pom.xml에 spring-websocket 추가

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

chatHandler라는 객체를 TextWebSocketHandler를 상속 시켜 만든다.

@Componentpublic class ChatHandler extends TextWebSocketHandler {private static List<WebSocketSession> list = new ArrayList<WebSocketSession>();//클라이언트가 접속 했을 때 호출될 메소드//클라이언트가 접속 했을 때 호출되는 메소드@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {list.add(session);System.out.println("하나의 클라이언트가 연결됨 ");}//클라이언트가 메시지를 보냈을 때 호출되는 메소드@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 전송된 메시지를 List의 모든 세션에 전송String msg = message.getPayload();for (WebSocketSession s : list) {s.sendMessage(new TextMessage(session.getAcceptedProtocol()+":"+msg));}}// 클라이언트의 접속이 해제 되었을 때 호출되는 메소드@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("클라이언트와 연결 해제됨");list.remove(session);}
}

servelt-context.xml 에 namespace에 추가

//servelt-context.xml
//만들었던 Chathandler 를 매핑
<websocket:handlers>
<websocket:mapping handler="chatHandler" path="/chat-ws"/></websocket:handlers>

채팅을 구현할 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><div id="one">별명:<input type="text" id="nickname" /> <input type="button"id="enter" value="입장" /></div><div id="two" style="display: none"><input type="button" id="exit" value="퇴장" /><br /><div id="chatarea" style="width:400px; height:600px; border:1px solid;"></div><input type="text" id="message" /> <input type="button" id="send"value="보내기" /></div></body><script type="text/javascript">one = document.getElementById("one");two = document.getElementById("two");document.getElementById("enter").addEventListener("click", function() {//웹 소켓 연결해주는 함수 호출connect();});document.getElementById("exit").addEventListener("click", function() {//연결을 해제해주는 함수 호출disconnect();});document.getElementById("send").addEventListener("click", function() {//연결을 해제해주는 함수 호출send();});var websocket;//입장 버튼을 눌렀을 때 호출되는 함수function connect(){websocket = new WebSocket("ws://localhost:8080/chat-ws");//웹 소켓에 이벤트가 발생했을 때 호출될 함수 등록websocket.onopen = onOpen;websocket.onmessage = onMessage;websocket.onclose = onClose;}//퇴장 버튼을 눌렀을 때 호출되는 함수function disconnect(){msg = document.getElementById("nickname").value;websocket.send(msg+"님이 퇴장하셨습니다");websocket.close();}//보내기 버튼을 눌렀을 때 호출될 함수function send(){nickname = document.getElementById("nickname").value;msg = document.getElementById("message").value;websocket.send(nickname + ":"+ msg);document.getElementById("message").value = "";}//웹 소켓에 연결되었을 때 호출될 함수function onOpen(){nickname = document.getElementById("nickname").value;two = document.getElementById("two");two.style.display='block';websocket.send(nickname + "님 입장하셨습니다.");}//웹 소켓에서 연결이 해제 되었을 때 호출될 함수function onMessage(evt){data= evt.data;chatarea = document.getElementById("chatarea");chatarea.innerHTML = data + "<br/>" + chatarea.innerHTML}function onClose(){}</script></html>

결과

--

--

No responses yet