오늘은 잠시 node.js 프로그램과 관련된 강좌를 올립니다. 


잠시 쉬어가는 페이지겠죠?


Node.js 는 자바스크립트언어 기반 플랫폼이죠.


최근에 저도 즐겨 사용하고 있습니다. 


그런데 node.js 를 웹 서버용이 아닌 TCP 통신용 프로그램으로 만들어야 하는 경우가 


임베디드 프로그램을 작성하다 보면 종종 발생합니다. 


이럴 경우 프로토콜을 json 으로 작성하는 것이 편합니다.


저는 제어용으로는 udp + json 형태를 선호하지만


큰 데이터나 제어를 단순하게 하고 싶으면 tcp + json 형태를 선호 합니다. 


이런 경우 node.js 에서 제공하는 tcp api 를 사용하는 것 보다


json-over-tcp  라는 모듈을 사용하는 것이 좋습니다. 


이것을 약어로  jot 라고 하는데 흠.. 한국 발음으로 하면 조금 거시기 합니다. ^^


그리고 인터넷에서 검색 하는 경우에도 jot 로 찾으면 엉뚱한 것이 먼저 걸리죠.


어찌되었든..


오늘의 강좌를 이것을 쓰는 방법을 알려 드리려 합니다. 


우선 모듈은 여기서 구할 수 있습니다.


https://www.npmjs.org/package/json-over-tcp


여기를 찾아 가면 소개부터 간단한 사용법까지 알 수 있습니다. 


자..


오늘의 목표는 이런 겁니다. 


클라이언트도  node.js 모듈이고 

서버도 node.js 모듈입니다. 

서버는 조금 큰 jpg 그림 파일을 가지고 있고 

클라이언트는 서버에게 특정 파일 이름을 전달합니다. 

그러면 서버는 요청을 보고 해당 파일을 읽어서 클라이언트로 전달 합니다. 

클라이언트는 이 수신된 파일데이터를 파일에 저장하는 겁니다. 


이 과정을 하나씩 단계별로 진행할 겁니다. 


조금 깁니다. ^^


다음과 같은 프로그램 실험을 위한 작업 폴더를 만듭니다. 

/home/frog/jot/

여기에 다음과 같이 jot 모듈을 설치 합니다.


$ npm install json-over-tcp

혹시 설치에 실패하면 sudo 로 실행해 보세요..


이제 실험해 봐야 겠죠?


하지만 먼저 기초부터 탄탄히 


json-over-tcp 는 net 모듈에 기반하고 있습니다. 

그래서 먼저 net 모듈을 이용한 tcp 서버와 클라이언트를 구현해 보죠..

이제 간단하게 서로 통신이 되는지 다음과 같이 서버용과 클라이언트용을 만듭니다.


이 모듈의 사용법은 기존 tcp 를 위한 net 모듈에 있는 api 와 사용법이 비슷합니다.



[C023_server_basic.js]------------------------------------------------------

var net = require('net');

 

var PORT = 34271;

var msg = 'will you be okay?';

 

var server = net.createServer( function ( client_socket ) { 


console.log('client connected >> '  + client_socket.remoteAddress +':'+ client_socket.remotePort);

client_socket.setNoDelay(true);

// client_socket.setTimeout( 4000 );

client_socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

});


client_socket.on('data', function (data) {

console.log('client received data >> '  + client_socket._peername.address +':'+ client_socket._peername.port + ' [' + data + ']' );

client_socket.write(msg);

});


client_socket.on('end', function () {

console.log('client received end >> '  + client_socket._peername.address +':'+ client_socket._peername.port );

});

client_socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

client_socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


client_socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

});

 

server.listen(PORT, function() {

address = server.address();

console.log("server listening on %j", PORT, address);

});

--------------------------------------------------------------------------------

[C023_client_basic.js]------------------------------------------------------

var net = require('net'); 


var HOST = '127.0.0.1';

var PORT = 34271;


var socket  = new net.Socket();

var msg = 'Hey! TCP server.';


socket.setNoDelay(true);


socket.connect(PORT, HOST, function() {


console.log('client connected to : ' + HOST + ':' + PORT );

// socket.setTimeout( 1000 );

socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

})

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

console.log('client received data >> ' + ' [' + data + ']' );

})


socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


socket.on('end', function () {

console.log('client received end >> ');

});

socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

var write_ok = socket.write(msg);

console.log( 'client send data [%s] and end of write = %s', msg, write_ok );

socket.end();

});

