NetMQTransport 상세 - planetarium/libplanet GitHub Wiki

이 문서에서는 Libplanet에서 제공하는 ITransport 인터페이스의 구현체인 NetMQTransport에 대해 자세히 설명합니다.

메세지 인코딩

Libplanet의 NetMQTransport에서는 NetMQ에서 제공하는 multi-frames 메세지를 사용합니다. NetMQTransport 에서는 메세지의 각 프레임에 다음의 정보를 담아 통신합니다.

┌────────────┐
│  Identity  │
│ (Optional) │
├────────────┤
│   Version  │
├────────────┤
│    Type    │
├────────────┤
│    Peer    │
├────────────┤
│  Timestamp │
├────────────┤
│   Version  │
├────────────┤
│    Sign    │
├────────────┤
│ DataFrames │
├────────────┤
│     ...    │
├────────────┤
│     ...    │
└────────────┘

NetMQTransport는 NetMQ의 Router-Dealer 방식으로 통신을 하고 있습니다. IdentityRouter 소켓에서 답장을 보낼 때 어떤 Dealer 소켓에 전달될 지 결정하는 값이므로, 처음 메세지를 보낼 때는 포함되지 않고 메세지를 받을 때 자동으로 추가되어 도착합니다. 답장을 보낼 때 해당 Identity 정보를 그대로 첫 번째 프레임에 포함하여 보내야 원래의 Dealer 소켓에 답장이 도착합니다.

Message 구현체를 이 NetMQMessage 형태로 바꾸고, 또 원래 Message 구현체로 돌리기 위해서 NetMQMessageCodec 을 활용합니다. 상세 구현은 해당 파일을 참고해주세요.

Kademlia DHT

초기 노드를 탐색과 노트 테이블 유지를 하기 위해 이더리움의 Kademlia DHT 알고리즘을 일부 채용하였습니다. 상세 구현은 Kademlia.cs 파일을 참고해주세요.

Send Message

NetMQTransport.SendMessageAsync() 함수를 통해 메세지를 다른 노드에 전달합니다. 병목을 막기 위해 _requests Channel에 리퀘스트를 담고, 별도로 계속해서 돌고 있는 ProcessRuntime 태스크에서 계속해서 리퀘스트들을 소비하여 처리합니다.

매 메세지마다 DealerSocket 을 새롭게 만들어 메세지를 인코딩하여 보내는 방식으로 구현되어 있으며, 사전에 정의된 예상된 답장을 모두 받을 때 까지 해당 DealerSocket 객체를 유지하는 방식으로 동작합니다.

Receive Message

NetMQTransport 객체를 생성할 때 RouterSocket 소켓을 하나 생성합니다. 이 소켓의 주소는 NetMQTransport 객체 생성자에 전달한 HostOption 정보 (IP Address 및 Port) 에 따라 생성됩니다. 다른 소켓에서 보낸 메세지는 이 RouterSocket 을 통해 받을 수 있으며, 메세지를 받을 때는 ReceiveMessage 함수를 통해 받게 됩니다. 해당 함수 내부적으로는 메세지를 디코딩해 Message 객체로 변환하여 외부에서 등록한 ProcessMessageHandler 를 인보크합니다.

외부에서도 이 이벤트 핸들러에 메서드를 등록한다면 받는 메세지를 구독할 수 있습니다.

Swarm.cs#L103

            Transport.ProcessMessageHandler.Register(ProcessMessageHandlerAsync);

Swarm에서는 위처럼 해당 이벤트 핸들러에 Swarm.ProcessMessageHandlerAsync 함수를 등록해서 사용하고 있습니다.