使用 libp2p 来实现一个肉鸡网络

  • 晓道
  • 更新于 2022-03-13 16:10
  • 阅读 4768

使用 libp2p 自己写一个服务,来实现一个肉鸡网络

前面已经做了一个libp2p的helloworld,比较简单,实现了ping功能。

我们先读代码,再实现一个自己的服务,建立一个p2p的肉鸡网络

有些技术细节看不懂的,请参考前面的文章从libp2p入门,开始区块链开发 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)

读代码

前面的代码是通过使用libp2p内置的 ping服务来完成的。 通过阅读ping的代码,我们可以知道如果定义实现一个服务。

const (
    PingSize    = 32
    pingTimeout = time.Second * 60

    ID = "/ipfs/ping/1.0.0"

    ServiceName = "libp2p.ping"
)

type PingService struct {
    Host host.Host
}
func NewPingService(h host.Host) *PingService {
    ps := &PingService{h}
    h.SetStreamHandler(ID, ps.PingHandler) //id 为协议名,注册一个StreamHander函数来处理收到的请求
    return ps
}

PingHandler 处理

定义如下

func (p *PingService) PingHandler(s network.Stream) {
    for { //忽略了前面部分代码
        _, err := io.ReadFull(s, buf)
        if err != nil {
            errCh <- err
            return
        }

        _, err = s.Write(buf)
        if err != nil {
            errCh <- err
            return
        }

        timer.Reset(pingTimeout)
    }
}

读代码可以知道,PingHandler建立循环读stream数据,读到什么就写回什么。

发起ping

func Ping(ctx context.Context, h host.Host, p peer.ID) <-chan Result {
    s, err := h.NewStream(network.WithUseTransient(ctx, "ping"), p, ID)
    if err != nil {
        return pingError(err)
    }
    go func() {
        defer close(out)
        defer cancel()

        for ctx.Err() == nil {
            var res Result
            res.RTT, res.Error = ping(s) //调用ping函数
}
func ping(s network.Stream) (time.Duration, error) {
    buf := pool.Get(PingSize)
    defer pool.Put(buf)

    u.NewTimeSeededRand().Read(buf)

    before := time.Now()
    _, err := s.Write(buf)
    if err != nil {
        return 0, err
    }

    rbuf := pool.Get(PingSize)
    defer pool.Put(rbuf)
    _, err = io.ReadFull(s, rbuf)
    if err != nil {
        return 0, err
    }
}

建立流,等待发起ping 的chan 信号,调用ping ping函数为产生随机字符串,通过流发出来到ping的host,再收到字符串比较。

依葫芦画个瓢

定义相关id,服务名

const (
    ID = "/ipfs/exec/1.0.0"
    ServiceName = "libp2p.exec"
)

type ExecService struct {
    Host host.Host
}

func NewExecService(h host.Host) *ExecService {
    ps := &ExecService{h}
    h.SetStreamHandler(ID, ps.ExecHandler) //这里id是上面 /ipfs/exec/1.0.0
    return ps
}

我们建立一个exec服务,通过连接节点输入 命令行,执行命令返回运行结果。

exec 命令处理

func (p *ExecService) ExecHandler(s network.Stream) {
    sreader := bufio.NewReader(s)
    for {
        linestr, _ := sreader.ReadString('\n')
        linestr = strings.TrimSpace(linestr)
        if len(linestr) > 0 {
            cmds := strings.Split(linestr, " ")
            cmd := exec.Command(cmds[0]) //调用命令行
            stdout, err := cmd.Output()
            if err != nil {
                println(err.Error())
            }
            println(string((stdout)))
        }
    }
}

为了方便处理,我们这里实现的telnet,redis服务器里面的常用命令行处理方式,"\n"表示命令行结束 我这里并没有把结果write回去,如果要弄还是参考前面的ping服务就可以。

发起 exec调用

  1. 建立流
  2. 等待命令行
  3. 发送命令行到连接node
  4. 收到命令行处理结果
func (ps *ExecService) ExecStart(ctx context.Context, p peer.ID, cmd chan string) <-chan string {
    s, err := ps.Host.NewStream(network.WithUseTransient(ctx, "exec"), p, ID)
    if err != nil {
        return execError(err)
    }
    if err := s.Scope().SetService(ServiceName); err != nil {
        //log.Debugf("error attaching stream to ping service: %s", err)
        s.Reset()
        return execError(err)
    }

    ctx, cancel := context.WithCancel(ctx)

    out := make(chan string, 1)
    go func() {
        defer close(out)
        defer cancel()
        select {
        case cmdstr := <-cmd: //等待命令行
            doexec(s, cmdstr)  //发送命令行到节点
        case <-ctx.Done():
            return
        }
    }()
    return out
}

func doexec(s network.Stream, cmd string) (int, error) {
    sreader := bufio.NewReader(s)
    _, err := s.Write([]byte(cmd)) //写命令行到 节点的 stream
    _, err = sreader.ReadString('\n')
    if err != nil {
        return 0, err
    }
    return 0, nil
}

在使用的时候我们这样用

ps := p001.NewExecService(node) 
    cmdch := make(chan string, 1)
    ch := ps.ExecStart(context.Background(), info.ID, cmdch) //对方的p2paddr,接受命令的chan
    cmdch <- "go\n"   //发送命令行,在真实的应用中需要在while里面根据输入发送
    result := <-ch
    fmt.Println("recv", result)

好了,这样一个简单exec服务的演示就完成了,github上的代码是可以跑的,关键代码在exec.go文件

上面的代码,只是代码演示,如需使用可以自己在上面的基础上面进行修改。 理论上,一个建立p2p肉鸡网络的技术细节都有了,还有一些体力活需要干。

应用libp2p到区块链领域,代码其实也差不多,这里有一篇: Substrate如何使用libp2p进行点对点通信 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn) 这里面讲到了,substrate干的工作,以及实现的几个p2p协议。

代码提交到了github, golang use libp2p 详细的可以看代码

参考文档: docs.libp2p.io 官方 golang examples

点赞 2
收藏 3
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
晓道
晓道
0xdD09...9161
技术交流:https://t.me/realDAO