맥북 키보드 수리하느라 꽤 오랜 날을 보냈다.
여튼 두번째 글!
이제 p2p다운 걸 좀 해보자. peer to peer로 접근하고 메시지를 주고 받는 것이 이번 목표다.
복습
전에 만든 index.js를 다시 보자.
const Bundle = require('./bundle');
const PeerInfo = require('peer-info');
console.log("peer is creating...");
PeerInfo.create((err, peerInfo)=> {
console.log("peerID is", peerInfo.id._idB58String);
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0');
const node = new Bundle({ peerInfo });
node.start(err=> {
if (err) { throw err; }
console.log(node.peerInfo.multiaddrs.toArray().map(o=>o.toString()));
});
});
Refactoring
실제로 PeerInfo.create를 통해 node를 생성하고 시작하는 것까지 했는데 이걸 node의 생성이란 것으로 일반화할 수 있다.
그러면 createNode라는 function을 만들고 node 자체를 가져오게 하면 어떨까?
const Bundle = require('./bundle');
const PeerInfo = require('peer-info');
console.log("peer is creating...");
const createNode = () => new Promise((resolve, reject) => {
PeerInfo.create((err, peerInfo) => {
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0');
const node = new Bundle({peerInfo});
node.start(err => {
if (err) {
reject(err);
}
resolve(node);
});
});
});
createNode()
.then(node =>
console.log(
node.peerInfo.multiaddrs.toArray().map(o => o.toString())
)
);
어짜피 알고 싶은 건 node니까 Promise로 받아오고 createNode.js 라는 파일로 분리해보자.
const Bundle = require('./bundle');
const PeerInfo = require('peer-info');
const createNode = ()=> new Promise((resolve, reject) => {
PeerInfo.create((err, peerInfo) => {
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0');
const node = new Bundle({peerInfo});
node.start(err => err && reject(err) || resolve(node))
});
});
module.exports = createNode;
이제 createNode를 require하도록 index.js를 수정하자.
const createNode = require('./createNode');
createNode()
.then(node =>
console.log(
node.peerInfo.multiaddrs.toArray().map(o => o.toString())
)
);
꽤 간단한 구조다. 그래프참조
graph LR
libp2p-->bundle
libp2p-tcp-->bundle
peer-info-->createNode
bundle-->createNode
createNode-->index
Dial to Handle
그럼 이제 node간 통신을 구현해볼 차례다
peer간 통신을 protocol이라는 경로를 통해 구현해보자.
그러기 위해 handle(https://github.com/libp2p/js-libp2p#libp2phandleprotocol-handlerfunc--matchfunc) 을 통해 새로운 프로토콜에 대한 응답을 만들고 dialProtocol(https://github.com/libp2p/js-libp2p#libp2pdialprotocolpeer-protocol-callback)로 접근을 시도해본다.
createNode에서 얻은 node 객체로 handle과 dialProtocol을 구현해보니
createNode()
.then(node => {
node.handle('/print', (protocol, conn) => console.log('dialed successfully'));
node.dialProtocol(node.peerInfo, '/print', (err, conn) => { if (err) throw err });
});
결과는
Error: A node cannot dial itself
란다. 스스로 node는 자신에게 dial을 할 수 없다.
그렇다면 두개의 node를 생성해서 한쪽은 handle, 한쪽은 dialProtocol을 호출하도록 구현하자.
두개의 node가 필요하므로 Promise.all로 배열을 만든 뒤 nodes 라는 배열로 받자.
전체 index.js의 구현은 다음과 같다.
const createNode = require('./createNode');
// createNode()
// .then(node => {
// node.handle('/print', (protocol, conn) => console.log('dialed successfully'));
// node.dialProtocol(node.peerInfo, '/print', (err, conn) => { if (err) throw err });
// });
Promise.all([createNode(), createNode()])
.then(nodes => {
nodes.forEach((node, idx) => console.log(idx, node.peerInfo.multiaddrs.toArray().map(o => o.toString())));
nodes[0].handle('/print', (protocol, conn) => {
console.log("node1 dialed to node0 successfully");
});
nodes[1].dialProtocol(nodes[0].peerInfo, '/print', (err, conn) => {
if (err) throw err;
console.log('dialProtocol conn:', conn.peerInfo.id._idB58String);
});
});
별거 없다.
- nodes[1]이 dialProtocol을 /print 라는 protocol로 하고
- nodes[0]가 handle에서 응답한 후
- nodes[1]의 dialProtocol의 callBack을 호출한다.
실행해보자.
0 [ '/ip4/127.0.0.1/tcp/60894/ipfs/QmTrs4KkyUXvjEhPKYnUhu1vUoy9e9yPpVytqnLsovNY6Y',
'/ip4/192.168.0.59/tcp/60894/ipfs/QmTrs4KkyUXvjEhPKYnUhu1vUoy9e9yPpVytqnLsovNY6Y' ]
1 [ '/ip4/127.0.0.1/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz', '/ip4/192.168.0.59/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz' ]
node1 dialed to node0 successfully
dialProtocol conn: QmTrs4KkyUXvjEhPKYnUhu1vUoy9e9yPpVytqnLsovNY6Y
예상한대로 나왔다.
여기서 흥미로운 점은 dialProtocol의 callback에서 인자로 넘어온 conn은 dialProtocol을 실행한 자신이 아니라 호출한 대상인 node라는 점이다.
다른 인스턴스에서 dial하기
node REPL(node)을 열고 handle과 dial을 따로 실행해보자.
터미널 두개를 열고 한쪽에는 아래와 같이 handle을 한줄 한줄 입력하고
createNode = require('./createNode')
let node;
createNode.then(o => node);
console.log(node.peerInfo.multiaddrs.toArray().map(o=>o.toString()));
node.handle('/print', ()=>console.log('dialed'));
여기까지하면 multiaddr 주소가 보일 것이다.
[ '/ip4/127.0.0.1/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz', '/ip4/192.168.0.59/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz' ]
다른 한쪽엔 dial을 걸어보자.
두번째 ('/ip4/192.168.0.59/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz') 주소를 일단 복사를 해두자.
터미널을 하나 더 열고 dialProtocol을 해보자.
createNode = require('./createNode');
let node;
createNode.then(o => node);
node.dialProtocol('/ip4/192.168.0.59/tcp/60895/ipfs/QmUwe9fauAd4jJrQEPGdLXptz5yChhHSL9gLbyVvcjexjz', '/print', ()=>console.log(err, ""))
문제가 없다면 첫번째 handle 을 하고 있던 터미널 창에 dialed
라고 반응할 것이다.
지금쯤 여러가지 질문이 떠오를 것이다. private network에선 외부로 나가지 않고 바로 접속할 수 없나? private network가 아닌 경우 public network에선 어떻게 해야하나? tcp가 아니라 udp환경이면? web이라면 websocket을 사용할 수 있는가? 무엇보다 처음 노드를 시작해서 어떻게 찾아가야하는가? 등등 물음표가 가득하다.