Socket.IO를 이용한 채팅 어플리케이션 구현 (1) – 개발환경 설정

저는 Node와 패키지들을 학습하면서 간단한 채팅 어플리케이션을 개발하기러 했고,
1차적으로 완성 됨에 따라 자세한 구현과정과 소스를 공개하도록 하였습니다.

Socket.IO 어플리케이션 개괄

기존 HTML은 “Client의 요청-Server의 응답” 형태로, 단방향성이라는 특징이 있었습니다. 그동안 Client의 비동기적으로 일어나는 요청에 대해 Server와 통신하는 기술이 발전되어 왔었으나, 이 것이 모든 브라우저에서 가동되는 것은 한계가 있었습니다.
Socket.IO는 기존에 있던 다양한 통신 기술 (AJAX, Flex의 Socket 등)들을 통합하여 단일 API로 만든 기술입니다.
“다양한 통신 기술”이라고 추상화하여 표현 한 것은 웹개발자에게 많은 의미가 있습니다. Socket.IO를 사용하면 더 이상 프로그래머가 HTML에서 발생하는 비동기 통신의 구현에 대해 스트레스를 받지 않아도 된다는 것을 의미합니다. 왜냐하면 Socket.IO는 이런 기술들을 통해 소켓 통신 기능을 크로스 브라우징화 하였기 때문입니다.

Desktop
Internet Explorer 5.5+ / Safari 3+

Google Chrome 4+ / Firefox 3+ / Opera 10.61+
Mobile
iPhone Safari / iPad Safari

Android WebKit / WebOs WebKit

Node.js의 강력한 패키지인 Socket.IO와 그 밖에 다양한 패키지들의 도움을 받으면
채팅 서버/클라이언트 정도는 TCP/IP 소켓 프로그래밍에 비해 간단하게 구현이 가능합니다.

관련기술

채팅 어플리케이션 개발을 위해 Node.js를 주요기술로 사용하였습니다.

관련 패키지에 대한 설명으로
Socket.IO는 소켓 구현을 담당하고,
Express는 일반적인 웹 서버 기능 구현과 View의 독립을 담당하고,
Jade는 Express에서 사용하는 기본 템플릿 엔진이며,
Mongoose는 MongoDB와의 연동을 하는 프레임워크이고,
Forever는 Node 인스턴스가 꺼지지 않고 계속 구동되는 것을 보장합니다.

채팅 기록을 남기기 위해 MongoDB를 사용하였으며,
기본 UI Set으로 Twitter Bootstrap을 활용하였습니다.
IDE는 Eclipse에서 Aptana Plugin을 설치하여 ftp에서 작업하였습니다.

개발환경 설정

이 포스트는 MongoDB의 설치를 포함하고 있지 않습니다. 일반적으로 yum을 통해 인스톨하시면 됩니다. Node.js의 설치에 대해서는 이미 설명을 한 포스트가 있으므로 NPM 세팅부터 진행하도록 하겠습니다.

먼저 Express의 설치는 다른 것과 약간 달라서 다음 포스트를 따라서 하시기 바랍니다.

Express의 전역 설치가 끝나면 다음 명령을 통해 디렉토리를 생성하고 Express 기본 프로젝트를 생성합니다.

mkdir 디렉토리명
cd 디렉토리명
express 
npm install


Express 세팅이 끝나면 기본적인 어플리케이션의 틀이 갖추어지게 됩니다.

 

다음 명령을 통해 나머지 패키지들을 다운받습니다.

npm install socket.io mongoose forever

여기까지하면 어플리케이션 개발을 위한 기본 설정이 끝나게 됩니다.
부가적으로 저는 UI를 꾸미고 싶어서 Twitter Bootstrap을 활용하였습니다. 자기만의 인터페이스를 입하고 싶으신 분은 다른 방법을 활용하셔도 좋습니다.

Twitter Bootstrap을 사용하기 위해서 다음 명령을 입력합니다.

w get twitter.github.com/bootstrap/assets/bootstrap.zip
unzip bootstrap.zip
mv bootstrap public

w get으로 다운 받은 것을 풀게되면 CSS, Icon 이미지들, jQuery 플러그인이 생기게 됩니다. 이 파일들은 static파일들이므로 압축이 풀리면서 생성된 bootstrap폴더를 public (Apache의 public_html 정도에 해당)폴더로 이동시킵니다.

