C/S 개발을 처음 시작한지가 언제쯤일까요? Client 는 처음 개발을 시작 할 때 부터 작성하지만 Server 쪽은 경륜이 좀 있어야 제대로 개발을 할 수 있습니다.
주로 웹서버, 메일서버, 기타 간단한 자체 프로토콜 서버 등을 개발했었는데 한 15년 정도 전문적인 개발 업무를 중단하고 조금씩 취미로 진행하지 않다 보니 최근에는 힘에 많이 부치는 것 같습니다.
1인 프로젝트라는 말을 많이 하죠. 특히 개발 쪽에서 1인 프로젝트를 완성도있게 진행 하려면 많은 지식이 필요합니다.
홈페이지 만드는데 홈페이지 만드는 방법만 알면되지, 웹서버를 직접 만들 필요가 있을까요?
당연히 만들면 좋죠. 아주 잘 만들면 더할 나위가 없죠.
하지만 수십명의 서버 전문 프로그래머가 몇 년을 작업해도 제대로 된 웹 서버를 개발하기 어려운데 혼자서 그것을 어떻게 만들겠어요 라고 생각하면 ... 글쎄요... 라고... 대답을 ...
자체 개발 웹 서버로 홈페이지를 만들어서 서비스한다는 것이 얼마나 자랑스러운 일이겠습니까. 장점도 많죠. 세상에 하나뿐인 서버이고 소스 공개가 되지 않아서 외부로 부터의 침입을 편리하게 차단 할 수 있죠. 각종 커스터마이징을 통해서 손 쉽게 원하는데로 요리를 할 수 있습니다.
그래서 ... 웹서버를 만든 것이 아닙니다..... ??
약간의 보조 기능이 필요한데, 그리고 그러한 기능은 아주 잘 만들어진 세계 최고의 안전과 성능이 보장된 무료 오픈 소프트웨어가 있는데도 불구하고 그냥... 사용하기 귀찮아서 만들버렸습니다.
이름하여 XS ( Center X Server 의 약자 ) 입니다.
XS 서버는 Node 로 만들어져 있으면 아주 간결하게 코드가 짜여져 있습니다.
단순함이 최고의 소프트웨어라고 생각을 합니다.
XS 는 몇 줄 안되는 소스코드로 이루어져 있지만 웹서버, 채팅서버, API 서버 3 가지 기능을 포함하고 있습니다.
다음은 XS 실행하는 장면입니다.
다음 XS 의 소스코드입니다.
---------------- 소스 코드 -----------------
///
docs.google.com/a/withcenter.com/document/d/1joigYA_4ZPB_I_IBkrNlS7sIKZCxEp7tWyd5ayzHJYA/edit#var port = 8083;
http = require("http").createServer(callback_http_request).listen(port),
path = require("path"),
url = require("url"),
fs = require("fs"),
io = require('socket.io').listen(http),
util = require('util'),
mime = require('mime');
console.log("Server Running on "+port);
io.set('log level', 2);
io.sockets.on('connection', on_connect);
function callback_http_request(request,response) {
var pathname = url.parse(request.url).pathname;
/**
abc.com 또는
abc.com/dir/ 와 같이 접속하는 경우 해당 경로의 index.html 이 로드되도록 한다.
*/
var p = //$/;
if ( pathname == '/' ) pathname = '/index.html';
else if ( p.test(pathname) ) {
pathname = pathname + 'index.html';
}
var full_path = path.join(process.cwd() + '/www' ,pathname);
console.log("pathname: " + pathname + " [ full_path : " + full_path + " ]");
path.exists(full_path,function(exists){
if(!exists){
response.writeHeader(404, {"Content-Type": "text/plain"});
response.write("404 Not Foundn");
response.end();
}
else{
var mimetype = mime.lookup(full_path);
fs.readFile(full_path, "binary", function(err, file) {
if(err) {
response.writeHeader(500, {"Content-Type": "text/plain"});
response.write(err + "n");
response.end();
}
else{
response.writeHeader(200, {"Content-Type": mimetype});
response.write(file, "binary");
response.end();
}
});
}
});
}
function on_connect(socket)
{
console.log('on_connect:'+socket);
//socket.on('chat name', function(data){chat.name(socket,data);});
//socket.on('chat message', function(data){chat.message(socket,data);});
socket.on('chat', function(data) {
chat(socket, data);
});
socket.on('api', function(data) {
api(socket, data);
});
socket.on('anything', function(data) {
io.sockets.emit('anything', { 'code' : -1001 } );
});
}
function api(socket, data)
{
console.log("api: post_id="+data.post_id);
var http = require('http');
var options = {
host: 'philgi.org',
port: 80,
path: '/?module=post&action=list_json&theme=N&post_id=' + data.post_id
};
callback = function(response) {
console.log('callback:');
var str = ''
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
console.log(str);
socket.emit( 'api-forum-list', { 'list': str } );
});
}
var req = http.get(options, callback);
}
/*********************************************************************
*
* Chatting Protocol...
*
*/
var chat_info = {};
var chat_socket_id = {};
function chat(socket, data)
{
console.log( "from client: " + util.inspect(data) );
var client = get_chat_info(socket, data);
if ( ! client ) {
return;
}
/** 처음 접속인가?
* 주의 : 재 접속 또는 페이지 이동을 할 때에도 이 함수가 호출 되어야 한다.
* 만약, 재 접속이라면 서버에 아이디가 기록되어져 있다. 이전에 접속이 되어 정보가 있는 상태라면,
* 즉, (대기실이라도) 방이 있는 상태라면 방으로 입장을 한다.
*/
if ( data.action == 'enter' ) {
console.log("Emitting to '"+client.room+"' : enter" );
/** 처음 입장하면 lobby 가 되고 원래 자기 방이 있으면 해당 방으로 브로드 캐스팅 한다.
*
*/
chat_room_enter( socket, client.id, client.room ); /// enter 할 때마다 방 입장을 한다. 동일한 방이라도 계속 입장한다.
data.room = client.room; // 입장한 사용자의 방 이름을 전달. 대기실이면 lobby
io.sockets.in(client.room).emit('chat', data);
}
else if ( data.action == 'message' ) {
//io.sockets.emit('chat', data);
console.log("Emitting to '"+client.room+"' : " + data.value);
data.name = client.name;
io.sockets.in(client.room).emit('chat', data);
}
else if ( data.action == 'join' ) {
console.log(client.id + ' leaves the room : ' + client.room);
socket.leave(client.room);
io.sockets.in(client.room).emit('chat', {action: 'leave', id: client.id, name: client.name, 'to': data.value} ); /* 퇴장 브로드 캐스팅 */
chat_room_enter( socket, client.id, data.value );
io.sockets.in(data.value).emit('chat', {action: 'join', id: client.id, name:client.name, value:data.value} ); /* 입장 브로드 캐스팅 */
}
else if ( data.action == 'room list' ) {
console.log("room list:");
var connected = new Array();
var entered = new Array();
var users = io.sockets.manager.rooms[''];
for ( var u in users ) {
var sid = users[u];
var id = chat_socket_id[sid];
var c = chat_info[id];
if ( c ) {
console.log("FOUND socket id:" + sid + " User ID:" + c.id);
entered.push(c);
}
else {
connected.push(sid);
}
}
console.log( util.inspect( io.sockets.manager.rooms[''] ) );
console.log("CONNECTED but not entered:");
console.log( util.inspect( connected) );
console.log("CONNECTED and entered:");
console.log( util.inspect( entered) );
socket.emit('chat', { action: 'room list', 'connected': JSON.stringify(connected), 'entered': JSON.stringify(entered)});
}
}
/** 모든 액션에서 이 함수를 통해서 사용자 정보를 얻는다.
* 즉, 어떤 액션이든 이름을 변경 할 수 있다.
*/
function get_chat_info(socket, data)
{
if ( typeof data.id == 'undefined' ) {
console.log("ERROR: WRONG PROTOCOL");
return {};
}
var id = data.id;
var sid = socket.id;
console.log("get_chat_info("+id+")");
//console.log(util.inspect(chat_info));
if ( chat_info[id] ) {
console.log("chat info exists");
chat_info[id].socket_id = sid; /// 재 접속할 때마다 socket.id 가 변경 될 수 있으므로 반드시 업데이트를 해야 한다.
}
else {
console.log("chat info does not exists");
chat_info[id] = {};
chat_info[id].id = data.id;
chat_info[id].socket_id = sid;
chat_info[id].name = data.name;
chat_info[id].room = 'lobby';
/** 처음 접속시 대기실로 자동 입장 */
chat_room_enter( socket, id, chat_info[id].room );
}
/// enter 할 때에는 사용자 이름을 업데이트 한다.
if ( data.action == 'enter' ) {
chat_info[id].name = data.name;
}
console.log( util.inspect( chat_info[id] ) );
chat_socket_id[sid] = id;
return chat_info[id];
}
function chat_room_enter( socket, id, room )
{
socket.join(room);
chat_info[id].room = room;
console.log(id + ' joins ' + room);
}
@알림 : 코멘트를 작성하시려면 로그인을 하십시오.