[Spring] WebSocket으로 채팅 구현하기 - 일반적인 WebSocket 사용

김강욱·2024년 5월 13일
0

Spring

목록 보기
9/17
post-thumbnail

이번 포스팅은 WebSocket을 이용하여 간단한 채팅 실습을 진행하려고 합니다.

daddyprogrammer님의 WebSocket 채팅 서버 구현 시리즈의 내용을 보면서 클론 코딩하며 정리한 내용들입니다.

WebSocket에 대해서 간단하게 공부하고 싶으신 분은 아래 링크를 참고하시면 됩니다.

WebSocket이란?

🎈 일반적인 WebSocket 사용하기

🙌 웹소켓

웹소켓은 기존의 단방향 HTTP 프로토콜과 호환되어 양방향 통신을 제공하기 위해 개발된 프로토콜입니다.

일반 Socket 통신과 달리 HTTP 80 Port를 이용하므로 방화벽에 제약이 없으며 접속까지는 HTTP 프로토콜을 이용하고 그 이후의 통신은 자체적인 WebSocket 프로토콜로 통신을 하게 됩니다.

build.gradle에서 websocket 의존성 추가하기

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
}

WebSocketHandler 작성하기

Socket 통신은 서버와 클라이언트가 1:N 관계가 됩니다. 따라서 한 서버에 여러 클라이언트가 접속할 수 있고, 서버에는 여러 클라이언트가 발송한 메시지를 받아 처리해줄 Handler가 필요하게 됩니다.

TextWebSocketHandler를 상속받아 아래와 같이 Handler를 작성해줄 수 있습니다.

package com.websocket.chat.handler;

// import 생략....

@Slf4j
@Component
public class WebSocketChatHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        log.info("payload {}", payload);
        TextMessage textMessage = new TextMessage("Welcome chatting sever~^^");
        session.sendMessage(textMessage);
    }
}

WebSocket Config 작성하기

WebSocketChatHandler을 이용하여 WebSocket을 활성화하기 위한 Config 작성이 필요합니다.

@EnableWebSocket를 선언하여 WebSocket을 활성화해줘야합니다. WebSocket에 접속하기 위한 endpoint를 설정하고 도메인이 다른 서버에서도 접속이 가능하도록 CORS 설정을 해줘야 합니다.

endpoint : /ws/chat
CORS : setAllowedOrigins("*")

이후 클라이언트는 ws://localhost:8080/ws/chat으로 커넥션을 연결하고 메시지 통신을 한다

@RequiredArgsConstructor
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    private final WebSocketHandler webSocketHandler;

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

채팅 고도화 하기

클라이언트들은 서버에 접속하면 개별의 WebSocket Sessions을 가지게 됩니다.

채팅방에 입장 시 클라이언트들의 WebSocket Session 정보를 채팅방에 맵핑시켜서 보관하고 있으면 서버에 전달된 메시지를 특정방의 WebSocket 세션으로 보낼 수 있습니다.

1. 채팅 메시지 구현

@Getter
@Setter
public class ChatMessage {
    // 메시지 타입 : 입장, 채팅
    public enum MessageType {
        ENTER, TALK
    }
    private MessageType type; // 메시지 타입
    private String roomId; // 방번호
    private String sender; // 메시지 보낸사람
    private String message; // 메시지
}

채팅 메시지는 입장, 대화하기 상태를 가지는 MessageType과 채팅방 구별 Id, 메시지 발신인, 메시지로 구성하였습니다.


2. 채팅방 구현하기

@Getter
public class ChatRoom {
    private String roomId;
    private String name;
    private Set<WebSocketSession> sessions = new HashSet<>();

    @Builder
    public ChatRoom(String roomId, String name) {
        this.roomId = roomId;
        this.name = name;
    }

    public void handleActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) {
        if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
            sessions.add(session);
            chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다.");
        }
        sendMessage(chatMessage, chatService);
    }

    public <T> void sendMessage(T message, ChatService chatService) {
        sessions.parallelStream().forEach(session -> chatService.sendMessage(session, message));
    }
}

채팅방은 입장한 클라이언트들의 정보를 가지고 있어야 하므로 WebSocket Session 정보 리스트를 멤버 필드로 가지게 됩니다.

채팅방 id, 채팅방 이름 정보를 가지고 있고 채팅방에서 입장, 대화하기 기능을 handleAction 메서드를 통해 분기로 처리하고 있습니다.

입장 시에는 채팅방의 session 정보에 클라이언트의 session을 추가하고 채팅방에 메시지가 도착할 경우 채팅방의 모든 session에 메시지를 발송하게 됩니다.


3. 채팅 서비스 구현

@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {

    private final ObjectMapper objectMapper;
    private Map<String, ChatRoom> chatRooms;

    @PostConstruct
    private void init() {
        chatRooms = new LinkedHashMap<>();
    }

    public List<ChatRoom> findAllRoom() {
        return new ArrayList<>(chatRooms.values());
    }

    public ChatRoom findRoomById(String roomId) {
        return chatRooms.get(roomId);
    }

    public ChatRoom createRoom(String name) {
        String randomId = UUID.randomUUID().toString();
        ChatRoom chatRoom = ChatRoom.builder()
                .roomId(randomId)
                .name(name)
                .build();
        chatRooms.put(randomId, chatRoom);
        return chatRoom;
    }

    public <T> void sendMessage(WebSocketSession session, T message) {
        try {
						// ChatMessage 객체를 직렬화한 값으로 TextMessage 생성
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }
}

채팅 서비스는 채팅방을 생성, 조회하고 하나의 세션에 메시지를 발송하도록 구현하였습니다.

채팅방 리스트를 Map에 저장하여 서버에 생성된 모든 채팅방의 정보를 저장하게 됩니다.


4. 채팅 컨트롤러 구현

@RequiredArgsConstructor
@RestController
@RequestMapping("/chat")
public class ChatController {

    private final ChatService chatService;

    @PostMapping
    public ChatRoom createRoom(@RequestParam String name) {
        return chatService.createRoom(name);
    }

    @GetMapping
    public List<ChatRoom> findAllRoom() {
        return chatService.findAllRoom();
    }
}

컨트롤러에서 채팅방을 생성할 수 있고, 현재 존재하는 채팅방 목록을 조회할 수 있습니다.


5. WebSocketHandler 수정

@Slf4j
@RequiredArgsConstructor
@Component
public class WebSockChatHandler extends TextWebSocketHandler {
    private final ObjectMapper objectMapper;
    private final ChatService chatService;

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        log.info("payload {}", payload);
// 삭제        TextMessage textMessage = new TextMessage("Welcome chatting sever~^^ ");
// 삭제       session.sendMessage(textMessage);
        ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
        ChatRoom room = chatService.findRoomById(chatMessage.getRoomId());
        room.handleActions(session, chatMessage, chatService);
    }
}

웹소켓 클라이언트로부터 채팅 메시지를 전달 받아 채팅 메시지 객체(DTO)로 변환하도록 수정하였습니다.

전달받은 메시지에 담긴 채팅방 Id로 발송 대상 채팅방 정보를 조회합니다.

해당 채팅방에 입장해있는 모든 클라이언트들(WebSocket Session)에게 타입에 따른 메시지를 발송합니다.

참고 자료
daddyprogrammer님의 WebSocket 채팅 서버 구현 시리즈

profile
TO BE DEVELOPER

0개의 댓글

관련 채용 정보