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