설명
https://m.blog.naver.com/scw0531/221052774287
http통신의 특징과 한계
http통신 특징
- client가 server에게 자신이 받고싶은 정보를 request에 담아 전송한다.
- server는 client의 request에 따라서 알맞은 response로 응답한다.
- client는 server에게 받은 response의 데이터를 사용한다. 즉, client가 자신이 어떤 데이터를 받고싶은지 server에 요청을 해야, server가 그 요청에 맞는 데이터를 제공해 주는 방식이다.
그래서 http통신의 Stateless, Connectionless라는 가장 크고 중요한 특징이 있다.
client가 server에 request를 보내면, 서버는 클라이언트에게 response를 하고, 그 이후 연결이 끊어져 서버와 클라이언트는 독립된 상태를 유지하는 것이다.
Why websocket?
실시간 채팅 기능을 구현하기 위해서는 http통신의 기존 방법으로는 한계가 있다.
실시간 채팅은
- client1이 서버에 메세지를 전송하고,
- server는 그 메세지를 client2에게 전송하고,
- client2는 자신에게 온 메세지를 확인한다.
3단계로 이루어져 있다. 여기서 하나의 문제가, 기본적으로 http통신이란 client가 요청을 해야 server가 응답하는 방식이고,server가 client2에게 메세지가 도착했다는 것을 알리려 해도, client2에게서 request가 없었기 때문에server가 일방적으로 response를 할 수 없는 것.
즉, client2의 입장에서 생각해 봐도 자신의 독립된 환경에서 메세지가 도착했는지, 안했는지 판단할 수 없기 때문에server에 자신에게 온 메세지의 데이터를 전송해달라고 request를 보낼 수가 없다.
물론
- ajax를 사용한 비동기적 통신을 통해 주기적으로 한 페이지 안에서 server한테 자신에게 보낼 정보가 있는지 request한다.
- 페이지가 이동될 때마다 자신에게 온 정보가 있는지에 대한 질문을 request에 포함시킨다.
이렇게 두가지를 쓸 수 있지만 두가지 방법 다 문제가 있다.
1번은 ajax를 주기적으로 사용해야하고, 계속된 요청으로 서버에 무리가 가며, 실시간도 아니다.2번은 자신이 페이지를 이동 할 때만 묻기 때문에, 채팅이라고 하기에는 문제가 있다.(실제로 1번은 polling이란 방법으로 웹 소켓이 나오기 이전 http통신의 한계를 극복하는데 잠시 쓰였던 역사가 있다.)
이 단점을 보완하기 위해 등장한 것이 웹 소켓이다.웹 소켓은 IETF에 의해 RFC 6455로 표준화된 엄연한 표준 기술이다.웹 소켓은 http와 다르게 전 이중 통신을 지원하기 때문에, clinet의 요청이 없어도 server에서 먼저 client에게 정보를 전송한다.그렇기에 우리는 실시간 채팅 기능을 구현하기 위해서 웹 소켓을 사용해야 하는 것이다.
상당히 잘 설명되어 있어서 가지고 왔습니다.(참조사이트)
https://gnaseel.tistory.com/11
Spring-websocket 사용하기
1.dependency 추가하기
akdnei
<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>2.12.1</version>
</dependency>
각자의 버전에 맞게 사용하면 됩니다.
필자는 버전을
- spring-websocket - 4.3.25.RELEASE
- jackson-databind - 2.12.1
을 사용했습니다.
spring-websocket은 spring의 웹 소켓 기능을 사용할 수 있게 해주고, jackson은 json 관련 라이브러리인데, 웹 소켓뿐만 아니라 json형식으로 데이터를 주고받는 것들을 위해 추가합니다.
2. bean객체 등록 및 xsi스키마 수정
(servlet-context.xml)
xmlns:websocket="http://www.springframework.org/schema/websocket"
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<!-- 웹 소켓 핸들러 -->
<websocket:handlers>
<websocket:mapping handler="echoHandler" path="/echo"/>
<websocket:sockjs />
</websocket:handlers>
<beans:bean id="echoHandler" class="kr.or.ddit.controller.EchoHandler" />
<!-- 웹 소켓 핸들러 end -->
기존에 존재하는 servlet-context.xml 파일에 추가해도 되고
따로 websocket-context.xml 등 다른 servlet파일로 등록해 주어도 됩니다.(web.xml을 수정해야 합니다)
**class="kr.or.ddit.controller.EchoHandler" 부분의 basePackagePath는 어떤 클래스에서 웹소켓을 컨트롤할 것인지에 대한 명세이기 때문에 각자 프로젝트에 맞춰서 다르게 기입해야 합니다.
3. EchoHandler 클래스 작성
package kr.or.ddit.controller;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
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());
}
}
적당한 경로의 파일을 선택한 후 handler class를 생성해 줍니다.
4. View 작성하기
<%@ 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>
<!-- sockjs 라이브러리를 추가해주는 코드이다.
이 부분이 있어야 script부분에서 sockjs 관련 함수를 사용할 수 있다. -->
<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:80/lunchOrder/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>
💡
이 코드가 존재해야 script에서 sockjs의 function들을 사용할 수 있습니다.
let sock = new SockJS("http://localhost:80/lunchOrder/echo");
sock.onmessage = onMessage;
sock.onclose = onClose;
SockJS객체를 생성하고, 그 객체가 메세지를 받고, 연결이 끊길 때 각각 어떤 함수를 호출할건지 세팅해주는 과정
**주의(이 부분을 잘못 기입해서 계속 "연결 끊김"이 출력되었습니다)
constructor의 매개변수에는 자신의 url과 EchoHandler를 맵핑한 주소를 적어주셔야 합니다.
실행 결과
사실 프로젝트 중 알람 기능 때문에 websocket을 사용해야 했는데 채팅 역시 실시간으로 하기 위해선 websocket을 사용하는 것이 바람직한듯 합니다.
'Project2021' 카테고리의 다른 글
[Spring] RequestMappingHandlerMapping , Resources 우선순위 설정하기 (0) | 2021.05.29 |
---|---|
[Spring] websocket 사용하기(1) (2) | 2021.05.27 |
[Error] Failed to load JavaHL Library (0) | 2021.05.12 |
[API] ToastUI 사용하기(markdown_editor) (0) | 2021.05.11 |
[DB] mac에서 Robo 3T 설치하기(brew) (0) | 2021.05.11 |