사랑하애오
article thumbnail

 

P2P (Peer to Peer) 구현 방법 

-> WebSocket 

-> socket.io = 웹소켓으로 웹으로 구성할 때 필수적인 구성을 미리 만들어 놓은 패키지 

   이전 node.js chatting을 만들 때 사용함

   기본 기능 외 여러가지 기능이 많다. 

   처음 사용하는 사람이 사용하기 편하다

-> ws (web socket 약자) 

   접속에 대한 것만 ex) broadcast, to 

 

블록체인은 두 개의 port가 필요

1) 서버 - 클라이언트 

2) 노드끼리 통신 

 

server - client http 서버 먼저 만들기 

with express 

express 설치  **** 터미널  경로 src 에서 

npm i express

 

network.js(websocket)

websocket은 여기서 주요한 역할을 담당한다. 서버의 역할을 담당하면서 클라이언트의 역할도 된다. 왜냐하면 블록체인은 말 그대로 자기 자신이 하나의 서버이자 클라이언트의 역할을 하기때문이다. 그래서 하나의 노드 서버가 종료되어도 모두의 서버가 같은 정보를 가지고 있을 수 있다.

서버

function wsInit(){
    const server = new WebSocket.Server({ port:wsPORT})
    server.on("connection",(ws)=>{
        init(ws)

    })
}

처음에 server.js에 있던 코드이다. 이걸로 시작이 된다.
서버의 역할을 하는 코드이다.
WebSocket.Server는 자신의 port번호를 설정함으로써 웹소켓 서버를 개설한다.
그리고 다른 웹소켓 서버가 접속할 때까지 기다리는 역할을 한다.
server.on("connection",callback)은 다른 소켓이 처음으로 접속했을 때 핸드쉐이크가 일어날 때 작동하는 코드이다. ws에는 고유의 키값이 담긴다.

클라이언트

function connectionToPeers(newPeers){
    newPeers.forEach(peer=>{ 
       
        const ws = new WebSocket(peer)
        ws.on("open",()=>{ init(ws) })
        ws.on("error",()=>{  console.log("connection failed") })
    })
}

클라이언트 쪽에서는 웹소켓에 접속하기 위에서 다음과 같이 코드를 작성해준다.
cosnt ws = new WebSocket(접속할 url)
그리고 접속이 이루어질 때 작동하게 할 코드가 있다면 ws.on("open",callback)으로 입력해준다.

클라이언트 접속과 서버 접속의 코드 차이
클라이언트가 접속해서 작동을 할 때는
cosnt ws = new WebSocket(접속할 url)으로 접속하고 ws.on("open",콜백함수)의 콜백함수에 코드를 입력해주면 된다.
반면 서버가 접속을 감지할 때는
const server = new WebSocket.Server({ port:자신의 포트번호})
를 입력한 뒤 자신의 포트번호에 들어온 이에게 행할 함수에 대해선
server.on("connection",콜백함수)로 입력해준다.

공통적인 코드

let sockets = []

다음과 같이 소켓에 주소를 넣을 공간을 마련한다.

init(ws)

여기서 ws는 상대방의 도메인 값이거나 상대방의 고유한 키값이다.

function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

가장 먼저 실행되는 wsInit()에 포함되어 가장 먼저 실행되는 코드이다. 클라이언트든지 서버든지 이 코드가 실행된다.
먼저, sockets의 배열에 넣는다.
그 다음에 initMessageHandler가 실행이 된다. 만약 에러가 날 경우는 initErrorHandler가 실행이 된다.
그 후에 write함수가 실행이 되는 구조이다.

initMessageHandler(ws)

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

initMessageHandler는 메세지를 받았을 때 그것을 타입에 따라 나누어주는 각기 다른 함수를 실행시키는 역할을 한다.
만약 메세지의 타입이 'MessageAction.QUERY_LAST'이라면 write(ws,responseLastMsg())를 실행시킨다. 이러한 함수의 형태는 react의 reducer의 형태와 아주 비슷하다. socket.io에서 사용자정의 함수가 하는 역할을 type이 한다고 생각하면 편하다. ws는 가벼운 패키지기에 이런 것 하나하나 설정해주어야 한다. 하나의 기법이기에 어렵게 생각하지 않아도 된다.
그리고

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

