TECHNICAL KNOWLEDGE BASE

[SpringBoot] Websocket을 이용한 Chatting Server 구현

프로젝트 생성 Java버전 및 Gradle 선택 img /assets/category/Spring/2023/07/13 01.png Spring 버전, 의존성 추가 img /assets/category/Spring/2023/07/13 02.png 패키지 구조 WebSoc

Spring Boot
Spring

Legacy Engineering Note · 기존 기술 블로그에서 선별 이관한 기록입니다. 작성 당시 환경과 버전을 기준으로 합니다.

프로젝트 생성

Java버전 및 Gradle 선택

Spring 버전, 의존성 추가

패키지 구조

chat
    WebSockConfig
    WebSocketHandler
    - controller
        - ChatController
    - dto
        - ChatMessage
        - ChatRoom
    - service
        - ChatService

WebSocket 기본설정

  1. WebSockConfig

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    import lombok.RequiredArgsConstructor;
    
    @RequiredArgsConstructor
    @Configuration
    @EnableWebSocket
    public class WebSockConfig implements WebSocketConfigurer {
        private final WebSocketHandler webSocketHandler;
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(webSocketHandler, "ws").setAllowedOrigins("*");
        }
    }
    
  2. WebSocketHandler

    import org.springframework.stereotype.Component;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import com.blackcode.www.chat.dto.ChatMessage;
    import com.blackcode.www.chat.dto.ChatRoom;
    import com.blackcode.www.chat.service.ChatService;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @RequiredArgsConstructor
    @Component
    public class WebSocketHandler 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);
            ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
            
            ChatRoom chatRoom = chatService.findRoomById(chatMessage.getRoomId());
            chatRoom.handlerActions(session, chatMessage, chatService);
        }
    }
    

Controller 생성

  • ChatController
    import java.util.List;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.blackcode.www.chat.dto.ChatRoom;
    import com.blackcode.www.chat.service.ChatService;
    
    import lombok.RequiredArgsConstructor;
    
    @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();
        }
    }
    

Service 생성

  • ChatService Class
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.UUID;
    
    import org.springframework.stereotype.Service;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    
    import com.blackcode.www.chat.dto.ChatRoom;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import jakarta.annotation.PostConstruct;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    @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);
            log.info(chatRoom.toString());
            return chatRoom;
        }
        
        public <T> void sendMessage(WebSocketSession session, T message) {
            try{
                session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    

dto 생성

  • ChatMessage Class

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class ChatMessage {
        public enum MessageType{
            ENTER, TALK
        }
    
        private MessageType type;
        private String roomId;
        private String sender;
        private String message;
    }
    
  • ChatRoom Class

    import java.util.HashSet;
    import java.util.Set;
    
    import org.springframework.web.socket.WebSocketSession;
    
    import com.blackcode.www.chat.service.ChatService;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Getter
    public class ChatRoom {
        private String roomId;
        private String name;
        private Set<WebSocketSession> sessions = new HashSet<>();
        
        @Builder
        public ChatRoom(String roomId, String name) {
            log.info("roomID : "+roomId);
            log.info("name : "+name);
            this.roomId = roomId;
            this.name = name;
        }
        
        public void handlerActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) {
            if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
                sessions.add(session);
                chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다.");
            }
            sendMessage(chatMessage, chatService);
        
        }
        
        private <T> void sendMessage(T message, ChatService chatService) {
            sessions.parallelStream()
                    .forEach(session -> chatService.sendMessage(session, message));
        }
    }
    

1. 채팅방 개설

post 방식으로 key/value 값을 송출하고 나면 roomId를 return받는다.
roomId값 메모 필요

2. 방 생성 조회

3. 방 진입

  • WebSocket Text Client
  • url : ws://127.0.0.1:8080/ws
  • Request
    {
        "type":"ENTER",
        "roomId":"<기록해둔 roomId 입력>",
        "sender":"<참가자 ID>",
        "message":"<채팅할때만 message 입력>"
    }
    

4. 채팅 진행

  • WebSocket Text Client
  • url : ws://127.0.0.1:8080/ws
  • Request
    {
        "type":"TALK",
        "roomId":"<기록해둔 roomId 입력>",
        "sender":"<참가자 ID>",
        "message":"<채팅할때만 message 입력>"
    }