본문 바로가기
Java

WebSocket으로 실시간 채팅 구현하기.(springboot)

by 완기 2021. 4. 13.
728x90
반응형

오늘은 웹소켓을 이용해서 실시간 채팅을 구현하면서 공부한 내용을 기록한다.

 

일단 웹소켓에 대해 잘 모른다면 여기를 참고하자

 

웹소켓은 클라이언트에서 주기적으로 서버 측에 데이터 확인 요청을 하는

polling 기법과 달리 새로운 데이터가 들어오면 먼저 서버가 클라이언트에게 데이터를 전송하는 기술이다.

 

기존에 client 와 server의 관계는

 

서버는 가동중이고 클라이언트에게 리소스를 줄 준비를 한다.

이후 클라이언트가 서버에 요청을 해서 html 등 여러 리소스를 받아 브라우저에 표시했다.

 

즉, 무조건적인 클라이언트의 요청이 먼저 있어야 서버가 그에 응답하는 방식이었다.

 

하지만 html5에서 등장한 웹소켓은 위에 언급했듯 서버가 클라이언트에게 먼저 데이터를 주는 역할을 한다. (데이터가 있으면)

 

이런 기존 클라이언트/서버의 관계를 탈피하는 양방향 통신 기술이 등장하면서 이를 이용한 기능들이 생겨났다.

 

웹에서 실시간 알림,실시간 채팅 등 실시간이라는 키워드가 들어가는 기능들은 대부분 이 웹소켓 기술을 이용한다.

 

 

설명은 여기까지 마치고 공부했던 내용을 기록하기로 한다.

 


사용 기술 : 

spring boot 2.4

spring-boot-starter-websocket

gradle

 

 

일단 프로젝트를 생성하고 인텔리제이 기준으로 spring boot starter websocket을 의존성을 추가해주면 된다.

 

implementation 'org.springframework.boot:spring-boot-starter-websocket'

 

 

 

그다음 프로젝트 구조는 위 이미지와 같다.

VO객체는 추후에 user를 구분하기 위해 만들었으며 사용하지 않아도 상관없다.

 

당장 필요한건 config와 controller이다.

 

300x250

 

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    // connection을 맺을때 CORS 허용
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/").setAllowedOrigins("*").withSockJS();
    }
}

WebSocketConfig.java

 

WebSocketConfig는 웹소켓이 관련 설정 파일이다.

 

일단 컨테이너 등록을 위해 @Configuration을 등록해주고

@EnableWebSocketMessageBroker는 웹소켓 서버 사용 설정이다.

 

상속받은 메서드에 endpoint는 양 사용자 간 웹소켓 핸드 셰이크를 위해 지정한다.

 

 

@Controller
@ServerEndpoint("/websocket")
public class MessageController extends Socket {
    private static final List<Session> session = new ArrayList<Session>();

    @GetMapping("/")
    public String index() {
        return "index.html";
    }

    @OnOpen
    public void open(Session newUser) {
        System.out.println("connected");
        session.add(newUser);
        System.out.println(newUser.getId());
    }

    @OnMessage
    public void getMsg(Session recieveSession, String msg) {
        for (int i = 0; i < session.size(); i++) {
            if (!recieveSession.getId().equals(session.get(i).getId())) {
                try {
                    session.get(i).getBasicRemote().sendText("상대 : "+msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                try {
                    session.get(i).getBasicRemote().sendText("나 : "+msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MessageController.java

컨트롤러는 위와 같이 구성했다.

웹소켓 단순 구현을 위한 샘플 프로젝트라 뷰를 위한 컨트롤러와 메시지 매핑을 위한 컨트롤러를 구분하진 않았지만

 

나중에 추후 고도화 작업에서 구분해야겠다.

 

일단 index.html 뷰를 리턴하기 위한 메서드가 있다.

 

사용자가 입장 시 index.html을 리턴하고,

index.html에서 웹소켓을 연결하기 위한 주소로 @ServerEndpoint()를 지정한다.

 

그리고 필드에는 사용자 정보를 담기 위해 session list를 선언하고 

사용자가 페이지에 접속할 때 실행되는 @OnOpen메서드에서 세션 리스트에 담아준다.

 

사용자가 증가할 때마다 세션의 getId()는 1씩 증가하며 문자열 형태로 지정된다.

 

이와 같이 두 브라우저에서 사용자가 접속했으므로 

첫 번째 사용자의 세션 아이디는 0

두 번 째는 1 이런 식이다.

session list의 size를 이용하면 이와 같이 사용할 수 있다.

 

 

 

두 번째로는 @OnMessage 메서드인데,

 

사용자로부터 메시지를 받았을 때, 실행된다.

 

@OnMessage
    public void getMsg(Session recieveSession, String msg) {
        for (int i = 0; i < session.size(); i++) {
            if (!recieveSession.getId().equals(session.get(i).getId())) {
                try {
                    session.get(i).getBasicRemote().sendText("상대 : "+msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                try {
                    session.get(i).getBasicRemote().sendText("나 : "+msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

위 메서드를 설명하자면 메세지를 받았을 때, 세션을 가져온다.

 

그 후 세션에 담겨있는 모든 사용자에게 메세지를 전달하기 위해 session의 size만큼 반복문을 돌려주고,

 

메세지를 보낸 사람의 SessionId와 SessionList의 Id가 같지 않으면 상대방이 보낸 메시지,

 

아이디가 같다면 내가 보낸 메시지다. (메시지를 보낸 이도 SessionList에 포함되어있기 때문에 위와 같이 사용)

 

위처럼 사용하게 되면 내가 보낸 메시지, 상대가 보낸 메시지를 구분하여 보낸다.

 

 

그리고 서버쪽에선 마지막으로 메인메서드가 있는 클래스에 

@Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

위 메서드를 선언해주어야 한다.


 

서버 구성은 이만하면 됐고, 이제는  index.html의 화면 구성을 보자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" placeholder="보낼 메세지를 입력하세요." class="content">
    <button type="button" value="전송" class="sendBtn" onclick="sendMsg()">전송</button>
<div>
    <span>메세지</span>
    <div class="msgArea"></div>
</div>
</body>
<script>
        let socket = new WebSocket("ws://localhost:8080/websocket");

        socket.onopen = function (e) {
            console.log('open server!')
        };

        socket.onerror = function (e){
            console.log(e);
        }
        
        socket.onmessage = function (e) {
            console.log(e.data);
            let msgArea = document.querySelector('.msgArea');
            let newMsg = document.createElement('div');
            newMsg.innerText=e.data;
            msgArea.append(newMsg);
        }

        function sendMsg() {
            let content=document.querySelector('.content').value;
            socket.send(content);
        }
</script>
</html>

마찬가지로 샘플 프로젝트라서 별거 없다.

이게 전부다 ㅋㅋㅋㅋㅋ

 

일단 let socket객체를 생성하면서 인자로 아까 서버 측 컨트롤러에서 설정한 @ServerEndpoint의 인자 값과 일치시켜준다.

 

이렇게 되면 서버와 연결을 하고, 

 

socket.onopen은 서버와 연결됐을 때, 발생하는 함수.

onerror은 에러 발생 시 함수.

onmessage가 메시지를 받았을 때, 함수다.

 

사실 너무 간단해서 설명할 것이 별로 없어서 해당 결과물을 바로 보자.

서버를 가동 후, 페이지에 접속하면 서버 측 콘솔에 위와 같이 로그가 찍히고,

 

 

실제 아래 영상과 같이 구현할 수 있다.

728x90
728x90

댓글