Web Socket

화낼거양's avatar
Dec 18, 2024
Web Socket
 
 
💡
웹소켓(WebSocket)은 클라이언트와 서버 간의 양방향 통신을 가능하게 하는 프로토콜입니다. 이를 통해 웹 애플리케이션에서 실시간으로 데이터를 주고받을 수 있습니다.
 

웹 소켓의 특징 :

 
  • Stateful :
    • 웹소켓은 상태를 유지(stateful)하는 연결 방식입니다. 일반 HTTP 요청과 달리, 클라이언트와 서버 간의 연결이 유지되며 지속적인 통신이 가능합니다. 이는 실시간 애플리케이션에서 매우 유용합니다.
  • 클라이언트와 서버 둘 다 리스닝을 유지 :
    • 웹소켓은 클라이언트와 서버가 지속적으로 연결을 유지하며 서로 데이터를 주고받을 수 있는 양방향 통신 프로토콜입니다. 일반적으로 클라이언트와 서버는 이벤트 기반의 비동기 방식으로 통신을 처리하며, 이는 while 루프와 같은 지속적인 리스닝을 포함할 수 있습니다. 하지만 실제 구현에서는 이벤트 핸들러를 통해 효율적으로 데이터를 주고받습니다.
 
 
 
 

웹 소켓 간단 예제

 
 

config(설정파일) :

// SRP : 마트 점원 (메시지 브로커) 세팅 @EnableWebSocketMessageBroker @Configuration public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // 웹 소켓 엔드포인트 설정 @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect").setAllowedOriginPatterns("*"); } // 구독, 발행 엔드포인트 설정 @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/sub"); // sub로 시작하는 모든 주소 registry.setApplicationDestinationPrefixes("/pub"); // pub로 시작하는 모든 주소 } }
 
 
 

클래스 및 어노테이션:

 
@Configuration public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
 
  • @Configuration: 이 클래스가 Spring의 설정 클래스임을 나타냅니다.
  • WebSocketMessageBrokerConfigurer 인터페이스: WebSocket 메시지 브로커를 설정하기 위해 필요한 메서드를 제공합니다.
 

웹 소켓 엔드포인트 설정:

 
@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect").setAllowedOriginPatterns("*"); }
 
  • registerStompEndpoints: STOMP (Simple Text Oriented Messaging Protocol) 엔드포인트를 등록합니다. 클라이언트는 이 엔드포인트를 통해 WebSocket 서버에 연결할 수 있습니다.
  • addEndpoint("/connect"): 클라이언트가 /connect 엔드포인트로 연결할 수 있게 합니다.
  • setAllowedOriginPatterns("")*: 모든 도메인에서의 접속을 허용합니다. 이는 Cross-Origin Resource Sharing (CORS) 문제를 해결합니다. 실제 운영 환경에서는 특정 도메인만 허용하는 것이 보안에 좋습니다.
 

메시지 브로커 설정:

 
@Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/sub"); // sub로 시작하는 모든 주소 registry.setApplicationDestinationPrefixes("/pub"); // pub로 시작하는 모든 주소 }
 
  • configureMessageBroker: 메시지 브로커를 구성합니다.
  • enableSimpleBroker("/sub"): 간단한 메시지 브로커를 활성화하고, sub로 시작하는 주소를 대상으로 메시지를 브로킹합니다. 예를 들어, 클라이언트는 /sub/topic/messages와 같은 주소를 구독할 수 있습니다.
  • setApplicationDestinationPrefixes("/pub"): 발행(publish) 주소의 접두사를 pub로 설정합니다. 클라이언트는 /pub/message와 같은 주소로 메시지를 전송할 수 있습니다.
 
 
 
 

v1

 

