๐Ÿ’ป ๊ณต๋ถ€ ๊ธฐ๋ก/๐Ÿƒ Spring

Spring | STOMP

  • -
ํ•ด๋‹น ๋‚ด์šฉ์€ ๊ณต๋ถ€์˜ ๋ชฉ์ ์œผ๋กœ ๊ธฐ๋ก๋˜์—ˆ์œผ๋ฉฐ,
์•„๋ž˜ ๋‚ด์šฉ์€ ๋ชจ๋‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์œผ๋กœ ๊ณต๋ถ€ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
์—ด์‹ฌํžˆ ๋ฐฐ์šฐ๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

 

 


 

 

๐Ÿ“Œ STOMP

STOMP(Streaming Text Oriented Messaging Protocol)์€ ๋ฉ”์„ธ์ง€ ๊ธฐ๋ฐ˜ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ ์ค‘ ํ•˜๋‚˜๋กœ, ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์˜ ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.
์ด ํ”„๋กœํ† ์ฝœ์€ ๋Œ€๋ถ€๋ถ„์˜ ๋ฉ”์„ธ์ง€ ๋ธŒ๋กœ์ปค์—์„œ ์ง€์›๋˜๋ฉฐ, ๋‹ค๋ฅธ ๋ฉ”์„ธ์ง€ ๊ธฐ๋ฐ˜ ํ”„๋กœํ† ์ฝœ์— ๋น„ํ•ด ๋” ๊ฐ€๋ณ๊ณ  ๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
STOMP๋Š” ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์˜ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์ œ๊ณตํ•˜๋ฉฐ, ํ•˜๋‚˜์˜ ์—ฐ๊ฒฐ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ๋ฉ”์„ธ์ง€์˜ ๊ตฌ๋…(subscription)๊ณผ ๋ฐœํ–‰(publish)์„ ์ง€์›ํ•˜๋ฉฐ, ๋ฉ”์„ธ์ง€์˜ ํ์ž‰(queueing)๊ณผ ํŠธ๋žœ์žญ์…˜(transaction) ์ฒ˜๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
๋งŽ์€ ์–ธ์–ด์—์„œ STOMP๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์›น ์†Œ์ผ“(WevSocket)๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ™œ๋™๋˜๊ธฐ๋„ ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์ง•์œผ๋กœ ์ธํ•ด STOMP ๋Š” ๋ฉ”์„ธ์ง€ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ๋งค์šฐ ์œ ์šฉํ•œ ํ”„๋กœํ† ์ฝœ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.

 

๐Ÿ“Œ Maven(์˜์กด์„ฑ)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>${Spring ๋ฒ„์ „๊ณผ ๋™์ผํ•˜๊ฒŒ}</version>
</dependency>

 

๐Ÿ“Œ servlet-context.xml

 ์ธํ„ฐ๋„ท์— ๊ฒ€์ƒ‰๋˜๋Š” ์ •๋ณด๋“ค ์ค‘ config ๋ฅผ ์„ค์ •ํ•ด์„œ stomp์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ํ”ํžˆ ์•Œ๋ ค์ ธ ์žˆ๋‹ค.

๋‚˜๋Š” Spring-legarcy ํ”„๋กœ์ ํŠธ๋กœ MVC ํŒจํ„ด์„ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— config ๋ณด๋‹จ servlet-context.xml์— ์„ค์ •ํ•ด์„œ stomp๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

<!-- chat stomp config -->
<websocket:message-broker application-destination-prefix="/">
    <websocket:stomp-endpoint path="/stompTest">
        <websocket:sockjs/>
    </websocket:stomp-endpoint>
    <websocket:simple-broker prefix="/topic, /queue"/> <!-- /topic + /addr ์ถ”๊ฐ€ํ•˜์—ฌ ํƒ€์ž…๋ณ„ ํ†ต์‹  -->
</websocket:message-broker>
  • <websocket:message-broker>
    • WebSocket ๋ฉ”์‹œ์ง•์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, application-destination-prefix ์†์„ฑ์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ฉ”์‹œ์ง€์˜ ๋ชฉ์ ์ง€๋ฅผ ์ง€์ •ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ชฉ์ ์ง€๋Š” ๋ฃจํŠธ("/")๋กœ ์„ค์ •ํ–ˆ๋‹ค.
  • <websocket:stomp-endpoint>
    • WebSocket ์—”๋“œํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•œ๋‹ค. path ์†์„ฑ์€ STOMP ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐํ•  ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•œ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” "/stompTest" ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    • <websocket:sockjs/> SockJS ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜๋„๋ก ์„ค์ •.
  • <websocket:simple-broker>
    • ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๋ฅผ ์„ค์ •ํ•œ๋‹ค. prefix ์†์„ฑ์€ ๋ฉ”์‹œ์ง€ ๋ชฉ์ ์ง€์˜ prefix๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” "/topic"๊ณผ "/queue" prefix๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ์ง€์ •ํ•œ ๋ฉ”์‹œ์ง€ ๋ชฉ์ ์ง€์— ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋ฉด, ํ•ด๋‹น ๋ชฉ์ ์ง€๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ๋“ค์ด ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“Œ StompController

 ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๊ณ , ํ•ด๋‹น ๋ฉ”์„ธ์ง€๋ฅผ ๋‹ค์‹œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ.

