클라우드 컴퓨팅 & NoSQL/Vert.x & Node.js

빠르게 훝어 보는 node.js - #11 Socket.IO 3/4 (1:1 귓속말 구현)

Terry Cho 2014. 4. 24. 22:55

빠르게 훝어보는 node.js

#11 - Socket.IO (3/4)

조대협 (http://bcho.tistory.com)


앞서 작성한 대화방 http://bcho.tistory.com/896 에 이어서 이번에는 1:1 귓속말 대화가 가능한 기능을 추가해보자


귓속말이 가능한 대화방

이번에는 특정 사용자가 다른 사용자에게 귓속말을 보내는 기능을 가지는 대화방을 만들어보자. 이를 통해서 특정 클라이언트 소켓에 메세지를 어떻게 보내는지를 배울 수 있다.

이 대화방의 기능은 다음과 같다.

  • Ÿ   대화방에 입장하는 손님들은 자동으로 대화명을 부여 받는다.

  • Ÿ   사용자는 대화명을 바꿀 수 있다.

  • Ÿ   사용자는 대화 수신자의 대화명을 선택하여 특정 사용자에게 귓속말을 보낼 수 있다.

코드를 보면서 설명하도록 하자.

app.js

var express = require('express');

var routes = require('./routes');

var http = require('http');

var path = require('path');

 

var app = express();

app.use(express.static(path.join(__dirname, 'public')));

 

var httpServer =http.createServer(app).listen(3000, function(req,res){

    console.log('Socket IO server has been started');

});

// upgrade http server to socket.io server

var io = require('socket.io').listen(httpServer);

 

var socket_ids = [];

var count = 0;

 

function registerUser(socket,nickname){

    // socket_id nickname 테이블을 셋업

    socket.get('nickname',function(err,pre_nick){

        if(pre_nick != undefined ) delete socket_ids[pre_nick];

        socket_ids[nickname] = socket.id

        socket.set('nickname',nickname,function(){

            io.sockets.emit('userlist',{users:Object.keys(socket_ids)});

        });

 

    });

}

 

io.sockets.on('connection',function(socket){

    socket.emit('new',{nickname:'GUEST-'+count});

    registerUser(socket,'GUEST-'+count);

    count++;

 

    socket.on('changename',function(data){

        registerUser(socket,data.nickname);

    });

    socket.on('disconnect',function(data){

        socket.get('nickname',function(err,nickname){

            if(nickname != undefined){

                delete socket_ids[nickname];

                io.sockets.emit('userlist',{users:Object.keys(socket_ids)});

                                

            }// if

        });

     });

    socket.on('send_msg',function(data){

        socket.get('nickname',function(err,nickname){

 

            data.msg = nickname + ' : '+data.msg;

            if(data.to =='ALL') socket.broadcast.emit('broadcast_msg',data); // 자신을 제외하고 다른 클라이언트에게 보냄

            else{

                socket_id = socket_ids[data.to];

                if(socket_id != undefined){

                    io.sockets.socket(socket_id).emit('broadcast_msg',data);

                }// if

            }

            socket.emit('broadcast_msg',data);

        });

    });

});

 

먼저 Express에서 socket.io를 사용할 준비를 하고

특정 사용자 즉 클라이언트에게만 메세지를 보내려면

io.sockets.socket(socket_id).emit

메세드를 사용해야 한다. 해당 클라이언트의 socket_id를 알아야 하며, 우리는 대화명(이하 nickname)을 통해서 특정 사용자에게 메세지를 보낼 것이기 때문에 nickname 에서 socket_id로의 맵핑 테이블이 필요하다.

var socket_ids = []

에서 socket_ids nickname to socket.id에 대한 맵핑 정보를 저장한다.

io.sockets.on('connection',function(socket){

    socket.emit('new',{nickname:'GUEST-'+count});

    registerUser(socket,'GUEST-'+count);

    count++;

클라이언트가 접속되면 new 라는 이벤트를 통해서 nickname을 생성해서 보낸다. nickname은 사용자가 접속한 순서대로 GUEST-0,GUEST-1,… 식으로 순차적으로 이름을 배정한다.

다음으로, 새로운 사용자가 추가되었음을 알리고, 현재 사용자 리스트들을 보내야 하는데, 이를 registerUser 메서드에서 수행한다.

function registerUser(socket,nickname){

    // socket_id nickname 테이블을 셋업

    socket.get('nickname',function(err,pre_nick){

        if(pre_nick != undefined ) delete socket_ids[pre_nick];

        socket_ids[nickname] = socket.id

        socket.set('nickname',nickname,function(){

            io.sockets.emit('userlist',{users:Object.keys(socket_ids)});

        });

 

    });

}

먼저 register user에서는 socket.get을 이용하여 현재의 클라이언트 소켓의 nicknamepre_nick이라는 변수로 읽어온다. 대화명이 바뀔 경우 기존에 socket_ids에 기존의 대화명으로 저장되어 있는 socket.id를 삭제하기 위함이다. 기존의 데이타를 삭제 하였으면, socket_ids nickname Key 값으로 하여, socket.id를 저장한다. 다음. nickname을 해당 socket set명령을 이용해서 저장한후에, userlist라는 이벤트를 통해서, 현재 접속된 사용자의 nickname들을 보낸다. nickname들은 socket_ids property의 키들로 저장이 되었기 때문에, Object.keys(socket_ids)를 이용하여 nickname 리스트를 추출할 수 있다.

마찬가지로, 대화명이 변경되었을 때에도 registerUser 함수를 이용하여 전체 사용자의 리스트를 업데이트 하여 클라이언트에게 보내준다.

socket.on('changename',function(data){

        registerUser(socket,data.nickname);

    });

만약에 해당 클라이언트가 브라우져를 닫았을 경우에는 대화방을 떠난 것으로 간주하여, ‘disconnect’이벤트에 대해서,

socket.on('disconnect',function(data){

        socket.get('nickname',function(err,nickname){

            if(nickname != undefined){

                delete socket_ids[nickname];

            

현재 socket nickname을 읽어서 socket_ids에서 해당 nickname에 해당 하는 데이타를 삭제한 후에,

io.sockets.emit('userlist',{users:Object.keys(socket_ids)});

를 이용하여, 업데이트된 사용자 목록을 다시 클라이언트들이 업데이트 하도록 이벤트를 보낸다.

마지막으로 클라이언트로 부터 대화메세지를 다른 사용자에게 대화 메세지를 보내는 “send_msg” 이벤트가 들어왔을때

socket.on('send_msg',function(data){

        socket.get('nickname',function(err,nickname){

 

            data.msg = nickname + ' : '+data.msg;

에서 처리하는데, 현재 클라이언트의 nickname socket.get을 이용해서 읽어와서 보낼 메세지문자열을 대화명”+”대화내용으로 만들어서 저장한다.

만약에 전체 메세지인 경우 data.to ‘ALL’로 들어오는데, 이경우 broadcast를 하고

if(data.to =='ALL') socket.broadcast.emit('broadcast_msg',data);

특정 nickname data.to에 들어오는 경우 귓속말로 간주하여, nickname을 이용해서 socket_ids로 부터 해당 nickname을 사용하는 클라이언트의 socket.id를 가져온후

socket_id = socket_ids[data.to];

                if(socket_id != undefined){

다음으로, 해당 socket_id로 메세지를 보낸다.

io.sockets.socket(socket_id).emit('broadcast_msg',data);

다음은 위의 서버를 사용하기 위한 HTML 클라이언트 폼이다.

index.html

<html>

<head>

 

    <title></title>

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

    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>

 

</head>

<body>

<b>Send message</b><p>

    Name <input type="text" id="nickname" /> <input type="button" id="changename" value="Change name"/><br>

    To

    <select id="to">

        <option value="ALL">ALL</option>

    </select>

    Message  <input type="text" id="msgbox"/>

    <br>

    <span id="msgs"></span>

 

    <script type="text/javascript">

        var socket = io.connect('http://localhost');

        $('#changename').click(function(){

            socket.emit('changename',{nickname:$('#nickname').val()});

        });

        $("#msgbox").keyup(function(event) {

            if (event.which == 13) {

                socket.emit('send_msg',{to:$('#to').val(),msg:$('#msgbox').val()});

                $('#msgbox').val('');

            }

        });

        socket.on('new',function(data){

            console.log(data.nickname);

            $('#nickname').val(data.nickname);

        });

 

        // 새로운 사용자가 들어오거나, 사용자가 이름을 바꿨을때 "To" 리스트를 변경함

        socket.on('userlist',function(data){

            var users = data.users;

            console.log(users);

            console.log(data.users.length);

            $('#to').empty().append('<option value="ALL">ALL</option>');

            for(var i=0;i<data.users.length;i++){

                $('#to').append('<option value="'+users[i]+'">'+users[i]+"</option>");

            }

        });

 

        socket.on('broadcast_msg',function(data){

            console.log(data.msg);

            $('#msgs').append(data.msg+'<BR>');

        });

    </script>

</body>

</html>


앞에서 작성한 채팅 프로그램과 크게 다를바는 없으나, new 이벤트와 userlist 이벤트 핸들러가 추가되었다.

‘new’ 이벤트는 채팅방에 들어왔을때, 서버로 부터 대화명과 함께 보내지며, 클라이언트에서는 이 대화명을 받아서, “대화명 창부분에 세팅 한다.

socket.on('new',function(data){

            console.log(data.nickname);

            $('#nickname').val(data.nickname);

        });

‘userlist’ 이벤트는 현재 대화방에 있는 사용자들의 nickname을 모두 받은 후에, 대화 상대를 지정하는 HTML <select> box 부분에 대화명들을 넣어준다.

socket.on('userlist',function(data){

            var users = data.users;

            console.log(users);

            console.log(data.users.length);

            $('#to').empty().append('<option value="ALL">ALL</option>');

            for(var i=0;i<data.users.length;i++){

                $('#to').append('<option value="'+users[i]+'">'+users[i]+"</option>");

            }

        });

다음은 위의 예제를 실행한 화면이다. 아래와 같이 TO 부분에서 사용자를 지정하고 메세지를 보내면 특정 사용자에게만 메세지가 전달됨을 확인할 수 있다.



다음에는 대화방의 기능을 추가하여, socket.io의 room 개념에 대해서 알아보도록 한다.