Doit! - 채팅서버 만들기

노드에서 채팅 기능을 만들려면 웹 소켓(Web Socket)을 사용해야한다. 웹 소켓은 웹 서버로 소켓을 연결한 후 데이터를 주고받을 수 있도록 만든 HTML5표준이다. 이러한 웹 소켓은 HTTP 프로토콜로 소켓 연결을 하기 때문에 웹 브라우저가 이 기능을 지원하지 않으면 사용할 수 없다. socket.io모듈을 사용하면 웹 소켓을 지원하지 않는 웹 브라우저에서도 웹 소켓을 사용할 수 있다.

10-1 socket.io 사용하기

$ npm install socket.io --save

socket.io를 사용하려면 cors모듈도 설치되어 있어야한다. CORS(Corss-Origin Resource Sharing)를 사용하면 Ajax를 사용해 데이터를 가져올 때 현재 보고 있는 브라우저의 웹 문서를 제공한 웹 서버 이외에 다른 웹 서버에서는 접속할 수 없는 제약이 풀린다.

$ npm install cors --save

프로젝트 구성

파일 or 폴더

설명

app.js

기본 코드가 들어 있는 메인 파일

/config/config.js

설정 정보가 들어 있는 파일 스키마 모듈이나 라우팅 모듈을 만들어 추가했다면 이 파일에 설정 정보를 추가

/database

데이터베이스의 스키마 모듈 파일들을 만들어 넣는 폴더

/routes

라우팅 함수들을 모듈 파일로 만들어 넣는 폴더

/views

뷰 템플릿 파일들을 만들어 넣는 폴더

//app.js
...

// socket.io 모듈 불러들이기
var socketio = require('socket.io');

// cors 사용 - 클라이언트에서 ajax로 요청하면 CORS 지원
var cors = require('cors');

...

//cors를 미들웨어로 사용하도록 등록
app.use(cors());

...

// socket.io 서버를 시작
var io = socketio.listen(server);
console.log("socket.io 요청을 받아들일 준비가 되었습니다.");

http모듈로 실행한 익스프레스 서버는 server변수에 저장되어 있으므로 그 아랫부분에서 socket.io모듈 객체의 listen()메소드를 호출한다.

socket.io모듈로 웹 소켓 요청 처리 메소드

메소드 이름

설명

attach(httpServer,options)

웹 서버 인스턴스가 socket.io를 처리한다.

listen(httpServer,options)

웹 서버 인스턴스가 socket.io를 처리한다.

socket.io 서버를 웹 서버 위에서 동작하도록 설정하면 웹 소켓과 관련된 요청은 모두 socket.io에서 처리한다. socket.io객체는 io변수에 할당되었는데 이 객체에 들어 있는 sockets객체는 클라이언트가 접속하거나 데이터를 전송했을 때 이벤트를 발생시킨다.

...
//클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection',function(socket){
    console.log('connection info : ',socket.request.connection._peername);

    // 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
    socket.remoteAddress = socket.request.connection._peername.address;
    socket.remotePort = socket.request.connection._peername.port;
});

on() 메소드로 connection 이벤트를 처리하는 콜백함수를 등록하면 콜백 함수 쪽으로 소켓 객체가 전달된다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>채팅 클라이언트 01</title>

        <script src="jquery-3.1.1.min.js"></script>      
        <script src="socket.io.js"></script>

        <script>
            var host;
            var port;
            var socket;

             // 문서 로딩 후 실행됨
            $(function() {

                $("#connectButton").bind('click', function(event) {
                    println('connectButton이 클릭되었습니다.');

                    host = $('#hostInput').val();
                    port = $('#portInput').val();

                    connectToServer();
                });

            });

            // 서버에 연결하는 함수 정의
            function connectToServer() {

                var options = {'forceNew':true};
                var url = 'http://' + host + ':' + port;
                socket = io.connect(url, options);

                socket.on('connect', function() {
                    println('웹소켓 서버에 연결되었습니다. : ' + url);
                });

                socket.on('disconnect', function() {
                    println('웹소켓 연결이 종료되었습니다.');
                });

            }

            function println(data) {
                console.log(data);
                $('#result').append('<p>' + data + '</p>');
            }
        </script>
    </head>
<body>
    <h3>채팅 클라이언트 01</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>

socket.io는 메시지를 주고받을 때 이벤트 처리 방식을 사용한다.

송수신 이벤트 처리 메소드

메소드

설명

on(event,callback)

이벤트 수신 형태로 메시지를 수신했을 때 처리할 콜백 함수를 등록한다. 콜백 함수의 파라미터로 수신한 객체가 전달된다.

emit(event,object)

이벤트 송신 형태로 메시지를 송신한다.

이벤트의 이름은 마음대로 정할 수 있다. 즉, 사용자 정의 이벤트와 같다. Echo기능은 서버에 보낸 데이터를 그대로 다시 돌려받는 기능이다.

socket.on('connect', function() {
   println('웹소켓 서버에 연결되었습니다. : ' + url);

   socket.on('message',function(message){
   console.log(JSON.stringify(message));

   println('<p>수신 메시지 : '+message.sender+','+message.recepient+','+ message.command+','+message.type+','+message.data+'</p>');
    });
});
  • sender : 보내는 사람의 아이디

  • recepient : 받는 사람의 아이디

  • command : 데이터의 종류

  • type : 전송될 데이터의 형태

  • data : 데이터

메세지 전송방법

메소드

설명

io.sockets.emit(event,object)

나를 포함한 모든 클라이언트에 전송

socket.broadcast.emit(event,object)

나를 제외한 모든 클라이언트에 전송

10-2 일대일 채팅하기

