본문 바로가기
개발/Spring

[Spring] WebSocket 을 이용한 메시지 전송

by 코딩하는 흰둥이 2025. 6. 12.

이전글

https://greed-yb.tistory.com/280

 

[SpringBoot] WebSocket 채팅방 구현(1) - 구현 화면

사용 중Bootstrap v4.6.0DataTables org.springframework.boot spring-boot-starter-websocket CSS 및 Class 명까지 그대로 올리니 나중에 구현할 때 감안할 것 직원 목록 및 채팅방 목록을 DataTables 로 구현하였으니DataTable

greed-yb.tistory.com

 

WebSocket 의 경우 새로고침 또는 메뉴 이동client 접속이 해제되었다가 재연결을 하는데

이전에 만들었던 코드에서는 같은 session Id중복으로 넣거나 최신 session Id 를 가져오는 코드가 없어서

이전에 만들었던 구현 코드를 개선하여 다시 작업하였다

이전 코드의 문제점으로는 상대방의 session Id 가 변경되면 메시지를 전송할 수가 없다는 점이다

 

 

 

 

WebSocketConfig
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;



@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final WebSocketHandler webSocketHandler;

    public WebSocketConfig(WebSocketHandler webSocketHandler){
        this.webSocketHandler = webSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ws/{username}/test.do").setAllowedOrigins("*");
    }
}

Spring framework mvc 에서 동작하고 있어서 대상 파라미터 값이 중간에 들어가 있다

.do 뒤로 /{파라미터} 는 구조상 동작하지 않는다

 

 

WebSocketHandler

 

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.stereotype.Component;
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;

@Component
public class WebSocketHandler extends TextWebSocketHandler {

	// 접속중인 유저의 session 및 채팅방 정보를 담기위한 변수
    List<HashMap<String, Object>> rls = new ArrayList<>();

    // 메시지 send 할 때 동작
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 사용자가 전송한 데이터를 JSON 으로 변경
        String payload = message.getPayload();
        JSONObject obj = jsonToObjectParser(payload);

        // 메시지를 받는 상대방 id
        String id = String.valueOf(obj.get("userId"));
        System.out.println("메시지 받는 상대방 id : " + id);
       
        
        // 상대방 id를 가지고 session 정보를 찾아 담기 위한 변수
        HashMap<String, Object> temp = new HashMap<>();
        if (rls.size() > 0) {
            for (int i = 0; i < rls.size(); i++) {
                HashMap<String, Object> sessionId = rls.get(i);

                if (sessionId.size() > 1 && sessionId.get("userId").equals(id)) {
                    // sessionId가 실제로 WebSocketSession 객체인지 확인
                    temp = sessionId;
                    temp.put("userId", id);
                    break;
                }
            }
        }
        
        boolean sent = false;

        for (String key : temp.keySet()) {
            if (key.equals("userId")) {
                continue;
            }
            WebSocketSession wss = (WebSocketSession) temp.get(key);
            if (wss != null) {
                try {
                    wss.sendMessage(new TextMessage(obj.get("roomDetail").toString()));
                    sent = true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        
        
     // 위 방식으로 메시지를 보내지 못했을 경우, findSessionByUserId() 를 이용하여 최신 sessionId 조회
        if (!sent) {
            WebSocketSession receiverSession = findSessionByUserId(id);
            if (receiverSession != null && receiverSession.isOpen()) {
                try {
                    receiverSession.sendMessage(new TextMessage(obj.get("roomDetail").toString()));
                    System.out.println("메시지 재전송");
                } catch (Exception e) {
                    System.err.println("메시지 전송 중 오류");
                    e.printStackTrace();
                }
            } else {
                System.err.println("userId [" + id + "]에 대한 세션이 존재하지 않거나 닫혀 있음");
            }
        }
  
}

    // client 접속
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    	System.out.println("웹소켓 연결됨: " + session.getId());
    	
        super.afterConnectionEstablished(session);
        boolean flag = false;
        String url = session.getUri().toString();
        
        URI uri = new URI(url);
        String path = uri.getPath();  // "/ws/TEST/test.do"

        String[] parts = path.split("/");

        String userId =  parts[2]; 
        
        System.out.println("afterConnectionEstablished : " + userId);
        int idx = rls.size();
        if (rls.size() > 0) {
            for (int i = 0; i < rls.size(); i++) {
                String id = (String) rls.get(i).get(i);
                if (id != null && id.equals(session.getId())) {
                    flag = true;
                    idx = i;
                    break;
                }
            }
        }

        if (flag) {
            HashMap<String, Object> map = rls.get(idx);
            map.put(session.getId(), session);
        } else {
            HashMap<String, Object> map = new HashMap<>();
            map.put(session.getId(), session);
            map.put("userId", userId);
            rls.add(map);
        }

        System.err.println("client 접속");
        
        rls.removeIf(map -> userId.equals(map.get("userId")));  // 중복 제거

        HashMap<String, Object> backupMap = new HashMap<>();
        backupMap.put("userId", userId);
        backupMap.put(session.getId(), session);
        rls.add(backupMap);

        System.err.println("client 재접속 userId 세션 추가 완료: " + userId);
    }

    
    
    // client 접속 해제
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.err.println(session + " client 접속 해제");

        Iterator<HashMap<String, Object>> iter = rls.iterator();

        while (iter.hasNext()) {
            HashMap<String, Object> map = iter.next();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                // userId 키는 제외하고, 나머지는 WebSocketSession 이라고 가정
                if (!entry.getKey().equals("userId")) {
                    WebSocketSession savedSession = (WebSocketSession) entry.getValue();
                    if (savedSession.getId().equals(session.getId())) {
                        iter.remove(); // 안전하게 삭제
                        System.out.println("세션 제거 완료: " + session.getId());
                        break;
                    }
                }
            }
        }