@Controller
public class StompController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/{roomNumber}/TTT") // html stompClient.send("/app/send", {}, message);
    public void onReceivedMessage(ChatMessage obj) throws Exception {
        messagingTemplate.convertAndSend("/topic/message/" + obj.getRoomNumber(), obj);
    }
}
  • SimpMessagingTemplate
    •  Spring์—์„œ STOMP ๋ฉ”์„ธ์ง• ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณต๋˜๋Š” ํ…œํ”Œ๋ฆฟ์ด๋‹ค. ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ @Autowired ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•œ๋‹ค.
  • @MessageMapping("/{roomNumber}/TTT")
    • STOMP ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์„ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ธ ๋ฉ”์„ธ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ChatMessage์˜ ๊ตฌ์กฐ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
  • .convertAndSend(' ๊ฒฝ๋กœ ', ' ๋‹ค์‹œ ๋ณด๋‚ผ ๋ฉ”์„ธ์ง€ ')
    • ๋ฉ”์„ธ์ง€๋ฅผ ๋‹ค์‹œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•œ๋‹ค.
    • "/topic/message"/{roomNumber}" ๊ฒฝ๋กœ๋กœ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด๋ฉด, ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ๊ตฌ๋…ํ•œ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.

 

๐Ÿ“Œ Clinent

 ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด SockJS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

function stompOpen() {
    // endpoint ์„ค์ •
    sock = new SockJS("/stompTest");
    client = Stomp.over(sock);

    // sock connect event ์‹คํ–‰
    stompEvent();
}
  • new SockJS("/stompTest")
    • STOMP ์„œ๋ฒ„์˜ endpoint ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • Stomp.over(sock)
    • SockJS ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด STOMP ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
    • STOMP ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์™€ ๋ฉ”์„ธ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 

function stompEvent() {
  // connect ํ•จ์ˆ˜ ์‚ฌ์šฉ
  client.connect({}, function(frame) {
    client.subscribe("/topic/message/" + roomNumber, function(message) {
    console.log(" :: subscribe: ", message);
  
    // msg >>> stomp controller
    client.send("/" + roomNumber + "/TTT", {}, message);
    });
  });
}
  • client.connect({}, function(frame)) { ... }
    • STOMP ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•˜๋Š” ํ•จ์ˆ˜.
    • ์ฒซ ๋ฒˆ์งธ ์ธ์ž: ํ—ค๋”๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฐ์ฒด
    • ๋‘ ๋ฒˆ์จฐ ์ธ์ž: ํ•จ์ˆ˜ ์—ฐ๊ฒฐ์ด ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜
  • client.subscribe("/topic/message/" + roomNumber, function(message) { ... }
    • /topic/message/ ๊ฒฝ๋กœ์— ์žˆ๋Š” ๋ฉ”์„ธ์ง€๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ํ•จ์ˆ˜
    • ์ฒซ ๋ฒˆ์งธ ์ธ์ž: ๊ฒฝ๋กœ + roomNumber ๋ณ€์ˆ˜ (์ฑ„ํŒ…๋ฐฉ์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜)
    • ๋‘ ๋ฒˆ์งธ ์ธ์ž: ๋ฉ”์„ธ์ง€๋ฅผ ์ˆ˜์‹ ํ–ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜
  • client.send("/" + roomNumber + "/TTT", {}, message)
    • ๊ฒฝ๋กœ์— message ๊ฐ์ฒด๋ฅผ ๋ฉ”์„ธ์ง€๋กœ ๋ฐœ์‹ ํ•˜๋Š” ํ•จ์ˆ˜

 

 

 

์ถœ์ฒ˜ | ๊ฐœ๋ฐœ ํ”์  ๋‚จ๊ธฐ๊ธฐ

Contents

ํฌ์ŠคํŒ… ์ฃผ์†Œ๋ฅผ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค

์ด ๊ธ€์ด ๋„์›€์ด ๋˜์—ˆ๋‹ค๋ฉด ๊ณต๊ฐ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.