일대일 채팅은 상대방을 지정하여 메시지를 보내야 하므로 서버에 연결된 각 클라이언트마다 고유한 정보가 있어야한다. 클라이언트가 로그인할 때 사용하는 로그인 아이디를 사용해 클라이언트를 구별할 수 있도록 하는 것이 좋다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>채팅 클라이언트 03</title>

        <script src="jquery-3.1.1.min.js"></script>    
        <script src="socket.io.js"></script>

        <script>
            var host;
            var port;
            var socket;

            // 문서 로딩 후 실행됨
            $(function() {

                // 연결하기 버튼 클릭 처리
                $("#connectButton").bind('click', function(event) {
                    println('connectButton이 클릭되었습니다.');

                    host = $('#hostInput').val();
                    port = $('#portInput').val();

                    connectToServer();
                });

                // 전송 버튼 클릭 시 처리
                $("#sendButton").bind('click', function(event) {
                    var sender = $('#senderInput').val();
                    var recepient = $('#recepientInput').val();
                    var data = $('#dataInput').val();

                    var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('message', output);
                });

                // 로그인 버튼 클릭 시 처리
                $("#loginButton").bind('click', function(event) {
                    var id = $('#idInput').val();
                    var password = $('#passwordInput').val();
                    var alias = $('#aliasInput').val();
                    var today = $('#todayInput').val();

                    var output = {id:id, password:password, alias:alias, today:today};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('login', output);
                });

            });

            // 서버에 연결하는 함수 정의
            function connectToServer() {

                var options = {'forceNew':true};
                var url = 'http://' + host + ':' + port;
                socket = io.connect(url, options);

                socket.on('connect', function() {
                    println('웹소켓 서버에 연결되었습니다. : ' + url);

                    socket.on('message', function(message) {
                        console.log(JSON.stringify(message));

                        println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
                    });

                    socket.on('response', function(response) {
                        console.log(JSON.stringify(response));
                        println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                    });

                });

                socket.on('disconnect', function() {
                    println('웹소켓 연결이 종료되었습니다.');
                });

            }

            function println(data) {
                console.log(data);
                $('#result').append('<p>' + data + '</p>');
            }
        </script>
    </head>
<body>
    <h3>채팅 클라이언트 03 : 일대일 채팅하기</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>
    <br>
    <div>
        <input type="text" id="idInput" value="test01" />
        <input type="password" id="passwordInput" value="123456" />
        <input type="text" id="aliasInput" value="소녀시대" />
        <input type="text" id="todayInput" value="좋은 날!" />

        <input type="button" id="loginButton" value="로그인" />
        <input type="button" id="logoutButton" value="로그아웃" />
    </div>
    <br>
    <div>
        <div><span>보내는사람 아이디 :</span> <input type="text" id="senderInput" value="test01" /></div>
        <div><span>받는사람 아이디 :</span> <input type="text" id="recepientInput" value="ALL" /></div>
        <div><span>메시지 데이터 :</span> <input type="text" id="dataInput" value="안녕!"/> </div>
        <br>
        <input type="button" id="sendButton" value="전송" />
    </div>    

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');

// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');

// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');

// Session 미들웨어 불러오기
var expressSession = require('express-session');


//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');


// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');

// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');

// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');



// Socket.IO 사용
var socketio = require('socket.io');

// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');



// 익스프레스 객체 생성
var app = express();


//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');


//===== 서버 변수 설정 및 static으로 public 폴더 설정  =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);


// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))

// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())

// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));

// cookie-parser 설정
app.use(cookieParser());

// 세션 설정
app.use(expressSession({
  secret:'my key',
  resave:true,
  saveUninitialized:true
}));



//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());


//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());



//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);


// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);

// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);



//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
 static: {
   '404': './public/404.html'
 }
});

app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );


//===== 서버 시작 =====//

//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
  console.log('uncaughtException 발생함 : ' + err);
  console.log('서버 프로세스 종료하지 않고 유지함.');

  console.log(err.stack);
});

// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
    console.log("프로세스가 종료됩니다.");
    app.close();
});

app.on('close', function () {
  console.log("Express 서버 객체가 종료됩니다.");
  if (database.db) {
    database.db.close();
  }
});

// 시작된 서버 객체를 리턴받도록 합니다. 
var server = http.createServer(app).listen(app.get('port'), function(){
  console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));

  // 데이터베이스 초기화
  database.init(app, config);

});


//===== Socket.IO를 이용한 채팅 테스트 부분 =====//


// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};


// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');

// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
  console.log('connection info :', socket.request.connection._peername);

    // 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
    socket.remoteAddress = socket.request.connection._peername.address;
    socket.remotePort = socket.request.connection._peername.port;


    // 'login' 이벤트를 받았을 때의 처리
    socket.on('login', function(login) {
      console.log('login 이벤트를 받았습니다.');
      console.dir(login);

        // 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
        console.log('접속한 소켓의 ID : ' + socket.id);
        login_ids[login.id] = socket.id;
        socket.login_id = login.id;

        console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);

        // 응답 메시지 전송
        sendResponse(socket, 'login', '200', '로그인되었습니다.');
    });


    // 'message' 이벤트를 받았을 때의 처리
    socket.on('message', function(message) {
      console.log('message 이벤트를 받았습니다.');
      console.dir(message);

        if (message.recepient =='ALL') {
            // 나를 포함한 모든 클라이언트에게 메시지 전달
          console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
            io.sockets.emit('message', message);
        } else {
          // 일대일 채팅 대상에게 메시지 전달
          if (login_ids[message.recepient]) {
            io.sockets.connected[login_ids[message.recepient]].emit('message', message);

            // 응답 메시지 전송
            sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
          } else {
            // 응답 메시지 전송
            sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
          }
        }
    });

});


// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
  var statusObj = {command: command, code: code, message: message};
  socket.emit('response', statusObj);
}

대상 소켓을 찾기위해서는 io.sockets.connected[login_ids[messagae.recepient]]를 사용해야한다.

배열의 요소는 delete키워드나 배열의 splice()메소드등을 이용해 여러가지 방법을 사용할 수 있다.

10-3 그룹 채팅하기

그룹 채팅은 방을 만들고 그 방에 초대된 사람끼리 동시에 메시지를 주고받는다.

방만들기