        super.afterConnectionClosed(session, status);
    }

    // send 로 전달한 메세지 변환
    private static JSONObject jsonToObjectParser(String jsonStr) {
        JSONParser parser = new JSONParser();
        JSONObject obj = null;
        try {
            obj = (JSONObject) parser.parse(jsonStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return obj;
    }
    
    
    // 메시지 전송 받는 userId 의 최신 session 조회
    public WebSocketSession findSessionByUserId(String userId) {
        for (HashMap<String, Object> sessionMap : rls) {
            if (userId.equals(sessionMap.get("userId"))) {
                for (Map.Entry<String, Object> entry : sessionMap.entrySet()) {
                    if (!entry.getKey().equals("userId")) {
                        return (WebSocketSession) entry.getValue();
                    }
                }
            }
        }
        return null;
    }
    

}

 

 

 

 

Javascript
<script type="text/javascript">

	var socket;
    // 로그인한 사용자 ID
	var username = 'TEST';
	
	// 웹소켓 연결
	function widgetWebsocket(username){
		socket = new WebSocket("ws://본인IP:port/ws/"+ username + "/test.do");
		socket.onopen = () => {
		  console.log("WebSocket connection opened");
		};
	
		socket.onerror = (error) => {
		  console.error("WebSocket error", error);
		};
	
		socket.onclose = (event) => {
		  console.log("WebSocket closed", event);
		};
		
		socket.onmessage = (msg) => {
			console.log("메세지 전달받는중");
			let message = msg.data;
	        console.log("message : " + message);
	        
            // 필요에 따라 로직 구현부
            
		}
	}
    
    	// 메시지 전송 
    	function apiTestBtn(){
		// 메시지 전송할 대상들
    		var targetUserId = [];
    		targetUserId.push("balju01");
    		targetUserId.push("sigong01");
    		targetUserId.push("TEST");
    	
    		var senMsg = "웹소켓 테스트 메시지 전송";
    	
    		if(socket == null){
    			widgetWebsocket(username);
        	}
    	
      		for (var i = 0; i < targetUserId.length; i++) {
    			console.log("wwwww : " + i);
    			let widgetData = new Object();
            	widgetData.userId = targetUserId[i];
            	widgetData.roomDetail = senMsg;

            	// 채팅방 웹소켓에 전송
            	socket.send(JSON.stringify(widgetData));
		} 
		
    }
    
    
    
    

$(function() {
	// 웹소켓 연결
	widgetWebsocket(username);

	// 30분마다 웹소켓 상태 체크 및 재연결
    setInterval(function() {
    	console.log("웹소켓 연결 여부 확인 중");
        if (!socket || socket.readyState === WebSocket.CLOSED) {
        	console.log("웹소켓이 닫혀 있음. 재연결 시도...");
            widgetWebsocket(username);
        }
	}, 30 * 60 * 1000); // 30분 - 30 * 60 * 1000
});
</script>

 

로그인 사용자 username 은 본인 구조에 맞게 바인딩 시켜주자

 

 

 

TEST

WebSocket 으로 임의의 메시지를 보내기 위한 테스트 폼

 

 

chrome 에서 계정1('sigong01') 을 로그인 

Edge 에서 계정2('TEST') 를 로그인

 

 

전송 버튼을 클릭하면 

임의의 하드코딩한 계정 3개에 메시지를 전송한다

 

 

 

왼쪽 : sigong01 계정의 브라우저 console / 오른쪽 : 서버 로그

 

 

현재 로그인한 사용자가 본인을 포함한 2명이라

전송 버튼 클릭 시 본인에게도 메시지를 전송하였기 때문에 테스트 폼에 전달받은 메시지가 출력되었고

sigong01 계정의 브라우저에 전달한 메시지를 확인할 수 있다

 

회사에서 중간중간 작성하던 글이라 내용이나 설명이 많이 부족할 수 있다...

댓글