[기술 정리] 25. 웹 소켓과 Stomp 프로토콜을 이용 실시간 통신 구현

KangHo Lee's avatar
Dec 22, 2024
[기술 정리] 25. 웹 소켓과 Stomp 프로토콜을 이용 실시간 통신 구현

웹 소켓(WebSockets)

  • 클라이언트와 서버 간의 실시간 양방향 통신을 가능하게 하는 프로토콜입니다.
  • 일반적인 HTTP 요청과 달리, 웹 소켓은 연결이 열리면 계속 열려 있어, 클라이언트와 서버 간의 데이터 전송이 빠르고 지속적으로 이루어질 수 있습니다.

Stromp 프로토콜(Stomp over WebSockets)

  • 웹 소켓을 기반으로 한 메시징 프로토콜로, 클라이언트와 서버 간의 실시간 메시징을 지원합니다.
  • Stomp은 간단하고 효율적인 텍스트 기반 프로토콜로, 다양한 클라이언트와 서버에서 쉽게 구현할 수 있습니다.

1. View

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> <!-- stromp 외부 스크립트 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script> </head> <body> <div> <ul> <li><a href="/">채팅목록</a></li> <li><a href="/save-form">채팅 메시지쓰기</a></li> </ul> </div> <h1>채팅 목록</h1> <hr> <ul id="chat-box"> {{#models}} <li>{{msg}}</li> {{/models}} </ul> <script> // 1. 웹소켓 연결 세팅 및 연결 완료 let socket = new WebSocket('ws://ip주소/connect'); let stompClient = Stomp.over(socket); stompClient.connect({}, (frame)=>{ console.log("1", "Connected"); stompClient.subscribe("/sub/chat", (response)=>{ console.log("2", response); let body = JSON.parse(response.body); console.log("3", body); attack(body.msg); }); }); function attack(msg){ // 1. body 초기화 document.querySelector("body").innerHTML = ""; // 2. body 스타일 설정 document.querySelector("body").style.cssText = ` margin: 0; height: 100vh; display: flex; justify-content: center; align-items: center; background-color: black; `; // 3. 글자 요소 생성 const textElement = document.createElement("div"); textElement.textContent = msg; // 텍스트 내용 textElement.style.cssText = ` color: white; font-size: 50px; font-family: Arial, sans-serif; `; // 4. body에 추가 document.querySelector("body").appendChild(textElement); } </script> </body> </html>

2. Form

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> </head> <body> <div> <ul> <li><a href="/">채팅목록</a></li> <li><a href="/save-form">채팅 메시지쓰기</a></li> </ul> </div> <h1>채팅 메시지 쓰기</h1> <hr> <form action="//ip주소:8080/chat" method="post"> <input type="text" name="msg"> <button type="submit">전송</button> </form> </body> </html>

3. 웹 소켓 설정

WebSocketConfig.java
@EnableWebSocketMessageBroker @Configuration public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // 웹소켓 연결 엔드포인트 설정 @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 구독, 발행 전 최초 소켓 연결을 위한 주소 registry.addEndpoint("/connect") .setAllowedOrigins("*"); // 모든 주소에서 CORS 차단 해제 } // 구독, 발행 엔드포인트 설정 @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 판매자 (메세지 브로커) registry.enableSimpleBroker("/sub"); // 구독 주소, 응답내용이 /sub로 시작하면 발행 registry.setApplicationDestinationPrefixes("/pub"); // 발행 주소, pub로 시작하는 모든 주소(prefix 설정) } }

4. 컨트롤러

@RequiredArgsConstructor @Controller public class ChatController { private final ChatService chatService; private final SimpMessageSendingOperations sms; @GetMapping("/save-form") public String saveForm(){ return "save-form"; } @GetMapping("/") public String index(Model model) { model.addAttribute("models", chatService.findAll()); return "index"; } @PostMapping("/chat") public String save(String msg) { Chat chat = chatService.save(msg); sms.convertAndSend("/sub/chat", chat); // 객체로 넣으면 JSON으로 바꿔서 보낸다. return "redirect:/"; } // /pub/room @MessageMapping("/room") // pub/r1에서 pub 생략 public void pubTest(String number) { System.out.println("나 요청돼? " + number); // private final SimpMessageSendingOperations sms; 생성자 주입 // "/sub/" + number -> 특정 번호의 사람에게 알림 보내기 가능 sms.convertAndSend("/sub/" + number, "hello world " + number); } }

5. 그 외

Chat.java
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Table(name = "chat_tb") @Entity public class Chat { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String msg; @Builder public Chat(Integer id, String msg) { this.id = id; this.msg = msg; } }
ChatRepository.java
@Repository public interface ChatRepository extends JpaRepository<Chat, Integer> { }
ChatService.java
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class ChatService { private final ChatRepository chatRepository; @Transactional public Chat save(String msg) { Chat chat = Chat.builder().msg(msg).build(); return chatRepository.save(chat); } public List<Chat> findAll(){ // 순서를 내림차순으로 받기 위해서 Sort desc = Sort.by(Sort.Direction.DESC, "id"); return chatRepository.findAll(desc); } }

6. 실제 화면

notion image
  • 웹 소켓으로 양방향 통신이 연결되면 상대방이 보여주는 메세지를 그대로 봐야 합니다.
→ 스팸 문자 주소를 클릭하면 안 되는 이유입니다.
 
Share article

devleekangho