socket.io모듈은 몇 명의 사용자를 하나으 ㅣ방에 모아 두고 방에 들어온 특정 클라이언트에만 메시지를 전송할 수 있는 기능을 제공한다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>채팅 클라이언트 04</title>

        <script src="jquery-3.1.1.min.js"></script>     
        <script src="socket.io.js"></script>

        <script>
            var host;
            var port;
            var socket;

             // 문서 로딩 후 실행됨
            $(function() {

                // 연결하기 버튼 클릭 처리
                $("#connectButton").bind('click', function(event) {
                    println('connectButton이 클릭되었습니다.');

                    host = $('#hostInput').val();
                    port = $('#portInput').val();

                    connectToServer();
                });

                // 전송 버튼 클릭 시 처리
                $("#sendButton").bind('click', function(event) {
                    var sender = $('#senderInput').val();
                    var recepient = $('#recepientInput').val();
                    var data = $('#dataInput').val();

                    var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('message', output);
                });

                // 로그인 버튼 클릭 시 처리
                $("#loginButton").bind('click', function(event) {
                    var id = $('#idInput').val();
                    var password = $('#passwordInput').val();
                    var alias = $('#aliasInput').val();
                    var today = $('#todayInput').val();

                    var output = {id:id, password:password, alias:alias, today:today};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('login', output);
                });

                 // 방만들기 버튼 클릭 시 처리
                $("#createRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();
                    var roomName = $('#roomNameInput').val();
                    var id = $('#idInput').val();

                    var output = {command:'create', roomId:roomId, roomName:roomName, roomOwner:id};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                 // 방이름바꾸기 버튼 클릭 시 처리
                $("#updateRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();
                    var roomName = $('#roomNameInput').val();
                    var id = $('#idInput').val();

                    var output = {command:'update', roomId:roomId, roomName:roomName, roomOwner:id};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                 // 방없애기 버튼 클릭 시 처리
                $("#deleteRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();

                    var output = {command:'delete', roomId:roomId};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

            });

            // 서버에 연결하는 함수 정의
            function connectToServer() {

                var options = {'forceNew':true};
                var url = 'http://' + host + ':' + port;
                socket = io.connect(url, options);

                socket.on('connect', function() {
                    println('웹소켓 서버에 연결되었습니다. : ' + url);

                    socket.on('message', function(message) {
                        console.log(JSON.stringify(message));

                        println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
                    });

                    socket.on('response', function(response) {
                        console.log(JSON.stringify(response));
                        println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                    });

                    // 그룹 채팅에서 방과 관련된 이벤트 처리
                    socket.on('room', function(data) {
                        console.log(JSON.stringify(data));

                        println('<p>방 이벤트 : ' + data.command + '</p>');
                        println('<p>방 리스트를 받았습니다.</p>');
                        if (data.command == 'list') { // 방 리스트
                            var roomCount = data.rooms.length;
                            $("#roomList").html('<p>방 리스트 ' + roomCount + '개</p>');
                            for (var i = 0; i < roomCount; i++) {
                                $("#roomList").append('<p>방 #' + i + ' : ' + data.rooms[i].id + ', ' + data.rooms[i].name + ', ' + data.rooms[i].owner + '</p>');
                            }
                        }
                    });

                });

                socket.on('disconnect', function() {
                    println('웹소켓 연결이 종료되었습니다.');
                });

            }

            function println(data) {
                console.log(data);
                $('#result').append('<p>' + data + '</p>');
            }
        </script>
    </head>
<body>
    <h3>채팅 클라이언트 04 : 그룹 채팅하기</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>
    <br>
    <div>
        <input type="text" id="idInput" value="test01" />
        <input type="password" id="passwordInput" value="123456" />
        <input type="text" id="aliasInput" value="소녀시대" />
        <input type="text" id="todayInput" value="좋은 날!" />

        <input type="button" id="loginButton" value="로그인" />
        <input type="button" id="logoutButton" value="로그아웃" />
    </div>
    <br>
    <div>
        <input type="text" id="roomIdInput" value="meeting01" />
        <input type="text" id="roomNameInput" value="청춘들의대화" />

        <input type="button" id="createRoomButton" value="방만들기" />
        <input type="button" id="updateRoomButton" value="방이름바꾸기" />
        <input type="button" id="deleteRoomButton" value="방없애기" />
    </div>
    <br>
    <div id="roomList">

    </div>
    <br>
    <div>
        <div><span>보내는사람 아이디 :</span> <input type="text" id="senderInput" value="test01" /></div>
        <div><span>받는사람 아이디 :</span> <input type="text" id="recepientInput" value="ALL" /></div>
        <!-- command 선택 <select> 채팅, 그룹채팅 -->
        <div><span>메시지 데이터 :</span> <input type="text" id="dataInput" value="안녕!"/> </div>
        <br>
        <input type="button" id="sendButton" value="전송" />
    </div>    

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>

클라이언트에서는 만든 방의 리스트만 전달받는다.

// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');

// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');

// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');

// Session 미들웨어 불러오기
var expressSession = require('express-session');


//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');


// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');

// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');

// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');



// Socket.IO 사용
var socketio = require('socket.io');

// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');



// 익스프레스 객체 생성
var app = express();


//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');


//===== 서버 변수 설정 및 static으로 public 폴더 설정  =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);


// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))

// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())

// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));

// cookie-parser 설정
app.use(cookieParser());

// 세션 설정
app.use(expressSession({
  secret:'my key',
  resave:true,
  saveUninitialized:true
}));



//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());


//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());



//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);


// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);

// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);



//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
 static: {
   '404': './public/404.html'
 }
});

app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );


//===== 서버 시작 =====//

//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
  console.log('uncaughtException 발생함 : ' + err);
  console.log('서버 프로세스 종료하지 않고 유지함.');

  console.log(err.stack);
});

// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
    console.log("프로세스가 종료됩니다.");
    app.close();
});

app.on('close', function () {
  console.log("Express 서버 객체가 종료됩니다.");
  if (database.db) {
    database.db.close();
  }
});

// 시작된 서버 객체를 리턴받도록 합니다. 
var server = http.createServer(app).listen(app.get('port'), function(){
  console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));

  // 데이터베이스 초기화
  database.init(app, config);

});