--------------------------------------------------------------------------------


매우 간단한 구조의 소스인데 


원래는 위에 처럼 이벤트를 모두 구현할 필요는 없습니다. 


하지만 제 경험상 처음 하시는 분들은 


이런식의 기본 구조위에 문제가 될때 마다 소스를 수정해 가시는 것이 좋습니다. 


왜냐하면 tcp 통신 프로그램이 간단해 보이지만


실제 여러가지 상황과 응용 방법에 따라서 다양한 문제가 발생하는데


그때 위의 이벤트들의 발생을 참고 하여 진행하는 것이 좋기 때문입니다. 


위 프로그램은 각각 두개의 텔넷을 열고 따로 따로 실행하면 다음과 같은 결과 하면이 나오게 됩니다. 

서버측 :

$ node server_basic.js

server listening on 34271 { address: '0.0.0.0', family: 'IPv4', port: 34271 }

client connected >> 127.0.0.1:60232

client received data >> 127.0.0.1:60232 [Hey! TCP server.]

client received end >> 127.0.0.1:60232

client closed >>



클라이언트 측 : 


$ node client_basic.js

client connected to : 127.0.0.1:34271

client send data [Hey! TCP server.] and end of write = true

client received data >>  [will you be okay?]

client received end >>

client closed >>


자 이제 두번째 json-over-tcp 모듈에 기반하고 위와 동일한 처리를 하는 것을 만들어 봅시다. 


단순히 net 모듈만 jot 모듈로 바꾸어 보는 것이죠.



[C024_server_basic_jot.js]------------------------------------------------------

var jot = require('json-over-tcp');

 

var PORT = 34271;

var msg = 'will you be okay?';

 

var server = jot.createServer( function ( client_socket ) { 

console.log('client connected >> '  + client_socket.remoteAddress +':'+ client_socket.remotePort);

client_socket.setNoDelay(true);

// client_socket.setTimeout( 4000 );

client_socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

});


client_socket.on('data', function (data) {

console.log('client received data >> '  + client_socket._peername.address +':'+ client_socket._peername.port + ' [' + data + ']' );

client_socket.write(msg);

});


client_socket.on('end', function () {

console.log('client received end >> '  + client_socket._peername.address +':'+ client_socket._peername.port );

});

client_socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

client_socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


client_socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

});

 

server.listen(PORT, function() {

address = server.address();

console.log("server listening on %j", PORT, address);

});

--------------------------------------------------------------------------------

[C024_client_basic_jot.js]------------------------------------------------------

var jot = require('json-over-tcp');


var HOST = '127.0.0.1';

var PORT = 34271;


var socket  = new jot.Socket();

var msg = 'Hey! TCP server.';


socket.setNoDelay(true);


socket.connect(PORT, HOST, function() {


console.log('client connected to : ' + HOST + ':' + PORT );

// socket.setTimeout( 1000 );

socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

})

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

console.log('client received data >> ' + ' [' + data + ']' );

})


socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


socket.on('end', function () {

console.log('client received end >> ');

});

socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

var write_ok = socket.write(msg);

console.log( 'client send data [%s] and end of write = %s', msg, write_ok );

socket.end();

});

--------------------------------------------------------------------------------


앞에 예제와 동일한 방식으로 실행해 보면 동일한 동작 상황을 볼 수 있습니다. 


서버측 :


$ node server_basic_jot.js

server listening on 34271 { address: '0.0.0.0', family: 'IPv4', port: 34271 }

client connected >> 127.0.0.1:60235

client received data >> 127.0.0.1:60235 [?"Hey! TCP server."]

client received end >> 127.0.0.1:60235

client closed >>


클라이언트 측 : 


$ node client_basic_jot.js

client connected to : 127.0.0.1:34271

client send data [Hey! TCP server.] and end of write = undefined

client occured error >>  {}

client received end >>

client closed >>

한가지 net 모듈과 다른 점은 위 실행 결과를 보시면 알수 있듯이 


write() 함수에 반환값이 없다는 점입니다. 

지금까지 TCP 통신을 기본 데이터 형태 보통 바이너리 형태라고 하는데 


이런 방식으로 했지만 우리의 목표는 json 형태의 프로토콜 구성입니다. 


자 이것을 어떻게 해야 할까요?


솔찍히 그렇게 복잡하지 않습니다. 


먼저 우리가 목표로 하는 파일 전송에 대해서 하기 전에 


간단한 json 형식의 데이터 전송을 주고 받아 봅시다. 


