새로운 강의는 이제 https://memi.dev 에서 진행합니다.
memi가 Vue & Firebase로 직접 만든 새로운 사이트를 소개합니다.

바로가기


Communication(통신) 5. 구현-수신

3 분 소요

이제 바이트 단위로 파악하여 원하는 데이터를 꺼내보도록 하겠다..

수신

우선 코드를 작성 후 설명해보겠다.

rx(r)

var R = {
    step : 0,
    com : 0, //command
    len : 0,
    d : [],
    log : (d) => {
        //중략  
    },
    tx : (command, data) => {
        //중략
    },
    rx : (r) => {
        switch (R.step) {
            case 0:
                if(r === 0x09) R.step++;
                break;
            case 1:
                R.com = r; 
                R.d = [];
                R.step++;
                break;
            case 2:
                R.d.push(r);
                if(R.d.length >= 4) {
                    R.len = Buffer.from(R.d).readUInt32LE(0);
                    R.d = [];
                    if(R.len > 0 && R.len < 1000) R.step++;
                    else                          R.step=0;
                }
                break;
            case 3:
                R.d.push(r);
                if(R.d.length >= R.len) {
                    R.step++;
                }
                break;
            case 4:
                let chksum = 0;
                for(let c = 0; c < R.d.length; c++) chksum += R.d[c];
                chksum = chksum%0x100;
                if(r === chksum) R.step++;
                else R.step=0;
                break;
            case 5:
                if(r === 0x90) {
                    R.step++;
                    R.decode();                    
                }                
                break;
            case 6:
                //idle
                break;
                
        }
    },
    decode : () => {
        //data 처리
        R.step=0;
    }
}

제어변수 R.step과 지난번 “ABCD” 패킷이 왔다고 생각하고 확인해보자.

0x9 0x0 0x4 0x0 0x0 0x0 0x41 0x42 0x43 0x44 0xa 0x90 

해석

  • 0
    9(0x09) 인지 확인하고 넘김.

  • 1
    ‘커맨드 : 0’ 저장하고 가용버퍼 비우고 넘김.

    다음 받을 것이 4바이트를 채워야하기 때문

  • 2
    스텝이 2인 상태에서 4번에 걸쳐 [0x4 0x0 0x0 0x0] 데이터를 받는다.
    형변환 하여 ‘길이 : 4’ 저장하고 가용버퍼 비우고 넘김.
    최소한의 예외처리 0보다 크고 1000보다 작으면 넘김.
    그렇지 않을 경우 다시 처음부터 시작한다.

  • 3
    스텝이 3인 상태에서 앞전에 받아 놓은 ‘길이 : 4’ 만큼 [0x41 0x42 0x43 0x44] 데이터를 받고 넘김.

  • 4
    ‘보낸 데이터 : 0xa’ 와 받은 데이터[0x41 0x42 0x43 0x44]의 ‘checksum : 0x0A’ 과 맞는지 확인하고 맞으면 넘김.
    그렇지 않을 경우 다시 처음부터 시작한다.

  • 5
    144(0x90) 인지 확인하고 넘기고 디코드

  • 6 아무것도 안하고 decode가 끝나길 기다림…

스텝6는 대체 왜 필요할까?
스텝5에서 0으로 만들면 되지 않을까? 라고 생각할 수 있다..
데이터가 연달아서 오면 어떻게 될까?
0x9 0x0 0x4 0x0 0x0 0x0 0x41 0x42 0x43 0x44 0xa 0x90 0x9 0x3 0x3 0x0 0x0 0x0 0x31 0x32 0x33 0xa 0x90
decode가 10초 이상 걸리는(DB work) 펑션인데 블러킹해놓고 돌리지 않으면 decode 중에 R.len R.com 등이 바뀔 수 있다..
그러므로 queue,ring등의 개념을 활용해 decode가 아닐때만 rx(r)의 r은 queue에서 빼다 쓰고 평소엔 queue 적재를 하는 방법을 구상해야한다..(이건 추후에..)
node는 callback을 활용해서 처리해야하지만..
다른 플랫폼 c/java등은 대부분 decode중 블러킹(decode가 끝나야 다음 일을 함) 상태이 때문에 간단하게 작성한 코드이다..

