05-16 05:44
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[Data Science / MongoDB] WebSocket과 Mongoose를 이용하여 간단한 채팅 프로그램 개발 본문

Data Science/MongoDB

[Data Science / MongoDB] WebSocket과 Mongoose를 이용하여 간단한 채팅 프로그램 개발

cinema4dr12 2014. 1. 28. 17:27

by Geol Choi | 


이번 글에서는 Node.js의 MongoDB 패키지인 mongoose를 이용하여 간단한 채팅 프로그램 개발에 대해 알아보도록 하겠다.

주지하다 시피 Node.js와 MongoDB는 Windows/MacOS/Linux/Solaris 등의 OS를 지원하므로 어느 한 OS 상에서 구현된 것은 다른 OS에 이식하기가 매우 수월하다.

본 내용은 Windows를 기반으로 설명하나 타 OS에서도 동일한 방식으로 구현이 가능함을 상기하기 바란다.

우선 앞서 Node.js와 MongoDB를 연동하는 방법에 대해 알아본 바 있다. 이 방법에 대해 알고 있다는 전제하에 설명을 진행할 것이므로 만약 아직 연동 방법에 대해 모르고 계신 분들은 반드시 먼저 연습해 보시길 바란다.

우선 다음 파일을 다운받는다.

KokoaTalk.zip


압축을 해제하면 2개의 파일("kokoatalk.html", "app.js")과 하나의 폴더("node_modules")가 하나 보일 것이다.

각각에 대해서는 차차 자세히 설명할 것이다.

1. Node.js 패키지 설치하기

Windows에서 console(Mac에서는 Terminal)을 실행하여 프로젝트 폴더로 이동한다.

다음 세 개의 명령어를 입력하여 "connect", "socket.io", "mongoose" 패키지를 다운받는다.


$npm install connect
$npm install socket.io
$npm install mongoose

만약 설치가 제대로 완료되었다면 "node_modules" 폴더에 각 패키지의 이름으로 된 폴더가 들어있을 것이다.

2. 실행화면과 HTML 엘리먼트 관계 파악하기

"kokoatalk.html" 파일과 "app.js" 파일은 독립적인 것이 아니며, "socket.io" 모듈을 통해 서로 데이터를 주고 받는 형태로 이루어져 있으므로 각각의 구조가 어떤 식으로 연관이 되어 있는지 파악하는 것은 매우 중요하다.

"app.js"는 Server 측에서 실행되는 JavaScript 코드이며, "kokoatalk.html"은 Client 측에서 표현되는 HTML 문서이다.

"kokoatalk.html"과 웹 브라우저에서 표현되는 엘리먼트를 연관하여 살펴보자. 우선 Kokoa Talk의 결과화면을 먼저 공개한다.


[Kokoa Talk 실행화면]


다음 그림은 위의 실행화면이 HTML 문서의 어느 부분과 연관이 되어 있는지를 표현한 것이다.


  [Kokoa Talk 실행화면과 HTML 엘리먼트 매칭]

3. HTML 코드 이해

다음 코드는 "kokoatalk.html" 파일이다.


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
75
76
77
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>KOKOA TALK</title>
<meta name = "viewport" content="width=device-width, initial-scale = 1" />
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src = "http://code.jquery.com/jquery-1.7.1.js"></script>
<script src="/socket.io/socket.io.js"></script>
 
<script>
    $(document).ready(function(){
        var socket = io.connect();
        socket.on('message',function(data){
            var output = '';
            output += '<div class="alert alert-info"><strong>';
            output += data.name;
            output += '</strong> : ';
            output += data.message;
            output += '</div>';
            $(output).prependTo('#content');
        });
 
        socket.on('preload',function(data){
            var output = '';
            output += '<div class="alert alert-info"><strong>';
            output += data.name;
            output += '</strong> : ';
            output += data.message;
            output += '</div>';
            $(output).prependTo('#content');
        });
 
 
        $('#button').click(function(){
            socket.emit('message',
            {
                name: $('#name').val(),
                message:$('#message').val()
            }
            );
            $('#message').val('');
        });
 
        $('#message').keydown(function(e) {
            if (e.which == 13) {/* 13 == enter key@ascii */
                socket.emit('message',{
                    name: $('#name').val(),
                    message:$('#message').val()
                });
                $('#message').val('');
            }
        });
    });
</script>
</head>
 
<body>
    <div style="width:500px;margin:50px">
        <h1>Kokoa Talk</h1>
        <div class="form-group">
            <label for="User Name">user name</label>
            <input type="text" class="form-control" id="name" placeholder="User Name"/>
          </div>
          <div class="form-group">
              <label for="Message">message</label>
            <input type="text" class="form-control" id="message" placeholder="Message"/>
        </div>
        <div class="form-group">
            <button id = 'button' type="button" class="btn btn-default" style="width:100%">send message</button>
        </div>
        <div id="content">
        <div>
      </div>
