[Node.js + RabbitMQ] Node.js + socket.io + RabbitMQ 이용한 실시간 메시지 처리

[Node.js + RabbitMQ] Node.js + socket.io + RabbitMQ 이용한 실시간 메시지 처리

Node.js와 RabbitMQ를 연동하기 위해 여러 모듈들이 있으며 이중에서 가장 많이 사용? 하고 활발한 모듈은 node-amqp 이거 인거 같습니다.

물론 RabbitMQ 공식 블로그에 보면 rabbitmq-nodejs-rabbitjs 에 대한 포스트가 있으며 거기에서 rabbit.js를 소개하고 있지만 결국 node-amqp를 이용해서 3가지 메세지 패턴(Publish/Subscribe, Request/Reply, PUSH/PULL) 을 구현해 놓은 것인데 각 패턴별로 고정된 옵션설정이 되어 있어서(exchange나 queue에 대한) 열심히 수정하다가 결국 막혔습니다.

대신 rabbit.js를 참고해서 나름대로 원하는 것을 만들어 보았습니다.

일단 node.js, npm, express, rabbitmq 가 설치되어 있다는 가정하에 진행해 보겠습니다. 기본적으로 express를 이용해 프로젝트를 만들면 다음과 같습니다.

-bash-3.2$ express ex3
  create : ex3
  create : ex3/package.json
  create : ex3/app.js
  create : ex3/public
  create : ex3/public/javascripts                   // app에서 필요한 javascripts 폴더
  create : ex3/public/images                        // app에서 필요한 image 폴더
  create : ex3/public/stylesheets                 // app에서 필요한 css 폴더
  create : ex3/public/stylesheets/style.css  // 기본 css
  create : ex3/routes
  create : ex3/routes/index.js                // 라우트를 위한 js (어떤 view를 보여줄지를 결정한다)
  create : ex3/views
  create : ex3/views/layout.jade                 // 기본 레이아웃(jade 들의 공통 head)
  create : ex3/views/index.jade                  // layout을 바탕으로 body 부분
  dont forget to install dependencies:    $ cd ex3 && npm install
                                           // 디펜던시한 모듈(jade 등)들을 또 설치하라는 안내 문구

기본 상태에서 실행했을때 동작 흐름은 다음과 같습니다.

클라이언트에서 요청이 오면 app.js 에서 app.get(‘/’, routes.index); 으로 인해 어떤 페이지가 보여질지 결정되며 routes.index는 routes 폴더에 index.js 파일에 exports.index로 선언된 내용을 보여주겠다는 것으로 만약 app.get(‘/’, routes.test); 라고 선언해면 routes에 test.js 파일을 찾는 것이 아니라 당연히 index.js 에 exports.test로 선언된 내용을 찾게 됩니다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

그리고 exports.index에 res.render(‘index’, { title: ‘Express’ }) 에서 index 는 /views 폴더 안에 index.jade를 렌더링 해서 response로 전달하며 { title: ‘Express’ } 은 index.jade 에 전달되어 Welcome to #{title}를 Welcome to Express로 화면에 보여줍니다.

이때 기본적으로 layout.jade가 head가 되고 index.jade가 body가 됩니다.

인제 app.js 를 다음과 같이 수정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//rabbitmq 서버에 접속(호스트, 포트, user, password, vhost 등을 지정할수 있습니다.)
var connec = amqp.createConnection({ host: 'localhost'
                                       , port: 5673
                                       , login: 'guest'
                                       //, password: 'quest'
                                       , vhost: '/'
                                         });
var io = require('socket.io');
var socketioserver = io.listen(app);
var _subscriptions = {};  //접속한 queue와 consumerTag를 저장하여 클라이언트 접속이 끊겼을때 unsubscribe 할수 있도록 합니다.
 
//socket.io 클라이언트가 접속할때 http://host/pub으로 연결(connection) 이벤트 발생시
socketioserver.of("/pub").on('connection', function(connection) {   
    sys.p("pub on connection");
          //rabbitmq의 exchage를 생성합니다. exchageName = node-conn-share1, exchangeType = topic
    var exchange1 = connec.exchange('node-conn-share1', {type: 'topic'});
    connection.on('disconnect', function(){ //socket.io 클라이언트의 접속이 끊어졌을때 할일을 정의
        sys.p("disconnect");
    });
          // 클라이언트에서 send()를 했을때 할일을 정의
    connection.on('message', function(msg){
                    //클라이언트에서 json.send()로 json형태로 보냅니다.
        sys.p("message : " + msg.message + ", routingKey : " + msg.routingKey );
                   //exchage에 클라이언트에서 보낸 routingKey와 message를 세팅해서 publish 합니다.
        exchange1.publish(msg.routingKey, msg.message, {contentType: 'text/plain'});
    });
});
 