상당히 단순해 보이는 코드지만 규약되지 않는 이상한 데이터들을 다 막아주며 본코드와 차이가 있지만 기본은 검증이 다 된 코드다..


이제 데이터를 프로토콜에 맞춰 만들어서 디코드를 해보겠다.

이전에 만든 가공 펑션을 이용해서 패킷을 만들어보자.

let msg = Buffer.alloc(20);
msg.write("FKKMEMI"); //10byte는 보내는 사람 이름
msg.write("U LOVE ME?",10); //10byte는 내용
console.log(logh(makebuf(0x00,msg)));
//printed
//0x9 0x0 0x14 0x0 0x0 0x0 0x46 0x4b 0x4b 0x4d 0x45 0x4d 0x49 0x0 0x0 0x0 0x55 0x20 0x4c 0x4f 0x56 0x45 0x20 0x4d 0x45 0x3f 0xa0 0x90 

다시 프로토콜을 확인 해보면 아래와 같은 패킷이었다.

send
  받는 사람 내용
길이 10 1~140
유형 char char
ack
  내용
길이 1
유형 int

decode()

var R = {
    step : 0,
    com : 0, //command
    len : 0,
    d : [],
    log : (d) => {
        //중략  
    },
    tx : (command, data) => {
        //중략
    },
    rx : (r) => {
        //중략
    },
    decode : () => {
        let d = Buffer.from(R.d);
        let p = {}
        switch (R.com) {
            case 0 : //message
                p.user = d.toString('ascii',0,10).replace(/[\0]/g, '');
                p.msg = d.toString('ascii',10,20).replace(/[\0]/g, '');
                break;
            case 1 : break;//another commands ...
            default : break;
        }
        console.log(p);
        MSGs.update({user:p.user},{$set:p},function(err,r) {
            if(err) {
                console.error(err);
                R.tx(0x00,[0x00]);
            }
            else R.tx(0x00,[0x01]);
            R.step=0;            
        })
    }
}

해석

decode는 아래와 같이 최소한 4단계의 과정이 필요하다.

*parse > store > send ack > ready

parse

let d 는 현재 [0x46 0x4b 0x4b 0x4d 0x45 0x4d 0x49 0x0 0x0 0x0 0x55 0x20 0x4c 0x4f 0x56 0x45 0x20 0x4d 0x45 0x3f] 의 데이터를 가지고 있다.

  • 받는 사람은 10바이트로 지정했기 때문에 [0x46 0x4b 0x4b 0x4d 0x45 0x4d 0x49 0x0 0x0 0x0] 가 된다.
    • 아스키 테이블을 확인하며 한바이트 씩 확인 해보면 7byte는 “FKKMEMI” 임을 알 수 있다.
    • 스트링에서 0의 의미는 문자가 끝났다는 표현이다.
    • 하지만 별도의 스트링파서를 넣지 않았기 때문에 이상한 문자가 될 수 있다.
    • 그래서 regexp로 종료케릭터(‘\0’ : 0x00)는 제거했다.
  • 내용은 가변이며 length(0x14:20)개 이기 때문에 나머지 바이트는 [0x55 0x20 0x4c 0x4f 0x56 0x45 0x20 0x4d 0x45 0x3f] 가 된다.
    • 아스키 테이블을 확인하며 한바이트 씩 확인 해보면 10byte는 “U LOVE ME?” 임을 알 수 있다.
store
  • file로 저장하든 db에 저장하든..

위의 예제는 mongoose를 이용한 방법이다..

send
  • 저장은 오래 걸릴 수 있으므로 꼭 callback을 받고 보낸다.
  • DB 처리중 에러가 있다면
    • R.tx(0x00,[0x00]); 못 저장했다고 0을 보낸다.
    • R.tx(0x00,[0x01]); 잘 저장했다고 1을 보낸다.

      실제 패킷 : 0x9 0x0 0x1 0x0 0x0 0x0 0x01 0x01 0x90

ready
  • 모든 처리 완료 후 스텝을 0으로 만든다.

    사실 Send도 callback을 받아야 명확하다…
    R.tx(0x00,[0x01],function(err) {R.step=0}); 형태로 제작해야한다..

통신 연재는 여기까지..

피곤해서 급 종료.. 나중에 더 추가.

댓글남기기