클라이언트에서는 다음과 같은 데이터를 보냅니다. 

{ req : 'file' , format : 'jpg' }

서버에서는 위 데이터를 수신받고 req 필드를 확인하고 다음과 같은 데이터를 보냅니다. 


{ ack : 'file' , filename : '001.jpg' }

이거 생각보다 간단하게 구현됩니다. 


구현 소스는 다음과 같습니다. 


몇가지 수정 사항이 있는데 소스를 보고 추측해 보시기 바랍니다. 


[C025_server_json.js]------------------------------------------------------

var jot = require('json-over-tcp');

 

var PORT = 34271;

var msg = { ack : 'file' , filename : '001.jpg' };

 

var server = jot.createServer();


server.on('connection', function ( client_socket ) { 


console.log('client connected >> '  + client_socket.remoteAddress +':'+ client_socket.remotePort);

client_socket.setNoDelay(true);

// client_socket.setTimeout( 4000 );

client_socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

});


client_socket.on('data', function (data) {

console.log('client received data >> %s:%s [%j]', client_socket.remoteAddress, client_socket.remotePort , data );

client_socket.write(msg);

});


client_socket.on('end', function () {

console.log('client received end >> ' );

});

client_socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

client_socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


client_socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

});


server.listen(PORT, function() {

address = server.address();

console.log("server listening on %j", PORT, address);

});

--------------------------------------------------------------------------------


[C025_client_json.js]------------------------------------------------------

var jot = require('json-over-tcp');


var HOST = '127.0.0.1';

var PORT = 34271;


var socket  = new jot.Socket();

var msg = { req : 'file' , format : 'jpg' };


socket.setNoDelay(true);


socket.connect(PORT, HOST, function() {


console.log('client connected to : ' + HOST + ':' + PORT );

// socket.setTimeout( 1000 );

socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

})

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

console.log('client received data >> [%j]' , data );

})


socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


socket.on('end', function () {

console.log('client received end >> ');

});

socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

socket.write(msg);

console.log( 'client send data [%j] ', msg );

socket.end();

});

--------------------------------------------------------------------------------


클라이언트 측은 동작 메세지를 표출하기 위한 console.log 부분 이외에는 바뀐게 없습니다.


반면에 서버측은 이벤트 부분이 수정되어 있습니다.


즉 'connection' 이벤트는 jot 에서 json 데이터 형태로 프로토콜을 주고 받는 경우에 사용하는 것이죠.


createServer() 함수의 콜백에서 접속 처리를 할 경우에 'data' 이벤트는 기존 tcp 방식의 이벤트와 같습니다. 

실행해 보면 json 형태의 데이터 전달이 되는 것을 확인해 보실수 있습니다. 


서버측 :


$ node server_json.js

server listening on 34271 { address: '0.0.0.0', family: 'IPv4', port: 34271 }

client connected >> 127.0.0.1:60358

client received data >> 127.0.0.1:60358 [{"req":"file","format":"jpg"}]

client received end >>

client closed >>

client connected >> 127.0.0.1:60359

client received data >> 127.0.0.1:60359 [{"req":"file","format":"jpg"}]

client received end >>

client closed >>


클라이언트 측 : 


$ node client_json.js

client connected to : 127.0.0.1:34271

client send data [{"req":"file","format":"jpg"}]

client received data >> [{"ack":"file","filename":"001.jpg"}]

client received end >>

client closed >>


자.. 여기서 잠깐 jot 는 도대체 json 객체를 전달하는지 살표 볼까요?


다음과 같은 순서로 json 데이터를 tcp 로 전달 합니다. 


1.먼저 write 함수에 의해서 전달된 json 데이터를 문자열로 변환합니다. 

2. 16 비트 리틀엔디언 정수형으로 206 구별 숫자를 전송 합니다.

다음과 같이 이 값을 선언되어 있습니다.

Protocol.prototype._SIGNATURE = 206;

3. 32 비트 리틀엔디언 정수형으로 전송할 데이터의 크기를 전송 합니다. 


4. 변환된 문자열을 전송합니다. 

결국 이것의 역순으로 수신되었을때 해석되고 처리 됩니다. 


만약 node.js 에서 이 모듈을 쓰는 프로그램과  C 또는 기타 json 통신으로 하시는 경우


이와 같은 헤더가 붙어서 전달된다는 점을 기억해야 문제가 없을 겁니다. 


자 이제 본격적으로 파일을 전송해 봅시다. 