이제 개발할 준비가 되었습니다!

app.js

app.js 파일은 본 어플리케이션의 시작점이 될 중요한 파일입니다.
이 파일에서 소켓 통신과 MongoDB와의 연동을 담당할 것입니다.

처음 app.js파일은 다음과 같이 구성되어 있습니다.

/**
* Module dependencies.
*/
 
var express = require('express')
  , routes = require('./routes');
 
var app = module.exports = express.createServer();
 
  // Configuration
app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});
 
app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
 
app.configure('production', function(){
  app.use(express.errorHandler());
});
 
// Routes
 
app.get('/', routes.index);
 
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

처음의 require 행은 일반적인 언어들의 include 문에 해당하며, 해당하는 이름의 js 확장자를 가지는 모듈을 불러와서 변수로 할당하는 역할을 해줍니다.

이후 app 이라는 변수에 기본 웹서버를 할당하고 configure를 통해 설정을 하고 있습니다.
이 설정문에서 핵심적인 사항들을 알 수 있습니다.
view 파일은 /views 폴더에 위치하고 있으니 render시 그 곳에 있는 파일을 참조하라는 것과, 템플릿 엔진으로 jade를 사용하겠다는 것, static 파일은 /public 폴더에 있으니 동적 파일이 아닌 것 중 public 폴더 내에 있는 것이 있다면 그대로 보내주라는 내용이 포함되어있습니다.

아래의 development와 production은 환경변수에 따라 해당 내용을 적용하라는 것입니다. 처음엔 NODE_ENV=development로 설정되어 있기 때문에 오류 발생시 덤프메시지가 출력됩니다.

Route문은 Express에서 가장 핵심적인 부분입니다.
” / ” (public root)에 접근시 routes.index 함수를 실행하라는 뜻이며, routes의 내용은 상단에서 require한 것처럼 routes.js에 존재하며 살펴보면 index라는 함수가 템플릿 엔진에게 index.jade파일의 렌더링을 지시한다는 것을 알 수 있습니다.

결과적으로 ” / ” 에 접근한 사용자는 index.jade파일을 렌더링 한 결과를 Response(응답)으로 받게 됩니다.

마지막으로 3000번 포트에서 클라이언트의 요청(Request)를 대기하게 됩니다.

socket.io

var socketio=require('socket.io');

socket.io를 사용하기 위해 require문으로 소스를 변수화 합니다.

var app = express.createServer();
var io = socketio.listen(app);

기존의 Express 선언문 밑에 Socket.io의 listen문을 추가하고, 인자로 Express의 웹서버를 넘겨줌으로써 Express와 포트를 공유하게 합니다.
이렇게 지정하면 Websocket과 HTTP Server의 포트가 단일화 되어 단순한 어플리케이션을 구현하는데 있어서 편리함을 가져다 줍니다.
인자로 app 대신에 포트번호를 기재하면 그 포트로 열리게 됩니다.

여기에서 주의할 점은 변수 “socketio”와 “io”는 다르다는 점입니다. 종종 예제를 보면 편의상 합쳐서 다음과 같이 표기하는 경우도 있으나,

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

이 경우 listen 함수에 대한 return value가 io에 저장되기 때문에, 추후에 Socket.io 라이브러리에 접근할 방법은 없어지게 됩니다. 그래서 나누어 둔 것이며 socketio 변수는 Reference를 가지고 있으므로 언제든지 필요할 때 사용할 수 있게 됩니다.

io.sockets.on('connection', function(socket) {
  socket.on('write', function (data) {
    data.datetime=new Date();
 
    io.sockets.emit('message', data);
  });
});

실질적인 소켓을 이용한 전송은 위의 코드로 이루어 집니다.
구체적인 설명은 다음 포스팅에서 진행하도록 하겠습니다.

여기에서 잠깐 중요한 이야기를 해야 할 것 같습니다.
Socket.IO 뿐만 아니라 여러 Node.js 패키지의 근간은 이벤트로 구성되어 있습니다.
이러한 개발 패러다임은 기존의 PHP, ASP, JSP에서 Server-side 개발을 진행하던 것과는  근본적인 차이가 있습니다.

이벤트에 대한 다음 포스팅을 참조해 주십시오.