</body>
</html>
cs


<body> 태그 부분은 앞서 설명한 "Kokoa Talk 실행화면과 HTML 엘리먼트 매칭" 이미지를 참고하기 바란다.

다음은 각 주요 태그 엘리먼트들에 대한 설명이다.


1
2
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
cs


Style sheet는 CSS 파운데이션 중 하나인 Bootstrap을 CDN을 통해 링크하였다.


1
<script src="/socket.io/socket.io.js"></script>
cs


앞서 언급한 바와 같이 HTML과 JavaScript 간의 실시간 데이터 통신을 위해 "socket.io" 패키지를 사용하며, 이에 대한 사용을 위해 "socket.io"의 JacaScript 소스를 포함시켰다.


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
<script>
    $(document).ready(function(){
        var socket = io.connect();
        socket.on('message',function(data){
            var output = '';
            output += '<div class="alert alert-info"><strong>';
            output += data.name;
            output += '</strong> : ';
            output += data.message;
            output += '</div>';
            $(output).prependTo('#content');
        });
 
        socket.on('preload',function(data){|
            var output = '';
            output += '<div class="alert alert-info"><strong>';
            output += data.name;
            output += '</strong> : ';
            output += data.message;
            output += '</div>';
            $(output).prependTo('#content');
        });
 
        $('#button').click(function(){
        socket.emit('message',
            {
                name: $('#name').val(),
                                message:$('#message').val()
            }
            );
            $('#message').val('');
        });
 
        $('#message').keydown(function(e) {
            if (e.which == 13) {/* 13 == enter key@ascii */                  
                        socket.emit('message',{
                      name: $('#name').val(),
                  message:$('#message').val()
                     });
            $('#message').val('');
            }
        });
    });
</script>
cs


위의 <script> 태그는 JavaScript 소스이며, 역할은 크게 다음과 같다.