index 파일 내용 :

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> <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="/chat-form">채팅 메시지쓰기</a></li> </ul> </div> <h1>채팅 목록</h1> <button onclick="send1()">메시지전송(pub)1</button> <hr> <ul id="chat-box"> {{#models}} <li>{{msg}}</li> {{/models}} </ul> <script> // 1. 웹 소켓 연결 세팅 및 연결 완료 let socket = new WebSocket('ws://localhost:8080/connect'); let stompClient = Stomp.over(socket); stompClient.connect({}, (frame) => { console.log("1. Connected") // 2. 구독하기 /sub -> /pub/room 이런 url로 요청해야 발동 stompClient.subscribe("/sub/1", (response) => { console.log("2. Sub/1") console.log(response); }); }); function send1() { stompClient.send("/pub/room", {}, "1"); } </script> </body> </html>
 
 

웹 소켓 연결 설정:

 
let socket = new WebSocket('ws://localhost:8080/connect'); let stompClient = Stomp.over(socket); stompClient.connect({}, (frame) => { console.log("1. Connected") // 2. 구독하기 /sub -> /pub/room 이런 url로 요청해야 발동 stompClient.subscribe("/sub/1", (response) => { console.log("2. Sub/1") console.log(response); }); });
 
  • 웹 소켓 생성:
    •  
      let socket = new WebSocket('ws://localhost:8080/connect');
       
    • WebSocket 객체를 생성하여 서버에 연결합니다. 여기서는 ws://localhost:8080/connect 엔드포인트로 연결합니다.
 
  • STOMP 클라이언트 생성:
    •  
      let stompClient = Stomp.over(socket);
       
    • Stomp.over(socket)를 사용하여 STOMP 클라이언트를 생성합니다.
 
  • 웹 소켓 연결 및 구독 설정:
    •  
      stompClient.connect({}, (frame) => { console.log("1. Connected") // 구독 설정 stompClient.subscribe("/sub/1", (response) => { console.log("2. Sub/1") console.log(response); }); });
       
    • connect 메서드: 서버와 STOMP 연결을 설정합니다. 연결이 완료되면 콜백 함수가 실행됩니다.
    • subscribe 메서드: 특정 경로(/sub/1)를 구독하여 해당 경로로 오는 메시지를 수신합니다. 메시지가 도착하면 콜백 함수가 실행되어 콘솔에 출력됩니다.
 

메시지 전송 함수:

 
function send1() { stompClient.send("/pub/room", {}, "1"); }
 
  • send1 함수: /pub/room 경로로 "1"이라는 메시지를 전송합니다.
  • stompClient.send 메서드: 특정 경로로 메시지를 발행합니다. 여기서는 /pub/room 경로로 빈 헤더와 함께 "1" 메시지를 전송합니다.
 
 

전체 흐름:

 
  1. 웹 소켓 연결: 클라이언트는 ws://localhost:8080/connect 엔드포인트로 WebSocket 연결을 설정합니다.
  1. STOMP 클라이언트 설정: STOMP 프로토콜을 사용하여 WebSocket을 통해 메시지를 주고받기 위한 클라이언트를 설정합니다.
  1. 연결 완료 후 구독: 연결이 완료되면 /sub/1 경로를 구독하여 해당 경로로 전송된 메시지를 수신합니다.
  1. 메시지 전송: send1 함수가 호출되면 /pub/room 경로로 메시지 "1"을 전송합니다.
 
 

 

controller :

// /pub/room 에서 /pub가 생략되어있음 @MessageMapping("/room") public void pubTest(String number) { System.out.println("확인 : " + number); sms.convertAndSend("/sub/" + number, "hello world " + number); }
 
  • @MessageMapping("/room"):
    • 이 어노테이션은 /room 경로로 들어오는 메시지를 이 메서드(pubTest)로 매핑합니다.
    • 클라이언트는 /pub/room 경로로 메시지를 보낼 때, 이 메서드가 실행됩니다. 여기서 /pub는 생략된 상태입니다.
  • public void pubTest(String number):
    • pubTest 메서드는 String 타입의 number를 매개변수로 받습니다. 이는 클라이언트가 보낸 메시지의 본문입니다.
  • System.out.println("확인 : " + number);:
    • 전달된 number 값을 콘솔에 출력합니다. 주로 디버깅 목적입니다.
  • sms.convertAndSend("/sub/" + number, "hello world " + number);:
    • sms 객체를 사용하여 /sub/{number} 경로로 메시지를 전송합니다.
    • 메시지 내용은 "hello world " + number입니다.
    • 여기서 number 값은 클라이언트가 구독한 경로의 일부가 됩니다.
 
 
 
 
 
 
 
 

v2 (글을 썼을 때 전달)

 

index :

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Document</title> <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://192.168.0.88:8080/connect'); // 현재 컴퓨터의 ip 주소 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); let site = document.querySelector("#chat-box"); let dom = document.createElement("li"); dom.innerHTML = body.msg; site.prepend(dom); }); }); </script> </body> </html>
 
v1 과의 변경점 :
 
  • 경로가 /sub/chat 으로 변경되었습니다.
  • 응답을 받으면 JSON으로 파싱한 뒤 HTML 요소로 변환하여 prepend 메서드를 사용해 요소를 리스트의 제일 앞에 추가합니다.
 

controller :

@RequiredArgsConstructor @Controller public class ChatController { private final ChatService chatService; private final SimpMessageSendingOperations sms; @GetMapping("/") public String index(Model model) { model.addAttribute("models", chatService.findAll()); return "index"; } @GetMapping("/save-form") public String saveForm(){ return "save-form"; } @PostMapping("/chat") public String save(String msg) { Chat chat = chatService.save(msg); sms.convertAndSend("/sub/chat", chat); return "redirect:/"; } }
 
 
  • @PostMapping("/chat") 메서드는 클라이언트로부터 전달받은 문자열 msg를 저장합니다.
  • 저장된 msgChat 객체로 변환되어 데이터베이스에 삽입됩니다.
  • 그런 다음, sms.convertAndSend("/sub/chat", chat)를 호출하여 /sub/chat 경로를 구독하고 있는 모든 클라이언트에게 Chat 객체를 전송합니다.
  • 이 과정이 완료되면 메서드는 "redirect:/"를 반환하여 클라이언트를 루트 경로(/)로 리디렉션합니다.
 
 
 
 
Share article

moohyun