socketioserver.of("/sub").on('connection', function(connection) {
    sys.p("sub on connection");
    var exchange1 = connec.exchange('node-conn-share1', {type: 'topic'});
 
    connection.on('bindQueue', function(opt){
        sys.p('bindQueue routingKey : ' + opt.routingKey);
        //queueName은 rabbitmq server가 자동으로 생성, durable=false, autoDelete=false
        var q1 = connec.queue('', { durable: false, autoDelete: false }, function(){
            //queue와 exchange를 바인딩하며 이때 exchange에서 routingKey에 해당하는 내용만 받도록 함
            q1.bind(exchange1, opt.routingKey);
            //바인딩이 완료가 되었을때 실행
            q1.on('queueBindOk', function() {
                sys.p("Queue " + q1.name + " is open");
                //queue에서 메세지를 받아 전송하도록 하며 1개씩 받아와 성공시 ack를 보내는 방식으로 한다.
                q1.subscribe({ ack: true, prefetchCount: 1},
                    function (m, headers, deliveryInfo) {
                        sys.p("subscribe : " + m.data.toString());
                        sys.p(deliveryInfo.deliveryTag + " : "+ deliveryInfo.routingKey + " : " + m.contentType);
                        //routingKey와 msg를 json으로 묶어서 보낸다.
                        var msg = {routingKey: deliveryInfo.routingKey, msg: m.data.toString()};
                        connection.emit('data', msg); //클라이언트에 보냄
                        q1.shift() //다음 메시지 받아오기
                }).addCallback(function(ok) {
                    sys.p("subscribe callback ok : " + ok.consumerTag);
                    //connection.sessionId에서 만든 queue와 consumerTag를 기억
                    _subscriptions[connection.id] = {'queue': q1, 'consumerTag': ok.consumerTag};
                });
            });
        });
    });
    //클라이언트 연결이 끊겼을때
    connection.on('disconnect', function(){
        sys.p("disconnect");
        var id = connection.id;
        var sub = _subscriptions[id];
        //_subscriptions에서 지우고
        delete _subscriptions[id];
        //해당 queue에 consumerTag에 해당하는 consumer가 더이상 soubscribe 하지 않도록 명시적으로 선언
        sub.queue.unsubscribe(sub.consumerTag);
        sub.queue.destroy(); //해당 queue를 삭제한다.
        sys.p("unsubscribe " + id + ", " + sub.queue.name + ", " + sub.consumerTag);
    });
});
 
app.get('/sub', routes.sub);
app.get('/pub', routes.pub);


pub/sub 클라이언트를 만들어서 위와 같이 테스트 하였습니다.
pub 에서 메시지와 routingKey를 보내고 sub에서는 자신들이 받을 routingKey를 지정합니다.
그럼 위에 그림과 같이 실시간 메시지 전송이 가능하며 응용하면 그룹채팅을 하면서 그룹채팅 사용자간에 귓속말 기능도 가능할것 같습니다.

[출처] https://teragoon.wordpress.com/2012/03/07/nodejs-rabbitmq-messag/

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
61 [web design][웹디자인][알아봅시다] 왕초보 피그마 기초 사용법???? 셀러님들 지금 당장 피그마 시작하세요! 그대로 따라만 해서 피그마 정복하기 졸리운_곰 2025.07.21 246
60 CSS selector 졸리운_곰 2024.08.02 690
59 [Web Design][웹 디자인] Sass(SCSS) 완전 정복! 졸리운_곰 2022.07.31 413
58 [HTML] HTML5 공간 분할 : 레이아웃(Layout) file 졸리운_곰 2021.09.06 554
57 [HTML5] HTML 공간 분할 file 졸리운_곰 2021.09.06 528
56 [웹제작][웹디자인] 시각화 대시보드 를 만들기 위해 고려해야 하는 4가지 file 졸리운_곰 2021.09.05 588
55 웹사이트 디자인하는 방법 file 졸리운_곰 2020.12.30 406
54 카드형 디자인/갤러리/리스트 코딩하기. file 졸리운_곰 2020.12.04 677
53 HTML DIV tag - 테두리 그리기 STYLE - BORDER 졸리운_곰 2020.08.26 241
52 CSS :: 버튼(Button) 예쁘게 꾸미기, 여러개의 버튼 그룹화 하기 file 졸리운_곰 2020.08.26 994
51 [button] CSS - Button - 버튼스타일 (버튼크기, 버튼색깔, 버튼비활성화, 버튼그룹, 이미지위버튼) 졸리운_곰 2020.08.26 2293
50 GUI 디자이너를 위한 색상 참고 사이트 file 졸리운_곰 2020.08.04 610
49 생산성을 빠르게 높여주는, 프런트엔드 개발 툴 10가지 file 졸리운_곰 2020.04.10 488
48 Mustache 공유 file 졸리운_곰 2019.12.29 344
47 Mustache 템플릿 문법 수많은 언어에서 지원되는 초간단 템플릿 문법 졸리운_곰 2019.12.29 273
46 HTML/CSS기초_drop down 메뉴 만들기 file 졸리운_곰 2019.12.21 1243
45 반응형이란 무엇인가? 졸리운_곰 2019.11.20 450
44 <인터넷, 블로그팁: 마진(margin)과 패딩(padding)의 차이점이 뭘까?> file 졸리운_곰 2019.11.01 504
43 CSS / 반응형 레이아웃 만들기 file 졸리운_곰 2019.02.08 572
42 Responsive Web ② - 반응형 웹을 위한 레이아웃 설계 방법 file 졸리운_곰 2019.02.08 634
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED