이전에 forever을 작성을 했었다.

 

얼마 뒤 다른 분과 협업을 하다 보니 그쪽은 pm2를 사용하여서 작성하게 됐다.

 

너무 바쁘기 때문에 쓰레드라던지 등등을 자세하게 파헤치진 않았다. 

 

대충 아는건 멀티 코어 혹은 하이퍼 스레딩을 사용가능하다 정도... forever도 이게 가능한지는 모르겠다.

 

아무튼 내가 체감되는 것들만 맨 끝에 남긴다. 

 

우선 pm2를 설치한다.(forever랑 명령어 방식이 거의 유사하다.)

 

$ > npm install pm2 -g

 

pm2 명령어를 사용하여 실행시켜본다. 

pm2 start app.js

프로세스 중지

pm2 stop 0 (프로세스 번호)

프로세스 삭제

pm2 del 0 (프로세스 번호)

프로세스 전체 삭제

pm2 kill

프로세스 전체 로그 - 이 부분이 forever 과 달랐다. 특정 프로세스의 로그를 출력하는 방법을 모르겠다. 현재 pm2로 실행되고 있는 프로세스 전체의 로그가 한번의 출력 된다.

pm2 log

프로세스 목록

pm2 ls

실행 중인 프로세스 모니터

pm2 monit

 

외주를 준 프로젝트 백엔드 소스코드를 받았는데 로컬테스트를 하기 위해 내가 사용하는 PC에서 구동을 해보려 하지만 모듈이 설치가 안된다. node와 npm 버전 때문인 것 같다. 그래서 업데이트를 해보려고한다.

 

 

Node.JS 공홈에 나와 있는 방법을 우선 올린다.

 

윈도우는 그냥 패키지 매니저 홈페이지에서 다운 받아서 원래 있던 폴더에 덮어쓰면 된다.

 

 

아마 검색을 하면 n 으로 Node.JS 패키지를 설치를 하는게 많을텐데 괜히 힘 빼지 말자 n은 리눅스 계열이다.

나도 윈도우에서 주구장창 했다... 결과 안된다. 아래처럼 EBADPLATFORM 에러가 발생한다. 대충 써보면 내가 사용중인 OS는 지원을 하지않는다는 얘기다. 

각 OS 마다 업데이트 방법이 다르니 공홈을 참고하자.

이 포스팅에선 postman을 이용하여 업로드를 진행할 것이다. 추후에 안드로이드 어플을 통해서 업로드 Client 부분을 작성할 예정이며 작성하면 이 글을 수정하여 링크를 달아놓을 예정이다.

 

우선 파일을 업로드하여 서버 upload라는 폴더 안에 저장할 것이고, DB에 파일에 대한 정보를 넣을 생각이다. 이 이유는 맨 아래에 남겨놨다.

 

우선 NPM 에서 사용하는 기본 예제다.

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })
 
var app = express()
 
app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file is the `avatar` file
  // req.body will hold the text fields, if there were any
})
 
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  // req.files is array of `photos` files
  // req.body will contain the text fields, if there were any
})
 
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
  // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
  //
  // e.g.
  //  req.files['avatar'][0] -> File
  //  req.files['gallery'] -> Array
  //
  // req.body will contain the text fields, if there were any
})

코드에서 보면 알 수 있듯이 upload할 때 속성이 다르다. 

.single(fieldname)

이름을 가진 단일 파일을 허용합니다 fieldname. 단일 파일은 req.file. 로 사용하면 된다.

.array(fieldname[, maxCount])

이름이 모두 인 파일 배열을 허용합니다 fieldname. 둘 이상의 maxCount파일이 업로드 되면 선택적으로 오류가 발생합니다 . 파일 배열은 req.files. 로 사용하면 된다.

.fields(fields)

fields 파라미터로 넘어오는 것들을 배열 형태로 저장된다. 파일은 req.files로 사용하면 된다.

 

 

 

NPM에 나와있는 각 파일 정보다.

multer(opts)

업로드된 파일을 Multer에게 어디로 업로드할 지 알려주며 옵션 객체를 하지않았다면 메모리에 저장된다. 보통은 dest 옵션으로 파일 저장 위치를 지정하는데 더 자세하게 제어하고 싶으면 storage를 사용하면 된다.

var  upload  = multer ( {  dest : ' uploads / ' } ) 

 

storage

DiskStorage

말 그대로 로컬 디스크에 저장한다는 의미이며 두 가지 옵션을 사용할 수 있다. 

1. destnation : 파일의 목적지 즉 경로를 설정 할 수 있다.

2. filename : 파일 이름을 설정 할 수 있다. 아래 코드에서 보면 알 수 있듯이 Date.now()가 들어 갔는데 타임스태프를 넣은 이유가 파일 이름이 겹칠 수 있기 때문이라고 생각해두면 될 것 같다.

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/my-uploads')
  },
  filename: function (req, file, cb) {
    cb(null, file.fieldname + '-' + Date.now())
  }
})
 
var upload = multer({ storage: storage })

 

MemoryStorage

메모리 스토리지 엔진은 파일을 메모리에 Buffer개체 로 저장 합니다. 옵션이 없습니다.

var storage = multer.memoryStorage()
var upload = multer({ storage: storage })

 

 

파일 정보를 DB에 저장해둬야 할 필요가 있다. 왜냐면 파일을 다운 받을 경우 자신이 업로드한 파일을 정확하게 받을 수 있다. 그리고 파일 이름은 한글로 넣을 경우 깨지는 경우가 발생했다. UTF-8 등 ... 인코딩 많은걸 해봤지만 안됐다. 나중에 듣기론 이런 경우가 많아 파일 오리지널 이름을 같이 업로드하여 DB에 넣어둔 뒤 다운로드 할 시 DB에서 파일 이름을 꺼내와 다시 붙여주는 걸로 알고 있다. 물론 파일 리스트 조회 할 시도 DB에서 파일 오리지널 이름을 가져와 Client로 넘겨준다.

비동기 다음으로 exports라는 개념이 발목을 잡았다.

지금까지C나 JAVA에서는 include나 import를 하면 그 클래스나 함수, 변수들을 사용할 수 있었다. Node에선 require를 해도 그 변수나 함수를 사용하지 못했다. 

 

Node에서는 모듈에서 exports를 하지 않으면 그 변수나 함수를 사용하지 못하는 것이다.

 

구글링으로 알아본 결과 A라는 모듈을 require를 하면 해당 모듈의 module.exports를 return 한다고 보면 되는 것이다. 그러므로 A모듈에서 module.exports를 하지 않았다면 그 어떤 것도 참조할 수 가 없다.

 

아무튼 본론으로 넘어와서 module.exports를 사용함에 많은 방법이 있었다.

 

우선 module.exports와 exports가 있다. 이 둘의 차이점... 사실 없었다. 공식 문서를 보면 그저 module.exports의 더 짧은 버전... 즉 module 조차도 길었다고 생각했나보다... 아무튼 둘의 차이점은 성능이 아닌 그저 더 짧다는 것에 있다.

exports

Added in: v0.1.12

A reference to the module.exports that is shorter to type. See the section about the exports shortcut for details on when to use exports and when to use module.exports.

 

그렇다면 어떻게 사용하면 되는건가... 여기부터는 솔직히 쓰는 사람의 특성과 그 모듈의 특성에 따라 나뉘는 듯 하다.

 

아래 예를 들어 코드를 작성해봤다. 기호에 맞게 사용하면 될듯하다.

exports.func = function (){console.log('func')}

exports = {func:func}   // 이 경우 따로 func 함수가 정의가 되어 있어야함.

exports = { func : function (){ console.log('func') } }

exports.a = b

 

처음 Node.js 개발을 하다가 갑자기 함수의 실행 순서가 이상했다. 난 당연히 내가 뭔가를 잘못 짰다는 생각만 하고 에러를 찾아봤다. 그렇지만 10분 뒤 내게는 이해할 수 없는 일이 일어났다. 잘못 짠게 하나도 없었다. 지금까지 계속 순차적인 함수 실행 방식만을 생각하고 코딩을 했기 때문이다. 지금까진 계속 안드로이드 자바 위주로 코딩을 하다보니 비동기는 생각을 하지 못했다. 우연하게 콘솔을 찍어보고 실행 순서가 이상하다는 걸 발견하고 Node.js는 비동기로 처리를 하는지 찾아봤다. 

 

Syncronous 동기 - 요청을 보낸 후 해당 요청의 응답을 받아야 다음 동작을 실행하는 방식

Asynchronous 비동기 - 요청을 보낸 후 응답과 관계없이 다음 동작을 실행하는 방식

 

그러니깐 비동기는 요청을 보내면 서버에서 오래 걸리면 먼저 응답을 하고 그 후에 처리를 하고 있었다. DB에서 데이터를 받아오는 등의 작업을 해야한다면 우선적으로 그에 대한 응답을 해버리고 다른 작업들도 실행한다.

 

가끔 동기식 처리를 해야할 경우가 생긴다. 그에 대한 해결 방안도 있다.

 

Node.js 에서 많이 찾아봤다. 보통 웹에선 callback을 사용했을 것이다. 물론 난 자바스크립트를 모르기 때문에 callback의 개념을 아예 몰랐다. 나중에 callback을 썼는데 문장이 계속 길어지며 한 눈에 보기도 불편했다.

그래서 Promise를 적용했다. 

function fooPromise(data) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log("fooPromise");
			if(data === 1000)
				reject(new Error("err"));
			else
				resolve(data);
		}, data);
	})
}

솔직히 Promise가 callback보단 낫지만 그렇다고 가독성 또한 좋은건 아니다. 그리고 또 다른 문제들도 있었다. return 뒤에 생성자를 통해서 인스턴스화를 하였는데 결국은 return 과는 조금 다른 성질이다.  보통의 return은 함수 종료가 되서 아래의 내용이 있더라도 실행하지 않는데 resolve가 있더라도 아래 내용을 실행한다. 이 부분은 코드 개발을 하다보니 발견하였다. 난 return의 개념을 적용되는 거라고 생각했지만 그게 아니었다. 물론 비동기이기 때문일수도 있다고 생각한다.  위에 코드에서 IF문 안에 조건문을 통해서 else로 내려와 resolve에 오면 함수가 종료되지 않고 함수가 끝까지 실행된다. 이 부분에 대해서 내부 구조를 다시 살펴보고 수정하겠다. 

 

물론 이 방법만 있는건 아니다 그보다 더 좋은 문법이 있긴하다. node.js 8 LTS 버전에서 정식 지원하는 문법인 asyncawait이다. async와 await의 최고의 장점은 node.js의 장점인 비동기를 유지하면서 동기적인 모습의 코드 스타일을 적용할 수 있고, 실행시 코드를 비동기적으로 동작하면서 코드를 동기적으로 실행한다. 사용방법 또한 어렵지도 길지도 않다.

 

사용법은 function 앞에 async 붙여주고 그 함수 안에서 실행하려는 함수 앞에 await를 붙여 사용한다. 시스템에서 코드를 실행하다가 await 함수 실행을 만나면 바로 실행된다. await는 async가 붙은 함수 안에서만 작동한다.

async function foo(data) {
	var result1 = await fooPromise(data);	
	var result2 = await fooPromise(result1);
	var result3 = await fooPromise(result2);
}

 

물론 async와 await가 만능은 아니었다. async function은 결국엔 promise를 반환하게 된다. 그렇기 때문에 위에 Promise와 연결 된다. 그리고 다중 함수 구조에서는 async와 await가 제대로 작동하지 않는다. 그렇기 때문에 어느순간부터는 비동기화 되어 함수 순서가 바뀔 수 있다. 나도 정확한 이유를 파악하지 못했다. 그렇기 때문에 foo 함수 안에서 fooPromise를 호출하고 fooPromise 함수 안에서 다른 함수 A를 호출하게 된다면 async/await가 깨지는 걸 발견했다. 그래서 나는 아래의 코드처럼 다른 함수 A 안에서는 return new Promise를 사용하여 처리한다. 그렇게 되면 제대로 순서대로 작동하는 걸 발견했다.

async function foo(data) {
	var result1 = await fooPromise(data);	
	var result2 = await fooPromise(result1);
	var result3 = await fooPromise(result2);
}

async function fooPromise(data){
	var result1 = await A();
}

async function A(){
	return new Promise((resolve, reject)=>{
    	...
    })
}

나처럼 자바나 C 계열을 자주 했다면 return을 자주하는 형식이 편했기 때문에 자주 화가 날 것이다...

 

그리고 이거는 아마 다른 모듈을 많이 사용하게 되면 발견하게 될 것인데...

단편적으로 지금 당장 생각나는건 MySQL 쿼리시 콜백에서 다른 함수를 호출해야할 경우... 아래처럼 사용하면 된다

conn.query(sql, async function(err, results, fields){
        if(err){
            console.log(err)
        }
        else
            await A()
    })

 

MQTT 통신 때문에 생긴 문제였다. MQTT는 message 통신 즉 문자열을 넘기기 때문에 여러개의 데이터를 보낼 수가 없다. 그렇기 때문에 데이터를 구분하려고 문자열에 특수문자로 구분하였다. 

그런데 HASH화를 한 데이터는 구분하려고 넣은 특수문자가 포함되는 일이 생겼다. 우선 데이터를 구분하여 배열로 넣었다. 구분 특수문자로는 '/' 를 사용하였다. 의문은 HASH 알고리즘에서 다른 특수문자는 안생기나? 라는 의문이다. 그런데 한편으론 뭐가 됐던 만들어 놓으면 어떤 문자든 상관이 없겠다는 생각이 들어서 만들었다.

client.on('message', async function (topic, message) {
    var _message = message.toString().split('/');

 

그 후 HASH 데이터는 배열의 끝 번째에 배치를 했기 때문에 특수문자가 없다면 4번째에 배치된다고 가정하면 4번째부터 배열의 끝까지 합치는 개념이었다. 그래서 급하게 N번째부터 끝까지 합치는 함수를 만들었다.

 

함수의 파라미터는 해당 배열과 HASH 데이터의 배열의 위치를 보내주면 된다. 위에서 말했던 것처럼 HASH 데이터가 배열의 4번째의 위치해있다면 4를 넘겨주면된다.

async function _combineData(data, num) {
    var _data = data[num]

    for (var i = num + 1; i <= data.length - 1; i++) {
        _data += '/' + data[i]
    }
    return _data
}

우선 구분할 때 split 함수를 사용했기 때문에 특수문자가 빠진다. 그렇기 때문에 합칠 경우 특수문자가 없으므로 주의해야한다. 그렇기 때문에 데이터를 합칠 때에는 특수문자를 같이 넣어줘야한다. 그리고 끝까지가 아니라 N번째에서 M번째까지 합치려고 하면 for문 조건식에 i<= M 으로 변경해주면 된다.

보통 통신에서는 송/수신으로 나뉜다. MQTT에서는 발행인/구독자로 나뉜다.

 

MQTT 모듈 설치

# npm install mqtt -save

MQTT 모듈 세팅

var mqtt = require('mqtt');
const options = {
      host: '127.0.0.1',
      port: 1883

};
const client = mqtt.connect(options);

기본 예제

var mqtt = require('mqtt')
var client  = mqtt.connect('mqtt://test.mosquitto.org')
 
client.on('connect', function () {
  client.subscribe('presence', function (err) {
    if (!err) {
      client.publish('presence', 'Hello mqtt')
    }
  })
})
 
client.on('message', function (topic, message) {
  // message is Buffer
  console.log(message.toString())
  client.end()
})

mqtt.connect 브로커 연결 세팅

기본 세팅은 아래와 같다. 단순하게 옵션 없이 url만 입력이 가능하다.

const client = mqtt.connect(url, options);

옵션부도 필수는 아니다. 옵션은 브로커와 연결할 때 설정될 속성들이다. broker에 따라 username, password를 요구하는 브로커가 있다면 자바스크립트 객체를 생성해 옵션을 작성하고 넣어주면 된다.

const options = {
  host: '127.0.0.1',
  port: 8883,
  protocol: 'mqtt',
  username:"saii42",
  password:"password",
};

const client = mqtt.connect(options);

 

connect - 브로커 연결 부 

브로커와 연결됐을 때 실행되는 메소드다.

client.on("connect", function () => {	
  console.log("connected");
}

subscribe 구독

브로커에 구독을 하려면 client.subscribe 메소드를 사용하면 된다. 물론 토픽이 배열이나 객체도 가능하다. 나도 자주 사용하지 않을 것 같아서 맨 아래 npm 링크를 참조하면 된다. 보통은 connect 할 시 구독을 한다.

client.subscribe(topic/topic array/topic object, [options], [callback])

message - 메세지 수신부

구독을 했다면 메시지가 왔을 때 message 이벤트로 리스너를 만들어 처리한다. 보통 문자열이 전송되기 때문에 구분 문자(/, \...)를 두어 구분하여 사용하면 된다. 구분 문자를 사용하여 여러개의 데이터를 문자열로 전송할 때 주의할 점은 해당 메시지 안에 구분 문자를 사용하면 안된다. 예를 들어 해당 데이터가 암호화가 되면 특수문자도 포함된다. 그렇기 때문에 여러개로 인식되니 주의 하자. 

client.on('message', async function (topic, message) {
    var _message = message.toString().split('/');
    })

publist - 메시지 전송

기본 예제는 아래와 같다. 보통 옵션이 없다면 topic과 message만 보내면 된다. 

client.publish(topic, message, [options], [callback])

옵션과 콜백은 선택사항이다. 옵션은 retain message flag나 qos 등을 설정할 수 있다.

var options = {
  retain:true,
  qos:1
};
client.publish(topic, message, options)

 

https://www.npmjs.com/package/mqtt#client

CoAP Server listen 부분은 HTTP랑 똑같다고 보면 된다.

 

HTTP CoAP Listen

그렇지만 Router 부분이 다르다. HTTP Express에선 아래처럼 router 에서 method를 설정하며 경로를 설정할 수 있지만 CoAP은 조금 다르다.

HTTP Router

CoAP은 createServer()를 한 후 coap.on을 하면 서버의 수신부가 된다. 이후부터는 HTTP랑 동일하며 method 및 다른 설정을 할 수가 없었다. 나도 왠만한 방법을 다 해봤지만 궁금해지기 시작했다. 그래서 현재는 req.payload를 사용하여 데이터를 주고받고 있다. 현재 상태에서 해볼만 한건 다 해본것 같다. 혹시 Node.js 에서 CoAP 프로토콜을 잘 사용하시는 분 댓글로 남겨주세요...

CoAP 수신부

 

+ Recent posts