개발목표
1. 채팅 방 접속 시 웹소켓 연결 정보를 찾아 채팅방 메모리에 변수에 저장한다.
2. 커넥션이 끊겼을 때 입장했던 방 사람들에게만 사실을 브로딩 캐스팅한다. 물론 메모리의 데이터는 remove한다.
3. 채팅방 입장 시 방 사람들에게만 사실을 브로딩 캐스팅한다.
4. 유요하지 않은 Text(none Json String)을 보냈더라도 커넥션을 유지한다.
1. 채팅 방 접속 시 웹소켓 연결 정보를 찾아 채팅방 메모리에 변수에 저장한다.
- 채팅방 정보는 WAS메모리에 기록한다.
- 커넥션 성공 시 Map<유저아이디, Session 객체> 로 저장된다.
- 추후 이 Map은 Map<채팅방 String, List<Map>> 형태로 2중 저장된다.(채팅방 입장 시)
- Map으로 선택한 이유는 HashMap을 사용해 빠른 탐색을 하기 위해서이다. (List의 경우 for문을 돌릴 수 밖에 없어서)
public void createOrJoinGrpNoByUserSession(Map<String, Object> inParam) throws Exception {
if(BzUtils.isEmpty(WebSocketConfig.getSessionMap().get(inParam.get(DBMeta.uid.name())))) {
inParam.put(ReturnMeta.message.name(), "연결된 소켓 통신이 없습니다.");
throw new Exception();
}
if (BzUtils.isNotEmpty(inParam.get(DBMeta.uid.name()))
&& BzUtils.isNotEmpty(inParam.get(DBMeta.grpNo.name()))) {
Map<String, List<Map<String, WebSocketSession>>> sessionListIncludeMap = WebSocketConfig
.getGrpNoSessionListIncludeMap();
List<Map<String, WebSocketSession>> sessionList = new ArrayList<Map<String, WebSocketSession>>();
// Step1 그룹 맵 중 grpNo가 있으면 해당 리스트에 접속정보 넣기
if (BzUtils.isNotEmpty(sessionListIncludeMap.get(inParam.get(DBMeta.grpNo.name())))) {
sessionList = sessionListIncludeMap.get(inParam.get(DBMeta.grpNo.name()));
removeSessionElementByUid(sessionList,(String)inParam.get(DBMeta.uid.name()));
sessionList.add(Map.of((String) inParam.get(DBMeta.uid.name()),
WebSocketConfig.getSessionMap().get(inParam.get(DBMeta.uid.name()))));
}
// Step1_1 그룹맵이 없다면 grpNo를 키로 그룹리스트를 생성한다.
else {
sessionList.add(Map.of((String) inParam.get(DBMeta.uid.name()),
WebSocketConfig.getSessionMap().get(inParam.get(DBMeta.uid.name()))));
sessionListIncludeMap.put((String) inParam.get(DBMeta.grpNo.name()), sessionList);
}
}
}
- 웹소켓 연결시 인터셉터가 연결가능 여부를 판단한다.
- 개발 편의 상 at의 payload 대신 url query String으로 받아서 Key를 만든다.
String atValue = UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams()
.getFirst(ProcessMeta.at.name());
if (BzUtils.isNotEmpty(atValue)) {
sessionUid=atValue;
}
httpSession.put(ProcessMeta.clientSession.name(),sessionUid);
2. 커넥션이 끊겼을 때 입장했던 방 사람들에게만 사실을 브로딩 캐스팅한다. 물론 메모리의 데이터는 remove한다
- 채팅 방 입장 시 입장 성공 정보는 DB에 기록한다.
- 입장 시 저장했던 메모리 변수에서 유저 커넥션 정보를 찾는다.
public void removeConnectionMemoryByUid(String uid) throws IOException {
Map<String, List<Map<String, WebSocketSession>>> sessionListIncludeMap = WebSocketConfig
.getGrpNoSessionListIncludeMap();
// 커넥션이 끊어졌을 때 접속이력(그룹번호)을 찾는다.
Map<String, Object> inParam = new CamelHashMap();
inParam.put(DBMeta.uid.name(), uid);
List<CamelHashMap> grpList = groupOrderMapper.selectGrpOrdInfoByUid(inParam);
if(BzUtils.isEmpty(grpList)) {
return;
}
// 그룹 번호로 방을 찾고 그 방에서 제거한다.
for (CamelHashMap grpMap : grpList) {
List<Map<String, WebSocketSession>> sessionList = sessionListIncludeMap.get(grpMap.get(DBMeta.grpNo.name()));
if(BzUtils.isEmpty(sessionList)) {
return;
}
removeSessionElementByUid(sessionList,uid);
//커넥션이 종료되었을 때 그룹멤버들에게 종료정보를 전달한다.
inParam.put(DBMeta.grpNo.name(), grpMap.get(DBMeta.grpNo.name()));
Map<String, Object> broadCastMap = new CamelHashMap();
broadCastMap.put(DBMeta.grpNo.name(), grpMap.get(DBMeta.grpNo.name()));
broadCastMap.put(ReturnMeta.refreshSid.name(), "GROUP0004");
inParam.put(ReturnMeta.data.name(), broadCastMap);
broadcastGrpNoMember(inParam);
}
}
3. 채팅방 입장 시 방 사람들에게만 사실을 브로딩 캐스팅한다.
- 2. 내용과 비슷하다.
public void broadcastGrpNoMember(Map<String, Object> inParam) throws IOException {
List<Map<String, WebSocketSession>> sessionList = new ArrayList<Map<String, WebSocketSession>>();
Map<String, List<Map<String, WebSocketSession>>> sessionListIncludeMap = WebSocketConfig
.getGrpNoSessionListIncludeMap();
if (BzUtils.isNotEmpty(sessionListIncludeMap.get(inParam.get(DBMeta.grpNo.name())))) {
sessionList = sessionListIncludeMap.get(inParam.get(DBMeta.grpNo.name()));
Gson gson = new Gson();
inParam.remove(DBMeta.usrPower.name());
List<CamelHashMap> grpList = groupOrderMapper.selectGrpOrdInfoByGrpNo(inParam);
for (CamelHashMap grpMap : grpList) {
String uid = (String) grpMap.get(DBMeta.uid.name());
for (Map<String, WebSocketSession> map : sessionList) {
WebSocketSession wss = map.get(uid);
if(BzUtils.isNotEmpty(wss)) {
wss.sendMessage(new TextMessage(gson.toJson(inParam.get(ReturnMeta.data.name()))));
}
}
}
}
}
4. 유요하지 않은 Text(none Json String)을 보냈더라도 커넥션을 유지한다.
- handleTextMessage 안에 try catch문을 사용해서 커넥션 익셉션으로 인한 종료를 방지했다.
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
Map<String, Object> servieRet = new CamelHashMap();
Gson gson = new Gson();
servieRet.put(ReturnMeta.code.name(), CommonConstants.ERROR);
servieRet.put(ReturnMeta.message.name(), "미지정오류");
try {
...
} catch (Exception e) {
...
session.sendMessage(new TextMessage(gson.toJson(servieRet)));
}
}
앞으로 체크할 일
- 위의 로직은 TextWebSocketHandler afterConnectionClosed 메서드가 잘 작동하길 바라면서 작성했다.
- 로직이 잘 작동하지 않으면 static 변수의 크기가 너무 커질 수 있다.
- WAS가 이중화 되어있으면 커넥션을 담는 static 변수 공유가 안된다. 그래서 메모리에 채팅방 정보를 없으면 insert하게 설계했다.
- 브로드캐스팅 할 트리거가 이중화된 WAS에서 어떻게 구성할지 고민된다.(카프카 or 레디스?)
'IT 기술 > 개념정리' 카테고리의 다른 글
Debezium 커넥터 복구 방법 (1) | 2024.03.28 |
---|---|
유사 채팅방 구현하기(1) (0) | 2024.02.02 |
Debezium 개념정리 (0) | 2024.01.23 |