μμ μ μΈ μ€μκ° μλ°©ν₯ ν΅μ μλΉμ€λ₯Ό μ 곡νκΈ° μν΄ μμ WebSocketμ νμ©νμ¬ λ©μΈμ§ νμ±κ³Ό λΌμ°ν , μμΈ μ²λ¦¬ μμ€ν μ ꡬννμμ΅λλ€. μ΄ κ³Όμ μμ λ©μΈμ§ νλ‘ν μ½ μ μ, λμμ± λ¬Έμ ν΄κ²°, AOPμ ν΅ν μμΈμ²λ¦¬ λ± λ€μν μ΄λ €μμ ν΄κ²°νμμ΅λλ€.
- CEBONEμ μ€μκ° μλ°©ν₯ ν΅μ μ ν΅ν΄ μλ νλ μ΄μ΄μ ν΄μ λ²κ°μ κ°λ©° μ ν¬λ₯Ό νλ κ²μμ λλ€.
- νλ μ΄μ΄λ μμ μμ μνλ 무기μ μ₯λΉ λ± μμ΄ν μ ν΅ν΄ μμ μ μΊλ¦ν°λ₯Ό κ°νν μ μμ΅λλ€.
- λ‘λΉμμ 맀μΉλ°©μ μ°Ύμ μ ν¬ν νλ μ΄μ΄λ₯Ό μ νν μ μμ΅λλ€.
- μ ν¬λ₯Ό μλ£νλ©΄ νλνλ κ²½νμΉλ₯Ό ν΅ν΄ λ 벨 μ μ ν μ μμΌλ©° μ΄λ μ λ΅μ μΌλ‘ μλ‘μ΄ μ€ν¬μ μ΅λνμ¬ μ±μ₯ λ°©ν₯μ κ²°μ ν μ μμ΅λλ€.
- Version : Java 17
- IDE : IntelliJ
- Framework : SpringBoot 3.1.5
- ORM : JPA
- Real Time Networking : WebSocket
- νμ κ°μ λ° λ‘κ·ΈμΈ
- λ‘λΉ - λ°© μμ± λ° μ μ₯
- μμ - μμ΄ν ꡬ맀
- μΈλ²€ν 리 - μμ΄ν μ₯μ°©, ν΄μ , ν맀
- μνμ°½ - μμ μ μ 보 νμΈ, κ²½νμΉ
- μΈκ²μ - μ€μκ° ν΅μ μΌλ‘ κ²μ μ§ν
{
"command": "COMMAND",
"matchId": "long",
"request":{ }
}
@Component
public class WebSocketSessionManager {
private Map<Long, WebSocketSession> webSocketSessionMap = new ConcurrentHashMap<>();
public synchronized void addWebSocketSessionMap(Long key, WebSocketSession socketSession) {
if (!validateSession(socketSession)) {
throw new WebSocketSessionInvalidException();
}
this.webSocketSessionMap.put(key, socketSession);
}
public synchronized void removeWebSocketSessionMap(Long key) {
this.webSocketSessionMap.remove(key);
}
public synchronized void sendMessage(String message, List<Long> keys) throws IOException {
for (Long key : keys) {
WebSocketSession socketSession = this.webSocketSessionMap.get(key);
if (validateSession(socketSession)) {
socketSession.sendMessage(new TextMessage(message));
}
}
}
public synchronized boolean validateSession(WebSocketSession socketSession) {
return socketSession != null && socketSession.isOpen();
}
}
λ©μμ§ νμ±
/**
* μΈκ²μ νλ μ΄ μ€ λ°μνλ WebSocket κΈ°λ° μμ²λ€μ μ²λ¦¬νλ ν΅μ¬ λ©μλ. λ©μΈμ§ νμ±κ³Ό λΌμ°ν
μ μνν¨.
*
* @param session WebSocketSession
* @param message WebSocketMessageProtocol νμμ μ€μν λ©μΈμ§
* @throws IOException
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws IOException {
Long characterId = null;
try {
characterId = extractCharacterId(session, Long.class);
WebSocketMessageRequest messageRequest = jsonUtil.parseWebSocketMessage(
message.getPayload());
Long matchId = messageRequest.matchId();
JsonObject request = messageRequest.request();
String command = messageRequest.command();
MatchPlayer matchPlayers = matchService.findPlayerByMatchId(matchId);
List<Long> matchPlayersList = matchPlayers.toList();
if (!matchPlayers.isContainsPlayer(characterId)) {
throw new CharacterNotInMatchException(matchId);
}
WebSocketCommand webSocketCommand = findWebSocketCommand(command);
dispatcher(characterId, matchId, request, matchPlayersList, webSocketCommand);
} catch (Exception e) {
webSocketSessionManager.sendMessage(e.getMessage(),
Collections.singletonList(characterId));
}
}
λ©μΈμ§ λΌμ°ν
private void dispatcher(Long characterId, Long matchId, JsonObject request,
List<Long> matchPlayers, WebSocketCommand webSocketCommand)
throws Exception {
String responseMessage = new String();
//message routing
switch (webSocketCommand) {
case GREETING -> {
responseMessage = playController.greeting(characterId);
}
case READY -> {
PlayReadyRequest playReadyRequest = jsonUtil.fromJson(request,
PlayReadyRequest.class);
responseMessage = playController.ready(characterId, matchId
, playReadyRequest);
}
case START -> {
responseMessage = playController.start(characterId, matchId);
}
case TURN_GAME -> {
responseMessage = playController.turnGame(characterId, matchId,
jsonUtil.fromJson(request, PlayTurnRequest.class));
}
case END_GAME -> {
responseMessage = playController.endGame(characterId, matchId);
}
case SURRENDER_GAME -> {
responseMessage = playController.surrenderGame(characterId, matchId);
}
case QUIT_GAME -> {
responseMessage = playController.quitGame(characterId, matchId);
}
case EMPTY -> throw new InvalidWebSocketMessageException(matchId);
}
webSocketSessionManager.sendMessage(responseMessage, matchPlayers);
}
@Aspect
@Component
@RequiredArgsConstructor
public class WebSocketExceptionAspect {
private final JsonUtil jsonUtil;
@Around("execution(* com.project.game.play.controller.PlayController.*(..))")
public Object handleExceptionAndLog(ProceedingJoinPoint joinPoint) {
try {
return joinPoint.proceed();
} catch (WebSocketException we) {
ErrorResponse response = new ErrorResponse(we);
return jsonUtil.toJson(response);
} catch (Throwable e) {
ErrorResponse response = new ErrorResponse(e);
return jsonUtil.toJson(response);
}
}
}