파일을 읽고 쓰는 것은 node.js 에서 readFile(), writeFile() 이라는 함수를 사용하면


쉽게 구현 가능합니다. 


노드의 최고의 장점은 쉽고 빠르게 개발을 가져 갈수 있다는 것이죠..


다음 소스가 이를 구현한 것입니다. 


몇 가지 추가 루틴으로 구현이 가능하다는 것을 알 수 있죠..


[C026_server_json_file.js]------------------------------------------------------

var jot = require('json-over-tcp');

var fs = require('fs');

 

var PORT = 34271;

var msg = { ack : 'file' , filename : 'test_ack.bmp' , data : null };

 

var server = jot.createServer();


server.on('connection', function ( client_socket ) { 


console.log('client connected >> '  + client_socket.remoteAddress +':'+ client_socket.remotePort);

client_socket.setNoDelay(true);

// client_socket.setTimeout( 4000 );

client_socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

});


client_socket.on('data', function (req_data) {

console.log('client received data >> %s:%s [%j]', client_socket.remoteAddress, client_socket.remotePort , req_data );

if( req_data.req === 'file' ){

fs.readFile( './' + req_data.filename, function (err,data) {

 if (err) {

return console.log(err);

 }

 console.log('readed file data %s:%d', req_data.filename, data.length );

 msg.data = data;

 client_socket.write(msg);

});

}

});


client_socket.on('end', function () {

console.log('client received end >> ' );

});

client_socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

client_socket.on('timeout', function () {

console.log('client occured timeout >> ' );

});


client_socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

});


server.listen(PORT, function() {

address = server.address();

console.log("server listening on %j", PORT, address);

});


--------------------------------------------------------------------------------


[C026_client_json_file.js]------------------------------------------------------

var jot = require('json-over-tcp');

var fs = require('fs');


var HOST = '127.0.0.1';

var PORT = 34271;


var socket  = new jot.Socket();

var msg = { req : 'file' , filename  : 'test.bmp' };


socket.setNoDelay(true);


socket.connect(PORT, HOST, function() {


console.log('client connected to : ' + HOST + ':' + PORT );

socket.setTimeout( 1000 );

socket.on('drain', function () {

console.log('client occured drain >> write buffer is empty' );

})

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

console.log('client received data >> [filename : %s, size : %d]' , data.filename, data.data.length );

fs.writeFile( './' + data.filename , new Buffer( data.data ), function (err) {

 if (err) console.log(err);

 socket.end();

});

})


socket.on('error', function (error) {

console.error('client occured error >> ', error);

});

socket.on('timeout', function () {

console.log('client occured timeout >> ' );

socket.end();

});


socket.on('end', function () {

console.log('client received end >> ');

});

socket.on('close', function (error) {

console.log('client closed >>' );

if (error) {

console.log('The socket had a transmission error.');

}

});

socket.write(msg);

console.log( 'client send data [%j] ', msg );

});


--------------------------------------------------------------------------------


이 프로그램들은 다음과 같은 json 데이터를 주고 받습니다. 


클라이언트는 


{ req : 'file' , filename  : 'test.bmp' }

를 전송해서 test.bmp 라는 파일을 요청합니다. 


이를 받은 서버는 다음과 같은 형태로 응답합니다. 


{ ack : 'file' , filename : 'test_ack.bmp' , data : [비트맵파일] };

data 프로퍼티는 비트맵파일 바이너리를 전달 합니다. 


여기서 클라이언트에서 조심해야 하는 것은 


저장시 buffer 타입으로 변경해서 전달해야 하는 겁니다. 


집적 파일로 저장하면 문자열로 해석되기 때문입니다. 


이제 실행 결과는 다음과 같죠...


서버측 :


$ node server_json_file.js

server listening on 34271 { address: '0.0.0.0', family: 'IPv4', port: 34271 }

client connected >> 127.0.0.1:60717

client received data >> 127.0.0.1:60717 [{"req":"file","filename":"test.bmp"}]

readed file data test.bmp:3302454

client occured drain >> write buffer is empty

client received end >>

client closed >>

클라이언트 측 : 


$ node client_json_file.js

client connected to : 127.0.0.1:34271

client send data [{"req":"file","filename":"test.bmp"}]

client received data >> [filename : test_ack.bmp, size : 3302454]

client received end >>

client closed >>


node.js 에서 tcp 를 json 형태로 하는 방법을 지금까지 알아 보았습니다. 


조금이라도 도움이 되었으면 좋겠네요

그렇지만 쉽죠?