새로운 강의는 이제 https://memi.dev 에서 진행합니다.
memi가 Vue & Firebase로 직접 만든 새로운 사이트를 소개합니다.
Communication(통신) 4. 구현-송신
바이트로 들어오는 데이터를 이제 명확하게 확인 할 수 있는 준비가 되었으니 실제 프로토콜을 이용해 송/수신 을 구현해보겠다.
송신
수신부를 테스트 하려면 송신부를 먼저 만들어야 된다..
제작된 프로토콜 기반으로 바이트(s)를 만들어보겠다..
가공 펑션을 만들어보자
function makebuf(command,data) {
// 빈 버퍼를 만든다 start + command + length + data.length + checksum + end
let buf = Buffer.alloc(1 + 1 + 4 + data.length + 1 + 1);
buf[0] = 0x09;
buf[1] = command;
buf.writeUInt32LE(data.length,2);
let chksum = 0;
let i;
for(i = 0; i < data.length; i++) {
buf[6+i] = data[i];
chksum += data[i];
}
buf[6+i] = chksum;
buf[6+i+1] = 0x90;
return buf;
}
가공 펑션을 사용해 헥사 스트링을 만들어보자
let send = Buffer.from([0x41,0x42,0x43,0x44]);
let buf = makebuf(0x00,send);
let lh = logh(buf);
console.log(lh);
//이렇게 한줄로 줄일 수도 있다.
console.log(logh(makebuf(0x00,Buffer.from("ABCD"))));
//printed
//-> 0x9 0x0 0x4 0x0 0x0 0x0 0x41 0x42 0x43 0x44 0xa 0x90
프로토콜 대로 체크를 다시 해보면
0x09 = start 0x00 = command 0x04 0x00 0x00 0x00 = length 0x41 0x42 0x43 0x44 = data 0x0A = checksum 0x90 = end
대부분 이해가 가는 내용이지만 바이트 처리를 처음 해본 사람이라면 궁금한 것이 있을 것이다..
바로 length 일 것이다.. 0x00 0x00 0x00 0x04 가 맞아보이는데…
우선 endian이라는 개념을 꼭 알아야만한다. 사실 프로토콜에도 명시를 해줘야 한다..
해당 링크 참조하여 숙지하도록 한다.. https://ko.wikipedia.org/wiki/엔디언
buf.writeUInt32LE(data.length,2) -> buf[2] 부터 LittleEndian(LE)으로 32비트(4바이트)를 쓰겠다는 의미이다..
대부분의 시스템(PC,임베디드장비,모바일)은 리틀엔디안을 쓰고 있는데 뒤집어서 저장을 하는 것이다..
과거 설계 당시 효율 때문이었다는데.. 관심 있는 사람은 더 찾아보면 재밌을 것 같다.
0x12345678 이라는 변수는 피씨에는 0x78 0x56 0x34 0x12로 저장되어 있는 것이다..
c/c++ 확인법
unsigned long l = 0x12345678;
unsigned char c[4];
memcpy(c,&l,4);
String s = "";
for(int i = 0; i < 4; i++) s += "0x" + IntToHex(c[i],2) + " ";
print(s);
//printed
//0x78 0x56 0x34 0x12
엔디안의 결론은 한바이트 이상의 정수에서만 필요한 개념이다.
수신
수신부는 복잡하다 한바이트 씩 카운팅 해야하므로 제어변수가 들어가야 할 수 있다..
서버는 클라이언트와 달리 늘 멀티 접속을 고려해야한다.. 접속한 클라이언트 마다 컨테이너를 생성해주고 관리를 해줘야한다..
해당 커넥션이 연결되어 있을 때 그 클라이언트만의 변수가 있어야 된다는 것이다..
c++의 경우 커넥션이 이루어질때
sock = new Conn(dclass);
형태로 메모리 할당이 필요하지만.
디스커넥트일때 메모리 해제도 잊지 말아야한다..
node net module은 간단하게 구현된다.. 그것도 싱글쓰레드 논블러킹으로..
원래 서버 수신 코드에 아래와 같이 변수를 추가해본다..
net.createServer(function(sock) {
var count = 0;
console.log('open : ' + sock.remoteAddress);
sock.on('close', function() {
console.log('close : ' + sock.remoteAddress);
});
sock.on('error', function(err){
console.error('error : ' + sock.remoteAddress + ' / ' + err);
});
sock.on('data', function (data) {
console.log((count++) + ' ' + logh(data));
});
}).listen(1234);
그리고 여러대의 클라이언트에서 접속하며 데이터를 쏴보면 해당 커넥션의 변수가 각각 증가함을 알수가 있다..
var count 는 각각의 컨테이너에서 잘 지내고 있음을 확인할 수 있는 것이다..
바이트 처리를 위한 최소한의 변수를 만들어 보겠다..
var R = {
step : 0,
com : 0, //command
len : 0,
d : [] //가변 배열
}
구분편의를 위해 obj형으로 만들었으며 걍 따로 만들어도 된다.
node V8 engine 덕에 저정도면 되는 것이다..
c/c++만 해도 배열 민/맥스로 할지 동적으로 할지 length, count 등 몇개가 더 필요하다.
가비지 컬렉터에게 모든 걸 맡겨놔도 후회 없다.
object 변수에 위에 작성한 tx를 넣어보자
var R = {
step : 0,
com : 0, //command
len : 0,
d : [],
tx : (command, data) => {
let buf = Buffer.alloc(1 + 1 + 4 + data.length + 1 + 1);
buf[0] = 0x09;
buf[1] = command;
buf.writeUInt32LE(data.length,2);
let chksum = 0;
let i;
for(i = 0; i < data.length; i++) {
buf[6+i] = data[i];
chksum += data[i];
}
buf[6+i] = chksum;
buf[6+i+1] = 0x90;
sock.write(buf);
}
}
이제 R.tx() 펑션으로 언제든지 연결된 소켓에 데이터를 보낼 수 있게되었다.
코드 가독성도 나아지고 있다.. :)
tx : (command, data) => { 는 function tx(command, data) { 와 같다.
ecmascript 6 버전 표현력으로 끝에 지저분한 }), 형태를 막아준다.
object 변수에 로깅용 함수도 추가하고 rx를 만들어서 수신할때 한바이트 씩 들어가게 해본다..
net.createServer(function(sock) {
var R = {
step : 0,
com : 0, //command
len : 0,
d : [],
log : (d) => {
if(d.length===0) return console.error('data is empty');
let l = d.length;
let hs = '';
for(let i = 0; i < l; i++) {
let h = d[i].toString(16);
if(h.length < 2) h = '0' + h;
hs += '0x' + h + ' ';
}
console.log(hs);
},
tx : (command, data) => {
let buf = Buffer.alloc(1 + 1 + 4 + data.length + 1 + 1);
buf[0] = 0x09;
buf[1] = command;
buf.writeUInt32LE(data.length,2);
let chksum = 0;
let i;
for(i = 0; i < data.length; i++) {
buf[6+i] = data[i];
chksum += data[i];
}
buf[6+i] = chksum;
buf[6+i+1] = 0x90;
sock.write(buf);
R.log(buf);
},
rx : (r) => {
//여기서 한바이트 작업..
}
}
console.log('open : ' + sock.remoteAddress);
sock.on('close', function() {
console.log('close : ' + sock.remoteAddress);
});
sock.on('error', function(err){
console.error('error : ' + sock.remoteAddress + ' / ' + err);
});
sock.on('data', function (data) {
let l = data.length;
for(let d = 0; d < l; d++) R.rx(data[d]);
R.log(data);
});
}).listen(1234);
이제 좀 가독성이 좋아져서 커넥션 독자적으로 무언 가를 넣고 싶은 마음이 생겼다 :)
결론
이제 R.rx() 코드 작성을 통해 받기만 하면 된다
댓글남기기