//===== Socket.IO를 이용한 채팅 테스트 부분 =====//


// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};


// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');

// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
  console.log('connection info :', socket.request.connection._peername);

    // 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
    socket.remoteAddress = socket.request.connection._peername.address;
    socket.remotePort = socket.request.connection._peername.port;


    // 'login' 이벤트를 받았을 때의 처리
    socket.on('login', function(login) {
      console.log('login 이벤트를 받았습니다.');
      console.dir(login);

        // 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
        console.log('접속한 소켓의 ID : ' + socket.id);
        login_ids[login.id] = socket.id;
        socket.login_id = login.id;

        console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);

        // 응답 메시지 전송
        sendResponse(socket, 'login', '200', '로그인되었습니다.');
    });


    // 'message' 이벤트를 받았을 때의 처리
    socket.on('message', function(message) {
      console.log('message 이벤트를 받았습니다.');
      console.dir(message);

        if (message.recepient =='ALL') {
            // 나를 포함한 모든 클라이언트에게 메시지 전달
          console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
            io.sockets.emit('message', message);
        } else {
          // 일대일 채팅 대상에게 메시지 전달
          if (login_ids[message.recepient]) {
            io.sockets.connected[login_ids[message.recepient]].emit('message', message);

            // 응답 메시지 전송
            sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
          } else {
            // 응답 메시지 전송
            sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
          }
        }
    });

    // room 이벤트를 받았을 때의 처리
    socket.on('room',function(room){
        console.log("room 이벤트를 받았습니다.");
        console.dir(room);

        if(room.command == 'create'){
            if(io.sockets.adapter.rooms[room.roomId]){//방이 이미 만들어져 있는경우
                console.log("방이 이미 만들어져 있습니다.");
            }else{
                console.log("방을 새로 만듭니다.");

                socket.join(room.roomId);

                var curRoom = io.sockets.adapter.rooms[room.roomId];
                curRoom.id = room.roomId;
                curRoom.name = room.roomName;
                curRoom.owner = room.roomOwner;
            }
        }else if(room.command == 'update'){
            var curRoom = io.sockets.adapter.rooms[room.roomId];
                curRoom.id = room.roomId;
                curRoom.name = room.roomName;
                curRoom.owner = room.roomOwner;
        }else if (room.command == 'delete') {
            socket.leave(room.roomId);

            if(io.sockets.adapter.rooms[room.roomId]){//방이 만들어져 있는 경우
                delete io.sockets.adapter.rooms[room.roomId];
            }else{
                console.log("방이 만들어져 있지 않습니다.");
            }
        }

        var roomList = getRoomList();

        var output = {command : 'list',rooms: roomList};
        console.log("클라이언트로 보낼 데이터 : "+JSON.stringify(output));

        io.sockets.emit('room',output);
    });

});

function getRoomList() {
    console.dir(io.sockets.adapter.rooms);

    var roomList = [];

    Object.keys(io.sockets.adapter.rooms).forEach(function(roomId) { // for each room
        console.log('current room id : ' + roomId);
        var outRoom = io.sockets.adapter.rooms[roomId];

        // find default room using all attributes
        var foundDefault = false;
        var index = 0;
        Object.keys(outRoom.sockets).forEach(function(key) {
            console.log('#' + index + ' : ' + key + ', ' + outRoom.sockets[key]);

            if (roomId == key) {  // default room
                foundDefault = true;
                console.log('this is default room.');
            }
            index++;
        });

        if (!foundDefault) {
            roomList.push(outRoom);
        }
    });

    console.log('[ROOM LIST]');
    console.dir(roomList);

    return roomList;
}

// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
  var statusObj = {command: command, code: code, message: message};
  socket.emit('response', statusObj);
}

방에 입장 / 퇴장 메소드

메소드

설명

join(roomName)

방에 입장한다. 방이 없으면 방을 새로 만든다.

leave(roomName)

방에서 나온다.

socket.io 모듈에서 방정보를 관리하고 있다. 방 객체에 속성을 추가하는 것도 가능하다. 방 정보는 io.sockets.adapter.rooms에 들어있다. getRoomList()는 처음부터 만들어져 있던 방이다.

