채팅
비트코인 거래소
리퀘스트 없이 리스폰스 할수 없다
Request를 주기적으로 보내서 Response하는데
Websocet을 쓰면 누군가는 보내고 누군가는 받는게 매끄럽게 이어진다
HTTP 프로토콜 특성상 선 Request > 후 Response 형태를 통신 규칙을 지켜야 하기 때문에 구현하기 어려운 일부 컨텐츠가 비효율적으로 제작됨 이러한 HTTP 특성을 넘어서 일반 네트워크 소켓 통신으로 데이터를 주고 받는기술 Websoclk
웹소캣 maven
<!-- https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api --> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>
> 프로토콜들은 핸드쉐이킹 과정을 통해 주고받는다
=======================
package kh.spring.endpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.google.gson.JsonObject;
import kh.spring.configurator.WSConfigurator;
@ServerEndpoint(value="/chat", configurator = WSConfigurator.class)
public class ChatEndpoint {
//webSocket 이 처음 연결되었을떄 실행될 함수
//접속한 사용자 Session 을 모아두는 컬렉션
private static Set<Session> clients = Collections.synchronizedSet(new HashSet<>());
//static이 필요한 이유, 사용자마다 ChatEndpin는 하나씩 생성 되는데 static을붙이면 한개 list에 사용자 정보들이 담긴다 /static은 한번 생성 되기 떄문에 객체 생성없이 사요악능
//접속자들의 정보를 저장하기 위해 Session 지역변수로 사용하면 안되고 멤버필드로 사용해야 함 컬렉션 set 필요 중복허용 x
private HttpSession hSession;
//접속자의HttpSession객체를 저장할메서드
//접속하는 사람마다 세션을 따로따로 만들어줘야되기떄문에 Autowired사용x
@OnOpen
public void onConnection(Session client, EndpointConfig config) { //websocket session
System.out.println("웹 소캣 연결 확인");
clients.add(client);
this.hSession = (HttpSession)config.getUserProperties().get("hSession");
System.out.println(this.hSession.getAttribute("loginID")+"가 채팅방에 입장하였습니다");//핸드쉐이킹이 끝나고넘어옴
}//웹소캣 연결
@OnMessage
public void onMessage(String msg) { //throws Exception하면 에러가 생길시 다른 사람들이 메시지 확인을못한다 그래서 try catch
System.out.println("도착한 메시지"+ msg);
msg = msg.replace("<", "<");
JsonObject data = new JsonObject();
data.addProperty("ip", (String)this.hSession.getAttribute("IP"));
data.addProperty("sender", (String)this.hSession.getAttribute("loginID"));
data.addProperty("msg", msg);
synchronized (clients) {
//for문이 동작중 사용자가 종료했을때 에러나는 상황, 동시성 오류 방지
for(Session client : clients) {//for문 돌다가 사용자가 종료해서 @Onclose에 의해 삭제되면 동시성 오류가 생긴다 ex)10개로 for문돌다가 9개로 바뀌면 for문 돌아가는 도중 갯수는 변경되면 안된다
try {
client.getBasicRemote().sendText(data.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@OnClose //연결 종료 정보제거
public void onClose(Session client) {
clients.remove(client);
}
@OnError
public void onError(Session client, Throwable t) { //에러가 난 사용자 제거
clients.remove(client);
}
}
package kh.spring.configurator;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
public class WSConfigurator extends Configurator{
@Override //헨드쉐이킹 과정을
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession session = (HttpSession)request.getHttpSession();
sec.getUserProperties().put("hSession", session);
//
}
}
package kh.spring.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@Autowired
private HttpSession session;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletRequest request, String id) {
session.setAttribute("IP", request.getRemoteAddr());
session.setAttribute("loginID", id);
return "home";
}
}
<%@ 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">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-3.6.1.js"></script>
<style>
/* div{
border: 1px solid black;
} */
.container {
width: 500px;
height: 100%;
margin: auto;
}
.box1 {
height: 400px;
border: 1px solid black;
overflow-y: auto;
background-color: wheat;
}
.box2 {
height: 150px;
border: 1px dotted black;
}
.box2>#input {
width: 100%;
height: 80%;
background-color: lavender;
overflow-y: auto;
text-align: top;
}
.box2>#emo {
width: 30px;
height: 30px;
background-color: blueviolet;
}
#emo>button {
width: 30px;
height: 30px;
}
button>img {
width: 100%;
height: 100%;
}
.textbubble {
position: relative;
display: inline-block;
/* max-width: calc(100% - 70px); */
padding: 10px;
margin-top: 7px;
font-size: 15px;
border-radius: 10px;
left: 10px;
background-color: white;
}
#emobox {
width: 100%;
height: 100%;
background-color: beige;
display: none;
}
.emoti {
width: 80px;
height: 80px;
}
.msg-box {
max-width: 250px;
word-wrap: break-word;
border: 1px dotted black;
border-radius: 5px;
margin: 5px;
padding: 5px;
display: inline-block;
}
</style>
<script>
function updateScroll() {
var element = document.getElementsByClassName("box1")[0];
element.scrollTop = element.scrollHeight;
}
$(function() {
let ws = new WebSocket("ws://192.168.150.35/chat");//webSocket 인스턴스생성
//연결
//@OnOpen
ws.onmessage = function(e) {
console.log(e.data);
let data = JSON.parse(e.data);
let text = e.data;
let outer = $("<div>");
let line = $("<div>");
line.addClass("msg-box")
line.append(data.msg);
outer.append(line);
$(".box1").append(outer);
updateScroll();
}
$("#input").on("keydown", function(e) {
if (e.keyCode == 13) {
let text = $("#input").text();
$("#input").text("");
ws.send(text); //@OnMessage로
return false;
}
});
})
</script>
</head>
<body>
<div class="container">
<div class="box1" id="chat"></div>
<div class="box2">
<div id="input" contenteditable="true"></div>
<button>전송</button>
<div id="emo">
<button id="emoadd">
<img src="emoticon1.png" id="econ01">
</button>
</div>
</div>
<div id="emobox">
<img src="emoticon1.png" id="econ01" class="emoti"> <img
src="emoticon2.gif" id="econ02" class="emoti"> <img
src="emoticon3.gif" id="econ02" class="emoti"> <img
src="emoticon4.gif" id="econ02" class="emoti"> <img
src="emoticon5.gif" id="econ02" class="emoti"> <img
src="emoticon6.gif" id="econ02" class="emoti">
</div>
</div>
</body>
</html>
guava 메시지 최근 30개 저장을 위한 라이브러리
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency>