본문 바로가기
프로젝트/파이널프로젝트-대학 행정 그룹웨어

[문제] 나갔다 들어오면 채팅이 안보임

by moca7 2024. 11. 19.

 

 

 

ㅁ 문제 발생

 

 

 

 

- user1과 user2가 채팅방 1에 접속해서 서로 메세지를 보냈다. 지금은 정상적으로 메세지가 잘 보인다.

 

 

 

 

 

 

 

 

- 문제는 user1이 다른 채팅방을 갔다가 다시 이전에 있던 채팅방으로 돌아와서 메세지를 보내면, 

계속 채팅방 1에 접속해있던 user2는 그 메세지가 보이지만 user1은 자기 메세지도 상대가 보낸 메세지도 화면에 보이지 않는다.

 

 

 

 

 

 

 

ㅁ 원인

 

 

- 맨 처음에는 채팅방 1에 연결되어 있는 세션이 2개다.

 

 

 

 

- user1이 다른 방에 갔다가 다시 채팅방 1로 돌아와서 메세지를 보낸 경우, 현재 채팅방 1에 연결되어 있는 세션이 1개다.

user2의 세션은 남아있지만 user1의 세션은 제거되고 다시 추가가 되지 않고 있다.

 

 

 

 

 

 

ㅁ 해결

 

@RequiredArgsConstructor
@Component // serlvet-context.xml에 빈 등록 구문 대체
@Slf4j // 로그 출력을 위한 롬복 어노테이션
public class ChatEchoHandler extends TextWebSocketHandler {
	

    // 각 채팅방의 세션 리스트를 관리하는 맵 (roomNo, userId)
    private Map<String, Map<String, WebSocketSession>> chatRooms = new ConcurrentHashMap<>();
    
    // 각 채팅방에 사용자가 참여하고 있는지 여부를 flag로 저장하는 맵 (roomNo, userId)
    private Map<String, Map<String, Boolean>> userStatusMap = new ConcurrentHashMap<>();
    
    private final ChatService chatService;
    

    
    /**
     * 웹소켓에 클라이언트가 연결되었을 때 처리할 내용 정의
     * @param session - 현재 웹소켓과 연결된 클라이언트 객체 (채팅방에 접속된 클라이언트)
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        String roomNo = session.getUri().getQuery().split("roomNo=")[1];
        String userId = ((UserDto) session.getAttributes().get("loginUser")).getUserId(); 
        // log.debug("session Attributes 목록: {}", session.getAttributes()); // {sessionId=xxxx, loginUser=MemberDto객체}
        
        session.getAttributes().put("roomNo", roomNo); // roomNo 저장
        session.getAttributes().put("userId", userId); // userId 저장       
        
        log.debug("{} --> User {} has entered room {}", "ChatEchoHandler의 afterConnectionEstablished 실행됨", userId, roomNo);
        

        // 현재 roomNo를 key값으로 가지는 List<WebSocketSession>이 없으면 생성한다.
        if ( !chatRooms.containsKey(roomNo) ) {
            chatRooms.put(roomNo, new ConcurrentHashMap<>() );
        }
        
        // 현재 roomNo를 key값으로 가지는 Map<String, Boolean>이 없으면 생성한다.
        if ( !userStatusMap.containsKey(roomNo) ) {
            userStatusMap.put(roomNo, new ConcurrentHashMap<>() );
        }
              

        // 사용자가 현재 roomNo 채팅방에 없었거나 퇴장상태인 경우에는 입장처리한다.
        if ( !userStatusMap.get(roomNo).containsKey(userId) || userStatusMap.get(roomNo).get(userId) == false ) {  

            
        	userStatusMap.get(roomNo).put(userId, true); // 이 userId를 현재 roomNo에 입장처리. (Map에 이미 있는 키로 put을 호출하면 기존 키에 매핑된 값이 새로운 값으로 덮어쓰기 됨)
            log.debug("{} 유저가 {} 채팅방 번호에 입장처리됨.", userId, roomNo);
        	
            chatRooms.get(roomNo).put(userId, session); // 현재 roomNo를 key로 List<WebSocketSession>에 현재 클라이언트 session을 추가한다. 
            

            // 입장 메시지 생성 및 전송
            String msg = "entry|" + userId + " 님이 " + roomNo + " 채팅방에 입장하였습니다.";
            log.debug("입장 메세지 : {}\n", msg);
            
            
            for (WebSocketSession sss : chatRooms.get(roomNo).values() ) { 
            	sss.sendMessage(new TextMessage(msg));
            }
        	// .values()는 Java의 Map 인터페이스에서 제공하는 메서드로 Map에 저장된 값들(value)만을 Collection 형태로 반환합니다. 즉, 키(key)에 상관없이 Map에 저장된 값들을 한꺼번에 처리하고 싶을 때 사용됩니다.
            // Map<K, V>에서 .values()를 호출하면 V 타입의 모든 값(value)이 포함된 Collection<V> 객체를 반환합니다. 반환된 Collection은 Iterable 인터페이스를 구현하고 있으므로, for-each 루프에서 사용할 수 있습니다.
        }
        
        // 클라이언트의 session이 현재 roomNo의 List<WebSocketSession>에 없다면 추가한다.
        if( chatRooms.get(roomNo).get(userId) == null ) {
        	chatRooms.get(roomNo).put(userId, session);
        }
        
        
    }

 

- chatRooms.get(roomNo).get(userId)가 null이면 추가한다.

 

 

 

- 나갔다 다시 와도 잘 된다.

 

 

 

 

 

- user001의 세션이 잘 들어갔다.