그룹 채팅에서 메시지 보내기

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>채팅 클라이언트 05</title>

        <script src="jquery-3.1.1.min.js"></script>     
        <script src="socket.io.js"></script>

        <script>
            var host;
            var port;
            var socket;

            // 문서 로딩 후 실행됨
            $(function() {

                // 연결하기 버튼 클릭 처리
                $("#connectButton").bind('click', function(event) {
                    println('connectButton이 클릭되었습니다.');

                    host = $('#hostInput').val();
                    port = $('#portInput').val();

                    connectToServer();
                });

                // 전송 버튼 클릭 시 처리
                $("#sendButton").bind('click', function(event) {

                    // chattype 구분
                    var chattype = $('#chattype option:selected').val();

                    var sender = $('#senderInput').val();
                    var recepient = $('#recepientInput').val();
                    var data = $('#dataInput').val();

                    var output = {sender:sender, recepient:recepient, command:chattype, type:'text', data:data};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('message', output);
                });

                // 로그인 버튼 클릭 시 처리
                $("#loginButton").bind('click', function(event) {
                    var id = $('#idInput').val();
                    var password = $('#passwordInput').val();
                    var alias = $('#aliasInput').val();
                    var today = $('#todayInput').val();

                    var output = {id:id, password:password, alias:alias, today:today};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('login', output);
                });

                // 방만들기 버튼 클릭 시 처리
                $("#createRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();
                    var roomName = $('#roomNameInput').val();
                    var id = $('#idInput').val();

                    var output = {command:'create', roomId:roomId, roomName:roomName, roomOwner:id};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                // 방이름바꾸기 버튼 클릭 시 처리
                $("#updateRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();
                    var roomName = $('#roomNameInput').val();
                    var id = $('#idInput').val();

                    var output = {command:'update', roomId:roomId, roomName:roomName, roomOwner:id};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                // 방없애기 버튼 클릭 시 처리
                $("#deleteRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();

                    var output = {command:'delete', roomId:roomId};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                // 방입장하기 버튼 클릭 시 처리
                $("#joinRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();

                    var output = {command:'join', roomId:roomId};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

                // 방나가기 버튼 클릭 시 처리
                $("#leaveRoomButton").bind('click', function(event) {
                    var roomId = $('#roomIdInput').val();

                    var output = {command:'leave', roomId:roomId};
                    console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                    if (socket == undefined) {
                        alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                        return;
                    }

                    socket.emit('room', output);
                });

            });

            // 서버에 연결하는 함수 정의
            function connectToServer() {

                var options = {'forceNew':true};
                var url = 'http://' + host + ':' + port;
                socket = io.connect(url, options);

                socket.on('connect', function() {
                    println('웹소켓 서버에 연결되었습니다. : ' + url);

                    socket.on('message', function(message) {
                        console.log(JSON.stringify(message));

                        println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
                    });

                    socket.on('response', function(response) {
                        console.log(JSON.stringify(response));
                        println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                    });

                    // 그룹 채팅에서 방과 관련된 이벤트 처리
                    socket.on('room', function(data) {
                        console.log(JSON.stringify(data));

                        println('<p>방 이벤트 : ' + data.command + '</p>');
                        println('<p>방 리스트를 받았습니다.</p>');
                        if (data.command == 'list') { // 방 리스트
                            var roomCount = data.rooms.length;
                            $("#roomList").html('<p>방 리스트 ' + roomCount + '개</p>');
                            for (var i = 0; i < roomCount; i++) {
                                $("#roomList").append('<p>방 #' + i + ' : ' + data.rooms[i].id + ', ' + data.rooms[i].name + ', ' + data.rooms[i].owner + '</p>');
                            }
                        }
                    });

                });

                socket.on('disconnect', function() {
                    println('웹소켓 연결이 종료되었습니다.');
                });

            }

            function println(data) {
                console.log(data);
                $('#result').append('<p>' + data + '</p>');
            }
        </script>
    </head>
<body>
    <h3>채팅 클라이언트 05 : 그룹 채팅하기</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>
    <br>
    <div>
        <input type="text" id="idInput" value="test01" />
        <input type="password" id="passwordInput" value="123456" />
        <input type="text" id="aliasInput" value="소녀시대" />
        <input type="text" id="todayInput" value="좋은 날!" />

        <input type="button" id="loginButton" value="로그인" />
        <input type="button" id="logoutButton" value="로그아웃" />
    </div>
    <br>
    <div>
        <input type="text" id="roomIdInput" value="meeting01" />
        <input type="text" id="roomNameInput" value="청춘들의대화" />

        <input type="button" id="createRoomButton" value="방만들기" />
        <input type="button" id="updateRoomButton" value="방이름바꾸기" />
        <input type="button" id="deleteRoomButton" value="방없애기" />
    </div>
    <br>
    <div id="roomList">

    </div>
    <br>
    <div>
        <input type="button" id="joinRoomButton" value="방입장하기" />
        <input type="button" id="leaveRoomButton" value="방나가기" />
    </div>
    <br>
    <div>
        <div>
            <span>보내는사람 아이디 :</span> 
            <input type="text" id="senderInput" value="test01" />
        </div>
        <div>
            <span>받는사람 아이디 :</span>
            <input type="text" id="recepientInput" value="ALL" />
        </div>

        <!-- command 선택 <select> 채팅, 그룹채팅 -->
        <select name="chattype" id="chattype">
            <option value="chat">채팅</option>
            <option value="groupchat" selected>그룹채팅</option>
        </select>

        <div>
            <span>메시지 데이터 :</span>
            <input type="text" id="dataInput" value="안녕!"/>
        </div>
        <br>
        <input type="button" id="sendButton" value="전송" />
    </div>    

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>

일대일 채팅에서는 io.sockets.connected객체에 들어 있는 소켓 객체들 중에서 대상이 되는 소켓 객체를 소켓 ID로 찾은 후 emit()메소드를 호출하여 메시지를 전송했다. 이와 다르게 그룹 채팅에서는 io.sockets.in()메소드를 사용해 대상이 되는 방에 들어 있는 소켓 객체들을 찾은 후 메시지를 전송한다.

// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');

// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');

// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');

// Session 미들웨어 불러오기
var expressSession = require('express-session');


//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');


// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');

// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');

// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');



// Socket.IO 사용
var socketio = require('socket.io');

// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');



// 익스프레스 객체 생성
var app = express();


//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');


//===== 서버 변수 설정 및 static으로 public 폴더 설정  =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);


// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))

// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())

// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));

// cookie-parser 설정
app.use(cookieParser());

// 세션 설정
app.use(expressSession({
  secret:'my key',
  resave:true,
  saveUninitialized:true
}));



//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());


//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());



//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);


// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);

// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);



//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
 static: {
   '404': './public/404.html'
 }
});

app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );


//===== 서버 시작 =====//

//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
  console.log('uncaughtException 발생함 : ' + err);
  console.log('서버 프로세스 종료하지 않고 유지함.');

  console.log(err.stack);
});

// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
    console.log("프로세스가 종료됩니다.");
    app.close();
});

app.on('close', function () {
  console.log("Express 서버 객체가 종료됩니다.");
  if (database.db) {
    database.db.close();
  }
});

// 시작된 서버 객체를 리턴받도록 합니다. 
var server = http.createServer(app).listen(app.get('port'), function(){
  console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));

  // 데이터베이스 초기화
  database.init(app, config);

});


//===== Socket.IO를 이용한 채팅 테스트 부분 =====//


// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};


// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');

// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
  console.log('connection info :', socket.request.connection._peername);

    // 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
    socket.remoteAddress = socket.request.connection._peername.address;
    socket.remotePort = socket.request.connection._peername.port;


    // 'login' 이벤트를 받았을 때의 처리
    socket.on('login', function(login) {
      console.log('login 이벤트를 받았습니다.');
      console.dir(login);

        // 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
        console.log('접속한 소켓의 ID : ' + socket.id);
        login_ids[login.id] = socket.id;
        socket.login_id = login.id;

        console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);

        // 응답 메시지 전송
        sendResponse(socket, 'login', '200', '로그인되었습니다.');
    });


    // 'message' 이벤트를 받았을 때의 처리
    socket.on('message', function(message) {
      console.log('message 이벤트를 받았습니다.');
      console.dir(message);

        if (message.recepient =='ALL') {
            // 나를 포함한 모든 클라이언트에게 메시지 전달
          console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
            io.sockets.emit('message', message);
        } else {
          // command 속성으로 일대일 채팅과 그룹채팅 구분
          if (message.command == 'chat') {
            // 일대일 채팅 대상에게 메시지 전달
            if (login_ids[message.recepient]) {
              io.sockets.connected[login_ids[message.recepient]].emit('message', message);

              // 응답 메시지 전송
                  sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
            } else {
              // 응답 메시지 전송
                  sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
            }
          } else if (message.command == 'groupchat') {
            // 방에 들어있는 모든 사용자에게 메시지 전달
            io.sockets.in(message.recepient).emit('message', message);

            // 응답 메시지 전송
              sendResponse(socket, 'message', '200', '방 [' + message.recepient + ']의 모든 사용자들에게 메시지를 전송했습니다.');
          }

        }
    });

    // room 이벤트를 받았을 때의 처리
    socket.on('room',function(room){
        console.log("room 이벤트를 받았습니다.");
        console.dir(room);

        if(room.command == 'create'){
            if(io.sockets.adapter.rooms[room.roomId]){//방이 이미 만들어져 있는경우
                console.log("방이 이미 만들어져 있습니다.");
            }else{
                console.log("방을 새로 만듭니다.");

                socket.join(room.roomId);

                var curRoom = io.sockets.adapter.rooms[room.roomId];
                curRoom.id = room.roomId;
                curRoom.name = room.roomName;
                curRoom.owner = room.roomOwner;
            }
        }else if(room.command == 'update'){
            var curRoom = io.sockets.adapter.rooms[room.roomId];
                curRoom.id = room.roomId;
                curRoom.name = room.roomName;
                curRoom.owner = room.roomOwner;
        }else if (room.command == 'delete') {
            socket.leave(room.roomId);

            if(io.sockets.adapter.rooms[room.roomId]){//방이 만들어져 있는 경우
                delete io.sockets.adapter.rooms[room.roomId];
            }else{
                console.log("방이 만들어져 있지 않습니다.");
            }
        }else if(room.command == 'join'){
        socket.join(room.roomId);

        sendResponse(socket,'room','200','방에 입장했습니다.');
      }else if(room.command == 'leave'){
        socket.leave(room.roomId);

        sendResponse(socket,'room','200','방에서 나갔습니다.');
      }

        var roomList = getRoomList();

        var output = {command : 'list',rooms: roomList};
        console.log("클라이언트로 보낼 데이터 : "+JSON.stringify(output));

        io.sockets.emit('room',output);
    });

});

function getRoomList() {
    console.dir(io.sockets.adapter.rooms);

    var roomList = [];

    Object.keys(io.sockets.adapter.rooms).forEach(function(roomId) { // for each room
        console.log('current room id : ' + roomId);
        var outRoom = io.sockets.adapter.rooms[roomId];

        // find default room using all attributes
        var foundDefault = false;
        var index = 0;
        Object.keys(outRoom.sockets).forEach(function(key) {
            console.log('#' + index + ' : ' + key + ', ' + outRoom.sockets[key]);

            if (roomId == key) {  // default room
                foundDefault = true;
                console.log('this is default room.');
            }
            index++;
        });

        if (!foundDefault) {
            roomList.push(outRoom);
        }
    });

    console.log('[ROOM LIST]');
    console.dir(roomList);

    return roomList;
}

// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
  var statusObj = {command: command, code: code, message: message};
  socket.emit('response', statusObj);
}

10-4 채팅 웹 문서 예쁘게 꾸미기

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">

    <title>일대일 채팅</title>

    <link href="./semantic.min.css" rel="stylesheet" >

    <style>
        * {
            padding:0;
            margin:0;
            box-sizing:border-box;
        }

        html {
            width:100%;
            height:100%;
        }

        body {
            width:100%;
            height:100%;
            color: #000;
            background-color: #fff;
        }

        .container {
            width:100%;
            height:100%;
            display:flex;
            flex-flow:column wrap;
            align-items:center;
            justify-content:center;
        }

        #cardbox {
            width:94%;
            height:94%;
            padding-left:0.4em;
            padding-right:0.4em;
        }

        #iconImage {
            display:inline;
        }

        #titleText {
            font-size:1.4em;
            font-weight:bold;
            color:#777;
        }

        #contentsText {
            color:#999;
        }

        #result {
            height:10em;
            overflow:auto;
        }

    </style>

    <script src="jquery-3.1.1.min.js"></script>    
    <script src="socket.io.js"></script>
    <script src="semantic.min.js"></script>

    <script>
        var host;
        var port;
        var socket;

        // 문서 로딩 후 실행됨
        $(function() {

            // 연결하기 버튼 클릭 처리
            $("#connectButton").bind('click', function(event) {
                println('connectButton이 클릭되었습니다.');

                   host = $('#hostInput').val();
                   port = $('#portInput').val();

                   connectToServer();
            });

            // 전송 버튼 클릭 시 처리
            $("#sendButton").bind('click', function(event) {
                var sender = $('#senderInput').val();
                var recepient = $('#recepientInput').val();
                var data = $('#dataInput').val();

                var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                if (socket == undefined) {
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                }

                socket.emit('message', output);
            });

            // 로그인 버튼 클릭 시 처리
            $("#loginButton").bind('click', function(event) {
                var id = $('#idInput').val();
                var password = $('#passwordInput').val();
                var alias = $('#aliasInput').val();
                var today = $('#todayInput').val();

                var output = {id:id, password:password, alias:alias, today:today};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                if (socket == undefined) {
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                }

                socket.emit('login', output);
            });

            // 결과 지우기 버튼 클릭 시 처리
            $("#clearButton").bind('click', function(event) {
                $("#result").html('');
            });

        });

        // 서버에 연결하는 함수 정의
        function connectToServer() {

            var options = {'forceNew':true};
            var url = 'http://' + host + ':' + port;
            socket = io.connect(url, options);

            socket.on('connect', function() {
                println('웹소켓 서버에 연결되었습니다. : ' + url);

                socket.on('message', function(message) {
                    console.log(JSON.stringify(message));

                    println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
                });

                socket.on('response', function(response) {
                    console.log(JSON.stringify(response));
                    println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                });

            });

            socket.on('disconnect', function() {
                println('웹소켓 연결이 종료되었습니다.');
            });

        }

        function println(data) {
            console.log(data);
            $('#result').append('<p>' + data + '</p>');
        }
    </script>
</head>
<body>

    <div class="container">

        <div id="cardbox" class="ui blue fluid card">
             <div class="content">
                <div class="left floated author">
                    <img id="iconImage" class="ui avatar image" src="./images/author.png">
                </div>
                <div>
                    <div id="titleText" class="header">일대일 채팅</div>
                    <div id="contentsText" class="description">
                        연결 및 로그인 후 메시지를 보내세요.
                    </div>
                </div>
             </div>

            <br>
            <!-- 연결하기 -->
            <div>
                <div class="ui input">
                    <input type="text" id="hostInput" value="localhost" />
                </div>
                <div class="ui input">
                    <input type="text" id="portInput" value="3000" />
                </div>
                <br><br>
                <input class="ui primary button" type="button" id="connectButton" value="연결하기" />
            </div>
            <br>
            <!-- 로그인/로그아웃 -->
            <div>
                <div class="ui input">
                    <input type="text" id="idInput" value="test01" />
                </div>
                <div class="ui input">
                    <input type="password" id="passwordInput" value="123456" />
                </div>
                <div class="ui input">
                    <input type="text" id="aliasInput" value="소녀시대" />
                </div>
                <div class="ui input">
                    <input type="text" id="todayInput" value="좋은 날!" />
                </div>
                <br><br>
                <input class="ui primary button" type="button" id="loginButton" value="로그인" />
                <input class="ui primary button" type="button" id="logoutButton" value="로그아웃" />
            </div>
            <br>
            <!-- 전송하기 -->
            <div>
                <div class="description">
                    <span>보내는사람 아이디 :</span> 
                    <div class="ui input">
                        <input type="text" id="senderInput" value="test01" />
                    </div>
                </div>
                <div class="description">
                    <span>받는사람 아이디 :</span> 
                    <div class="ui input">
                        <input type="text" id="recepientInput" value="ALL" />
                    </div>
                </div>
                <div class="description">
                    <span>메시지 데이터 :</span> 
                    <div class="ui input">
                        <input type="text" id="dataInput" value="안녕!"/> 
                    </div>
                </div>
                <br>
                <input class="ui primary button" type="button" id="sendButton" value="전송" />
                <input class="ui primary button" type="button" id="clearButton" value="결과 지우기" />
            </div>    

            <br>

            <!-- 결과 표시 -->
            <h4 class="ui horizontal divider header">메시지</h4>
            <div class="ui segment" id="result">
            </div>

        </div>

    </div>

</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">

    <title>일대일 채팅</title>

    <link href="./semantic.min.css" rel="stylesheet" >

    <style>
        * {
            padding:0;
            margin:0;
            box-sizing:border-box;
        }

        html {
            width:100%;
            height:100%;
        }

        body {
            width:100%;
            height:100%;
            color: #000;
            background-color: #fff;
        }

        .container {
            width:100%;
            height:100%;
            display:flex;
            flex-flow:column wrap;
            align-items:center;
            justify-content:center;
        }

        #cardbox {
            width:94%;
            height:94%;
            padding-left:0.4em;
            padding-right:0.4em;
        }

        #iconImage {
            display:inline;
        }

        #titleText {
            font-size:1.4em;
            font-weight:bold;
            color:#777;
        }

        #contentsText {
            color:#999;
        }

        #result {
            height:14em;
            overflow:auto;
        }


        .discussion {
              list-style: none;
              background: #ededed;
              margin: 0;
              padding: 0 0 50px 0;
        }

        .discussion li {
              padding: 0.5em;
              overflow: hidden;
              display: flex;
        }

        .discussion .avatar {
              width: 40px;
              position: relative;
        }

        .discussion .avatar img {
              display: block;
              width: 100%;
        }

        .other .avatar:after {
              content: "";
              position: absolute;
              top: 0;
              right: 0;
              width: 0;
              height: 0;
              border: 5px solid white;
              border-left-color: transparent;
              border-bottom-color: transparent;
        }

        .self {
              justify-content: flex-end;
              align-items: flex-end;
        }

        .self .messages {
              order: 1;
              border-bottom-right-radius: 0;
        }

        .self .avatar {
              order: 2;
        }

        .self .avatar:after {
              content: "";
              position: absolute;
              bottom: 0;
              left: 0;
              width: 0;
              height: 0;
              border: 5px solid white;
              border-right-color: transparent;
              border-top-color: transparent;
              box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
        }

        .messages {
              background: white;
              padding: 10px;
              border-radius: 2px;
              box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
        }

        .messages p {
              font-size: 0.8em;
              margin: 0 0 0.2em 0;
        }

        .messages time {
              font-size: 0.7em;
              color: #ccc;
        }


    </style>

    <script src="jquery-3.1.1.min.js"></script>     
    <script src="socket.io.js"></script>
    <script src="semantic.min.js"></script>

    <script>
        var host;
        var port;
        var socket;

        // 문서 로딩 후 실행됨
        $(function() {

            // 연결하기 버튼 클릭 처리
            $("#connectButton").bind('click', function(event) {
                println('connectButton이 클릭되었습니다.');

                   host = $('#hostInput').val();
                   port = $('#portInput').val();

                   connectToServer();
            });

            // 전송 버튼 클릭 시 처리
            $("#sendButton").bind('click', function(event) {
                var sender = $('#senderInput').val();
                var recepient = $('#recepientInput').val();
                var data = $('#dataInput').val();

                  var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                   if (socket == undefined) {
                     alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                       return;
                 }

                   socket.emit('message', output);

                   addToDiscussion('self', data);
              });

            // 로그인 버튼 클릭 시 처리
            $("#loginButton").bind('click', function(event) {
                var id = $('#idInput').val();
                var password = $('#passwordInput').val();
                var alias = $('#aliasInput').val();
                var today = $('#todayInput').val();

                var output = {id:id, password:password, alias:alias, today:today};
                   console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                   if (socket == undefined) {
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                   }

                socket.emit('login', output);
            });

             // 결과 지우기 버튼 클릭 시 처리
            $("#clearButton").bind('click', function(event) {
                $("#result").html('');
            });

        });

        // 서버에 연결하는 함수 정의
        function connectToServer() {

            var options = {'forceNew':true};
            var url = 'http://' + host + ':' + port;
            socket = io.connect(url, options);

            socket.on('connect', function() {
                   println('웹소켓 서버에 연결되었습니다. : ' + url);

                socket.on('message', function(message) {
                    console.log(JSON.stringify(message));

                    println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');

                    addToDiscussion('other', message.data);
                });

                socket.on('response', function(response) {
                    console.log(JSON.stringify(response));
                    println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                });

            });

            socket.on('disconnect', function() {
                println('웹소켓 연결이 종료되었습니다.');
            });

           }

        function println(data) {
            console.log(data);
            //$('#result').append('<p>' + data + '</p>');
        }

        function addToDiscussion(writer, msg) {
            println("addToDiscussion 호출됨 : " + writer + ", " + msg);

            var img = '/public/images/user1.png';
            if (writer == 'other') {
                img = '/public/images/user2.png';
            }

            var contents = "<li class='" + writer + "'>"
                         + "  <div class='avatar'>"
                         + "    <img src='" + img + "' />"
                           + "  </div>"
                           + "  <div class='messages'>"
                         + "    <p>" + msg + "</p>"
                         + "    <time datetime='2016-02-10 18:30'>18시 30분</time>"
                           + "  </div>"
                         + "</li>";

            println("추가할 HTML : " + contents);
            $(".discussion").prepend(contents);
        }

    </script>
</head>
<body>


    <div class="container">

        <div id="cardbox" class="ui blue fluid card">
             <div class="content">
                 <div class="left floated author">
                     <img id="iconImage" class="ui avatar image" src="./images/author.png">
                </div>
                <div>
                    <div id="titleText" class="header">일대일 채팅</div>
                       <div id="contentsText" class="description">
                           연결 및 로그인 후 메시지를 보내세요.
                    </div>
                </div>
             </div>

            <br>
            <!-- 연결하기 -->
            <div>
                <div class="ui input">
                    <input type="text" id="hostInput" value="localhost" />
                </div>
                <div class="ui input">
                    <input type="text" id="portInput" value="3000" />
                </div>
                <br><br>
                <input class="ui primary button" type="button" id="connectButton" value="연결하기" />
            </div>
            <br>
            <!-- 로그인/로그아웃 -->
            <div>
                <div class="ui input">
                    <input type="text" id="idInput" value="test01" />
                </div>
                <div class="ui input">
                    <input type="password" id="passwordInput" value="123456" />
                </div>
                <div class="ui input">
                    <input type="text" id="aliasInput" value="소녀시대" />
                </div>
                <div class="ui input">
                    <input type="text" id="todayInput" value="좋은 날!" />
                </div>
                <br><br>
                <input class="ui primary button" type="button" id="loginButton" value="로그인" />
                <input class="ui primary button" type="button" id="logoutButton" value="로그아웃" />
            </div>
            <br>
            <!-- 전송하기 -->
            <div>
                <div class="description">
                    <span>보내는사람 아이디 :</span> 
                    <div class="ui input">
                        <input type="text" id="senderInput" value="test01" />
                    </div>
                </div>
                <div class="description">
                    <span>받는사람 아이디 :</span> 
                    <div class="ui input">
                        <input type="text" id="recepientInput" value="ALL" />
                    </div>
                </div>
                <div class="description">
                    <span>메시지 데이터 :</span> 
                    <div class="ui input">
                        <input type="text" id="dataInput" value="안녕!"/> 
                    </div>
                </div>
                <br>
                <input class="ui primary button" type="button" id="sendButton" value="전송" />
                <input class="ui primary button" type="button" id="clearButton" value="결과 지우기" />
            </div>    

            <br>

            <!-- 결과 표시 -->
            <h4 class="ui horizontal divider header">메시지</h4>
            <div class="ui segment" id="result">

              <ol class="discussion">
                <li class="other">
                  <div class="avatar">
                    <img src="/public/images/user2.png" />
                  </div>
                  <div class="messages">
                    <p>어디쯤이야? 다들 기다리고 있어.</p>
                    <time datetime="2016-02-10 18:10">18시 10분</time>
                  </div>
                </li>
                <li class="self">
                  <div class="avatar">
                    <img src="/public/images/user1.png" />
                  </div>
                  <div class="messages">
                    <p>차가 막히네. 조금 늦을 듯.</p>
                    <time datetime="2016-02-10 18:00">18시 00분</time>
                  </div>
                </li>
                <li class="other">
                  <div class="avatar">
                    <img src="/public/images/user2.png" />
                  </div>
                  <div class="messages">
                    <p>강남역에 있는 카페에 자리 잡았어.</p>
                    <time datetime="2016-02-10 17:40">17시 40분</time>
                  </div>
                </li>
              </ol>

            </div>

        </div>

    </div>

</body>
</html>

채팅 메시지 표시를 위해 만든 CSS는 https://css-tricks.com/replicating-google-hangouts-chat/참조

Last updated