-
스프링으로 소켓구현 : 세션 관리백엔드 : 서버공부/Spring 2024. 7. 15. 15:48728x90
현재 프로젝트에서 WebRTC를 통한 실시간 서비스를 개발중이다. 그에따라 나는 스프링 부트로 시그널링서버를 구축하는 역할을 맡게 되었다.
생각보다 순탄했다. WebRTC에서 시그널링서버는 클라이언트가 P2P로 서로의 정보만 최초교환 할 수 있게 해주면 된다.잘 알려진 코드들과 스프링부트의 라이브러리들로 소켓핸들러를 구현하였다.
소켓핸들러는 크게 3-4가지 정도의 함수들로 구성된다.
afterConnectedEstablished(),handleTextMessage(),afterConnectionClosed(),broadcastMessage()
먼저 afterConnectedEstablished()함수는 소켓 연결된 후 호출되는 함수이다.나는 이 함수에게 세션을 소켓 URI에서 추출한 유저의 아이디, 스터디룸의 아이디를 기반으로 매핑시키는 역할을 주었다.
아이디어는 이러하다.유저아이디는 해쉬맵을 선언한뒤 키값을 맴버아이디로하고 각 맴버의 세션을 Value 값으로 하여 관리한다.
스터디룸또한 소켓 URI에서 스터디룸의 아이디를 기반으로 해쉬맵을 선언하여 관리하였는데스터디룸은 유저세션의 집합이라고 볼 수 있기때문에 Set<WebSocketSession> 을 Value 값으로 하였다.
스터디룸자체는 세션이 없다. 스터디룸은 유저 세션들의 모임이라고 볼 수 있다. 따라서 아래와같이 반복문을 돌며 메세지를 보내면 스터디룸 내부인원들에게 모두 메세지가 전달 되도록 하였다.
/** 반복문 돌면서 자신의 세션을 제외한 각세션에게 메세지를 뿌림! */ private void broadcastMessage(String studyroomId, String userId, String message) { Set<WebSocketSession> sessions = studyroomSessions.get(studyroomId); if (sessions != null) { for (WebSocketSession s : sessions) { if (!s.getId().equals(userId)) { try { s.sendMessage(new TextMessage(message)); } catch (Exception e) { log.error("Failed to send message to session {}", s.getId(), e); } } } } }
이를 응용하여 스터디룸 내부에서 특정 유저만을 호출하고싶다면 특수한 조건을 넣어 그 유저의 세션에만 메세지를 보내게하면된다.
아래는 메세지가 @로 시작하면 그 특정 유저에게만 메세지를 보내는 조건이다.
if (payload.startsWith("@")) { int index = payload.indexOf(':'); if (index != -1) { String targetUserId = payload.substring(1, index); String directMessage = payload.substring(index + 1); log.info("direct call from {}: {}", targetUserId, directMessage); sendMessageToUser(targetUserId, "[" + userId + " (call)] : " + directMessage); return; }
이로써 각 스터디룸과 맴버의 세션을 아이디를 기반으로 관리함으로써 스터디원 팀원들끼리 메세지를 주고받을 수 있게 되었다.
그런줄 알았다..
세션은 "연결돤 상태이다" 이러한 개념을 간과하고 구현한 결과
다음과 같은 재밌는 일이 생겼다.맴버1이 스터디룸1에 접속한다.
이어서 다른 맴버들이 같은 스터디룸에 입장하고 서로 메세지를 주고받을 수 있다!
3은 유저1에게 다이렉트 메세지를 보낼 수 있다!
이때 맴버1이 스터디룸2에도 접속한다.맴버 4도 입장했다.
스터디룸 2에서도 메세지가 잘 주고받아진다!
이떄 스터디룸1의 맴버3이 맴버1에게 다이렉트 메시지를 보낸다!
????에? 이상하게도 스터디룸1에서 맴버1에게 도착한 다이렉트 메세지는 없다..
그럼어디로 갔을까????
뜬금없이 스터디룸2의 맴버1에게 다이렉트메세지가 도착해있다! 맴버3은 스터디룸2에 속해있지도 않은데 말이다!
이유가 무엇일까?그이유는 다이렉트 메세지원리와 afterConnectedEstablished()함수 내부에서 유저세션을 관리하는 방식에있다.
아래는 afterConnectedEstablished()함수의 일부이다.
String userId = getUserId(session); userSessions.put(userId, session);
현재 유저아이디를 키값으로 유저의 세션을 관리중이다.
그리고 다이렉트 메세지는 유저아이디기반으로 그 유저아이디에 매핑된 세션에 메세지를 보내도록하였다.
유저의 세션이 유저 아이디로만 관리되기때문에 서로 다른 스터디룸에대한 세션이 관리되지않는 것이다.
위 경우처럼 특정유저가 다른 스터디룸에 동시에 접속하게된다면, 유저아이디에 매핑되어있던 세션값이 최신값으로 매핑되게된다.
때문에 맴버3이 다이렉트 보낼때 사용한 세션은 맴버1이 스터디룸1에 접속할때 생성되었던 세션이 아닌 맴버1이 스터디룸2에 접속할때 생성되었던 세션인것이다.
이를 그럼 어찌해결할까?
단순하다. 관계형데이터베이스의 관계테이블처럼 두가지의 식별자를 조합해서 고유한 키값을 만들어내면 된다.
나는 아래처럼 스터디룸 아이디와 유저 아이디를 조합하여 세션을 관리하기로 하였다.userSessions.put(userId + "_" + studyroomId, session); // 유저 ID와 스터디룸 ID를 키로 사용
더 좋은 방법이 있겠지만 우선 이렇게 해결하였고
앞으로 더 공부해보려고 한다.
'백엔드 : 서버공부 > Spring' 카테고리의 다른 글
흔히 보이는 애노테이션 1탄 (0) 2024.08.03 DispatcherServlet 이야기... (0) 2024.08.02 EC2, REDIS,Docker로 CI/CD 구축하기 (0) 2024.07.10 <스프링 기초 - 자바> 2차원 배열 정렬 (0) 2024.05.23 레디스 도입하기 [기초] (0) 2024.05.05