(1) HTML 도큐먼트가 준비 완료 시 실행: $(document).ready(function(){...}

(2) 사용자로부터 "User Name"과 "Message"를 받아 ID="content"를 갖는 <div> 태그에 계속 등록 / 등록 시 jQuery의 prepentTo() 함수를 이용하여 신규 메시지는 가장 위로 등록한다.

(3) Message를 등록하는 텍스트 박스에서 키보드 "enter(ASCII key = 13)"를 입력하거나 "send message" 버튼을 마우스 클릭할 때 데이터(name, message)를 socket.io 모듈의 "socket.emit()" 메써드를 통해 "app.js"에서 처리할 수 있도록 전달한다.

(4) (3)번에서 "socket.emit()" 메써드를 통해 데이터가 전달된 후 Message 텍스트 박스의 내용 제거한다.

(5) "socket.on(...)" 메써드를 살펴보면 입력 파라미터로 하나는 'message', 다른 하나는 'preload'를 볼 수 있다. 이 입력 인자는 "app.js" 코드 내용과도 관련이 있는데 이 부분은 "app.js" 내용 설명 시 자세히 다루기로 우선 역할에 대해서만 설명하도록 하겠다.

(6) socket.on('message', function(data){...}); : 사용자로부터 신규 메시지 등록 시 실행되며 클라이언트 페이지에 "User Name : Message" 형식으로 등록된다.

(7) socket.on('preload', function(data){...}); : 새로운 사용자가 채팅 페이지를 열었을 때 실행되며 기존 사용자들의 기록된 대화 내용을 DB로부터 가져와 표시한다.

(8) $('#button').click(function(){...}); : id="button"을 갖는 메시지 전송 버튼 클릭 시 발생되며, 사용자 이름('name')과 메시지('message')를 socket.io로 "app.js"에서 처리할 수 있도록 전달

(9) $('#message').keydown(function(e) {...}); : id="message"를 갖는 메시지 입력 박스에서 메시지 작성 후 키보드의 ENTER키를 누를 때 실행되며, 역할은 (8)과 같다.

4. "app.js" JavaScript 코드 이해

우선 "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
75
76
77
78
79
80
81
82
83
// opens file system
var fs = require('fs');
 
// creates WebServer
var http = require('http');
var connect = require('connect');
var app = connect();
var socketio = require('socket.io');
 
// open mongoose
var mongoose = require('mongoose');
 
// connects to MongoDB / the name of DB is set to 'kokoatalk'
mongoose.connect('mongodb://localhost/kokoatalk');
 
// get the connection from mongoose
var db = mongoose.connection;
 
// gets notified if error occurs
db.on('error'console.error.bind(console'connection error:'));
 
// executed when the connection opens
db.once('open'function callback () {
    // if connection open succeeds print out the following in the console
      console.log("open: success");
});
 
// creates DB schema for MongoDB / requires 'username' & 'message'
var userSchema = mongoose.Schema({
    username: 'string',
    message: 'string'
});
 
// compiles our schema into a model
var Chat = mongoose.model('Chat', userSchema);
 
// request from web browser
app.use('/'function(req,res,next){
    if(req.url != '/favicon.ico'){
        fs.readFile(__dirname+'/kokoatalk.html'function(error, data){
            res.writeHead(200, {'Content-Type':'text/html'});
            res.write(data);
            res.end();
        });
    }
});
 
// creates server
var server = http.createServer(app);
server.listen(8008function(){
    console.log('server listen on port 8008');
});
 
// creates WebSocket Server
var io = socketio.listen(server);
 
// executed on connection
io.sockets.on('connection'function(socket) {
 
    // receives message from DB
    Chat.find(function (err, result) {
        for(var i = 0 ; i < result.length ; i++) {
            var dbData = {name : result[i].username, message : result[i].message};
            io.sockets.sockets[socket.id].emit('preload', dbData);
        }
    });
 
    // sends message to other users + stores data(username + message) into DB
    socket.on('message'function(data) {
 
        io.sockets.emit('message', data);
        // add chat into the model
        var chat = new Chat({ username: data.name, message: data.message });
 
        chat.save(function (err, data) {
          if (err) {// TODO handle the error
              console.log("error");
          }
          console.log('message is inserted');
        });
 
    });
});
cs


Line 1 - 26 : Node.js와 MongoDB를 연동하는 방법을 참고하시기 바란다.

Line 28 - 32 : Schema를 설정하는 부분이다. 데이터는 "username"과 "message"이다.

Line 35 : 방금 설정한 Schema를 Mongoose model로 컴파일한다. 컴파일 된 model은 "Chat"이라는 변수에 저장된다.

Line 37 - 46 : 웹 브라우저 주소창에 (web host + '/') 입력 시 실행된다. 만약 local host를 사용 중이라면 주소창에 "localhost:<server port number>/" 또는 "127.0.0.1:<server port number>/" 입력 시 실행된다. 참고로 "/"는 브라우저 주소 창에서 생략되더라도 자동으로 입력된 것으로 판단한다. 실행 시 Node.js의 file system 모듈(var fs = require('fs');)에 의해 "kokoatalk.html" 파일을 읽는다.

Line 48 - 52 : Server를 생성하고 8008번 포트를 연다.

Line 55 : Web socket을 열고 방금 생성한 server와 연결한다.

Line 58 : Web socket이 연결되는 순간 실행된다.

Line 60 - 66 : DB로부터 데이터를 가져온다. 가져온 데이터는 "result"에 포함되며 전체 아이템 수(result.length)를 반복하며 각 아이템에 대해 "name"(name : result[i].username)과 "message"(message : result[i].message)로 분류하여 dbData에 저장 후 socket.io 모듈 메써드(io.sockets.sockets[socket.id].emit('preload', dbData);)를 통해 HTML로 데이터를 전달한다. 이 때 "emit(... , ...)" 메써드의 첫번째 입력 인자로 "preload"를 두번째 입력 인자로 "dbData" 사용하였는데 이는 HTML의 "socket.on(...)" 메써드와 짝이 되어 HTML측에 dbData를 전달한다.

Line 68 - 83 : HTML측에서 "message"가 event 발생 시, (1) 채팅 정보(username / message)를 다른 사용자에게 전달하여 각 사용자의 HTML 페이지에 렌더링(io.sockets.emit('message', data);) (2) 현재DB의 collection(model)에 추가(var chat = new Chat({ username: data.name, message: data.message });) 후 저장(chat.save(function (err, data) {...});)한다. 만약 저장 시 에러가 발생하면 "error" 메시지가 출력되며 성공하면 "message is inserted"가 출력된다.

5. 전체 실행 구조의 이해

지금까지 앞서 설명된 부분의 디테일에 집중하였다면 이제 전체적인 실행 구조를 이해할 필요가 있다. 오히려 전체적인 실행 구조를 이해한다면 역으로 앞서 설명한 부분을 더욱 잘 이해할 수 있으리라 믿는다.

또한 MongoDB를 잘 이해하더라도 mongoose에 익숙하지 않으면 더욱더 혼란스러웠을 것이다.

전체 실행 구조를 이해하여 전체적인 이해를 마무리 짓기로 한다.


[전체 실행 구조] (클릭해서 큰 이미지로 보기 권장)

Comments