이기에 결국 이 함수의 형태는 다음과 같이 된다.

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case 0:
                write(ws,{type:2,data:JSON.stringify([bc.getLastBlock()])}) 
            break;
            case 1:
                write(ws,{type:0,data:null})
            break;
            case 2:
                handleBlockResponse(message)
            break;
        }
    })
}

여기에

function write(ws,message){
  ws.send(JSON.stringify(message)) 
}

을 대입하면 initMessageHandler(ws)는 다음과 같이 나타낼 수 있다.

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case 0://query_last
            	ws.send(JSON.stringify({type:2,data:JSON.stringify([bc.getLastBlock()])})) 
            break;
            case 1://query_all
    	         ws.send(JSON.stringify({type:0,data:null})) 
            break;
            case 2://response_block
	          handleBlockResponse(message)
            break;
        }
    })
}

handleBlockResponse(message)

다른 사람의 블럭이 나의 블럭보다 많을 때
받은 블록 중 마지막 블록의 previousHash값 = 내 마지막 블록으로 만들어진 암호화 값
      ->하나만 추가한다.(addBlock사용)
받은 블록의 길이가 1일 때(내꺼의 블록이 연결되지 않았음?)
      ->모든 블럭을 보낸다.
많이 차이가 날 때
      ->블록 전체를 최신화한다. (replaceBlock사용)

만약 다른사람들의 블럭보다 나의 블럭이 더 많을 때는 반대로 실행이 될 것이다.

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
            if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

broadcast

function broadcast(message){
    sockets.forEach( socket => {
        write(socket,message)
    })
}

내가 연결된 모든 소켓한테 메세지를 보내는 함수이다.

공통적인 코드 모음

function getSockets(){ return sockets }

const MessageAction = {
    QUERY_LAST:0,
    QUERY_ALL:1,
    RESPONSE_BLOCK:2,
}


function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

function queryBlockMsg(){
    return {
        type:MessageAction.QUERY_LAST,
        data:null
    }
}

function initErrorHandler(ws){
    ws.on("close",()=>{ closeConnection(ws) })
    ws.on("error",()=>{ closeConnection(ws) })
}

function write(ws,message){
  ws.send(JSON.stringify(message)) 
}

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

function responseBlockMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify(bc.getBlocks())
    }
}


function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
        console.log(
            "블록의 갯수 \n" +
            `내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
            `내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
        )
        

        if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

전체적인 코드

//network.js
const WebSocket = require('ws')
const wsPORT = process.env.WS_PORT || 6006
const bc = require('./block')

let sockets = []
function getSockets(){ return sockets }

const MessageAction = {
    QUERY_LAST:0,
    QUERY_ALL:1,
    RESPONSE_BLOCK:2,
}

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

function queryAllMsg(){
    return {
        type:MessageAction.QUERY_ALL,
        data:null
    }
}


function queryBlockMsg(){
    return {
        type:MessageAction.QUERY_LAST,
        data:null
    }
}

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

function responseBlockMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify(bc.getBlocks())
    }
}

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
        console.log(
            "블록의 갯수 \n" +
            `내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
            `내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
        )
        

        if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

function initErrorHandler(ws){
    ws.on("close",()=>{ closeConnection(ws) })
    ws.on("error",()=>{ closeConnection(ws) })
}

function closeConnection(ws){
    console.log(`Connection close ${ws.url}`)
    sockets.splice(sockets.indexOf(ws),1)
}


function wsInit(){
    const server = new WebSocket.Server({ port:wsPORT})
    server.on("connection",(ws)=>{
        console.log('ws는 과연 무엇일까요?')
        console.log(ws)
        init(ws)

    })
}

function write(ws,message){ ws.send(JSON.stringify(message)) }
function broadcast(message){
    sockets.forEach( socket => {
        write(socket,message)
    })
}

function connectionToPeers(newPeers){
    newPeers.forEach(peer=>{ 
       
        const ws = new WebSocket(peer)
        ws.on("open",()=>{ init(ws) })
        ws.on("error",()=>{  console.log("connection failed") })
    })
}

function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

module.exports = {
    wsInit,
    getSockets,
    broadcast,
    responseLastMsg,
    connectionToPeers,
    
}
profile

사랑하애오

@사랑하애

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!