관리 메뉴

웹개발자의 기지개

[SpringBoot] WebSocket 을 이용한 채팅구현하기 본문

Java/Spring Boot

[SpringBoot] WebSocket 을 이용한 채팅구현하기

http://portfolio.wonpaper.net 2023. 12. 27. 06:23

 

 

Java SpringBoot , WebSocket 을 이용해서 채팅을 구현해보았다.

 

Java 17

SpringBoot 3.2.1

Gradle, War

 

[ build.gradle ]

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
31
32
33
34
35
plugins {
    id 'java'
    id 'war'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
}
 
group = 'kr.co.alwaysweb'
version = '0.0.1-SNAPSHOT'
 
java {
    sourceCompatibility = '17'
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
 
    //implementation 'javax.servlet:jstl' //스프링부트 3.0 미만
    implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
    implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
    implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
    implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
}
 
tasks.named('test') {
    useJUnitPlatform()
}
cs

 

JSP와 Springboot3.0 이상, websocket 등의 의존성을 추가

 

[ /src/main/resources/application.properties ]

 

1
2
3
4
5
6
7
8
9
# 서버 포트
server.port=8071 
 
# static
spring.mvc.static-locations=/resources/**
 
# JSP
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
cs

 

 

 

[ WebSocketServer.java ]

 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package kr.co.alwaysweb.springbootwebsocket;
 
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Service;
 
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
 
@Service
@ServerEndpoint(value="/chat")
public class WebSocketServer {
    private static Set<Session> clients =
            Collections.synchronizedSet(new HashSet<Session>());
 
 
    @OnOpen
    public void onOpen(Session s) {
        System.out.println("open session : " + s.toString());
        if(!clients.contains(s)) {
            clients.add(s);
            System.out.println("session open : " + s);
        }else {
            System.out.println("이미 연결된 session 입니다.");
        }
    }
 
 
    @OnMessage
    public void onMessage(String msg, Session session) throws Exception{
        System.out.println("receive message : " + msg);
        for(Session s : clients) {
            System.out.println("send data : " + msg);
            s.getBasicRemote().sendText(msg);
        }
    }
 
    @OnClose
    public void onClose(Session s) {
        System.out.println("session close : " + s);
        clients.remove(s);
    }
 
    @OnError
    public void onError(Throwable e) {
        System.out.println("onError : " + e.getMessage());
        e.printStackTrace();
    }
}
 
cs

 

[ WebSocketConfig.java ]

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package kr.co.alwaysweb.springbootwebsocket;
 
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 
@Component
public class WebSocketConfig {
 
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
 
cs

 

 

[ WebSocketChatController.java ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package kr.co.alwaysweb.springbootwebsocket;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
 
@RestController
public class WebSocketChatController {
 
    @RequestMapping("/mychat")
    public ModelAndView chat() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("websoket_chatting");
        return mv;
    }
}
cs

 

 

[ /webapp/WEB-INF/views/webSocket_chatting.jsp ]

 

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
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <link rel='stylesheet' type='text/css' href='./css/chat.css'>
</head>
<body>
<div id='chatt'>
    <h1>WebSocket Chatting</h1>
    <input type='text' id='mid' value='' placeholder="채팅명을 입력해주세요.">
    <input type='button' value='로그인' id='btnLogin'>
    <br/>
    <div id='talk'></div>
    <div id='sendZone'>
        <textarea id='msg' ></textarea>
        <input type='button' value='전송' id='btnSend'>
    </div>
</div>
 
<script src='./js/chat.js'></script>
 
</body>
</html>
cs

 

[ chat.js ]

 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function getId(id){
    return document.getElementById(id);
}
 
var data = {};//전송 데이터(JSON)
 
var ws ;
var mid = getId('mid');
var btnLogin = getId('btnLogin');
var btnSend = getId('btnSend');
var talk = getId('talk');
var msg = getId('msg');
 
btnLogin.onclick = function(){
    ws = new WebSocket("ws://" + location.host + "/chat");
 
    ws.onmessage = function(msg){
        var data = JSON.parse(msg.data);
        var css;
 
        if(data.mid == mid.value){
            css = 'class=me';
        }else{
            css = 'class=other';
        }
 
        var item = `<div ${css} >
                        <span><b>${data.mid}</b></span> [ ${data.date} ]<br/>
                      <span>${data.msg}</span>
                        </div>`;
 
        talk.innerHTML += item;
        talk.scrollTop=talk.scrollHeight;//스크롤바 하단으로 이동
    }
 
    this.disabled = true;
    mid.disabled = true;
}
 
msg.onkeyup = function(ev){
    if(ev.keyCode == 13){
        send();
    }
}
 
btnSend.onclick = function(){
    send();
}
 
function send(){
    if(msg.value.trim() != ''){
        data.mid = getId('mid').value;
        data.msg = msg.value;
        data.date = new Date().toLocaleString();
        var temp = JSON.stringify(data);
        ws.send(temp);
    }
    msg.value ='';
 
}
cs

 

[ chat.css ]

 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@charset "UTF-8";
 
*{
    box-sizing: border-box;
}
 
#chatt{
    width: 800px;
    margin: 20px auto;
}
 
#chatt #talk{
    width: 800px;
    height: 400px;
    overflow: scroll;
    border : 1px solid #aaa;
}
#chatt #msg{
    width: 740px;
    height:100px;
    display: inline-block;
}
 
#chatt #sendZone > *{
    vertical-align: top;
 
}
#chatt #btnSend{
    width: 54px;
    height: 100px;
}
 
#chatt #talk div{
    width: 70%;
    display: inline-block;
    padding: 6px;
    border-radius:10px;
 
}
 
#chatt .me{
    background-color : #ffc;
    margin : 1px 0px 2px 30%;
}
 
#chatt .other{
    background-color : #eee;
    margin : 2px;
}
cs

 

 

 

참고 : https://jobtc.tistory.com/59

참고 : https://dev-gorany.tistory.com/212

참고 : https://velog.io/@prettylee620/8%EC%9B%94-1

 

 

 

nginx + tomcat9 + springboot websocket

Ubuntu tomcat9 설치

다운로드
sudo wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.84/bin/apache-tomcat-9.0.84.tar.gz

압축풀기
sudo tar xzvf apache-tomcat-9.0.84.tar.gz

폴더명 변경
sudo mv apache-tomcat-9.0.84 tomcat9

디렉토리 권한 설정
sudo chown -R tomcat:tomcat tomcat9



포트 열린 전체 목록
sudo ufw status verbose


우분투 방화벽 8071 포트 열기
sudo ufw allow 8071/tcp



/etc/nginx/sites-enabled/default 설정 수정


        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.

            try_files $uri $uri/ =404;
         proxy_pass http://localhost:8071;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header Host $http_host;
        }


nginx 재가동
sudo systemctl restart nginx

 

 

 

참고 : https://yooloo.tistory.com/170

참고 : https://velog.io/@prettylee620/8%EC%9B%94-1

참고 : https://velog.io/@jinjinhyojin/WebSocket-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

참고 : https://velog.io/@ichubtou/Spring-Boot-Web-Socket-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%B1%84%ED%8C%85-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84

 

[Spring Boot] Spring Boot & Web Socket 간단한 채팅 기능 구현

웹 소켓이란 두 프로그램 간의 메세지 교환을 위한 통신 방법 중 하나이다.웹 소켓은 서버와 클라이언트간에 연결을 유지하여 언제든 양방향 통신 또는 데이터 전송이 가능하게 하는 기술이다.R

velog.io

 

 

 

참고 : https://spring.io/guides/gs/messaging-stomp-websocket

 

Getting Started | Using WebSocket to build an interactive web application

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t

spring.io

 

Comments