IPFS原理及实现-入口

  对于想深入了解IPFS原理的人来说,在当前IPFS技术资料并不够多的情况下,阅读IPFS源码更是一种好的选择。但是IPFS代码较多,而且设计的模块也比较多,很多人对此往往不知道该从何下手。针对这一问题,社区有两个建议:一是从github上头寻找带有help_wanted标签的issue,并以此为切入口开始学习IPFS代码,这样带有目标性的学习就不会枯燥;另一种方式是可以从比较熟悉的命令行入手,一步一步理解IPFS的原理,这样有一个由浅入深的过程。本文就从命令行出发,让大家对IPFS实现有一个初步的感觉。

说明:本文涉及到代码版本为v0.4.7

总体思路

  IPFS当中,不同的功能作为单独的模块存放,各个模块之间来互相调用。涉及到本文档的主要有两个模块:go-ipfs,go-ipfs-cmds。设计思路如下:

a, go-ipfs/core/commands/ 目录下定义好各个子命令(如add.go,cat.go)。包括子命令的描述信息、option内容、和将要运行的函数。

b, 在go-ipfs/cmd/ipfs/main.go中定义好两个函数用于后续的构造(buildEnv和makeExecutor)。

c, 使用go-ipfs-cmds模块,将命令行工具和环境配置等需要的信息转换成request结构体。

d, 根据request内容,跳到相应的已定义好的待执行函数并执行。

关键数据结构

go-ipfs-cmds/command.go中的Command类型

type Command struct {
        Options   []cmdkit.Option     
        Arguments []cmdkit.Argument   
        PreRun    func(req *Request, env Environment) error   //此处定义的函数会在Run之前运行

        Run      Function   //子命令要执行的函数
        PostRun  PostRunMap
        Encoders EncoderMap
        Helptext cmdkit.HelpText  //help时候的输出

        External bool 

        
        // type返回的类型
        Type        interface{}
        //子命令
        Subcommands map[string]*Command
}

go-ipfs-cmds/request.go中的request类型

type Request struct {
        Context       context.Context
        Root, Command *Command    //Root是根命令,Command是子命令解析

        Path      []string
        Arguments []string
        Options   cmdkit.OptMap

        Files files.File

        bodyArgs *arguments
}

源码详解

当我们在命令行输入: ipfs refs local 会发生什么?

首先,当命令运行时候会先执行go-ipfs/cmd/ipfs/ipfs.go中的init()函数,此函数完成子命令的注册。

var Root = &cmds.Command{
        Options:  commands.Root.Options,
        Helptext: commands.Root.Helptext,
}


var localCommands = map[string]*cmds.Command{
        "daemon":   daemonCmd,
        "init":     initCmd,
        "commands": commandsClientCmd,
}

func init() {
        Root.Subcommands = localCommands

        for k, v := range commands.Root.Subcommands {
                if _, found := Root.Subcommands[k]; !found {
                        Root.Subcommands[k] = v
                }
        }
}

然后从main()函数这个入口开始看:
打开go-ipfs/cmd/ipfs/main.go文件

func main() {
        os.Exit(mainRet())   //转到下头的mainRet函数
}

func mainRet() int {
        rand.Seed(time.Now().UnixNano())  //设置随机数种子
        ctx := logging.ContextWithLoggable(context.Background(), loggables.Uuid("session"))  //生成一个带有ID的context
        var err error

        //定义一个报错函数,方便后面调用
        printErr := func(err error) {
                fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
        }

        stopFunc, err := profileIfEnabled() //设置pprof用于性能分析
        if err != nil {
                printErr(err)
                return 1
        }
        defer stopFunc() //函数执行完之后结束性能分析 

        intrh, ctx := setupInterruptHandler(ctx) //设置处理信号函数
        defer intrh.Close()

        // 微调命令行参数
        if len(os.Args) == 2 {
                if os.Args[1] == "help" {
                        os.Args[1] = "-h"
                } else if os.Args[1] == "--version" {
                        os.Args[1] = "version"
                }
        }

        // 程序名固定下来,这样输出会稳定
        os.Args[0] = "ipfs"

        //构造环境的函数,后续会调用
        buildEnv := func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) {
                checkDebug(req)
                repoPath, err := getRepoPath(req)
                if err != nil {
                        return nil, err
                }
                log.Debugf("config path is %s", repoPath)


                return &oldcmds.Context{
                        ConfigRoot: repoPath,
                        LoadConfig: loadConfig,
                        ReqLog:     &oldcmds.ReqLog{},
                        ConstructNode: func() (n *core.IpfsNode, err error) {
                                if req == nil {
                                        return nil, errors.New("constructing node without a request")
                                }

                                r, err := fsrepo.Open(repoPath)
                                if err != nil {
                                        return nil, err
                                }

                                n, err = core.NewNode(ctx, &core.BuildCfg{
                                        Repo: r,
                                })
                                if err != nil {
                                        return nil, err
                                }

                                n.SetLocal(true)
                                return n, nil
                        },
                }, nil
        }
        //调用go-ipfs-cmds下的cli包继续
        err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor)
        if err != nil {
                return 1
        }

        return 0
}

然后进入go-ipfs-cmds/cli/run.go文件:

func Run(ctx context.Context, root *cmds.Command,
        cmdline []string, stdin, stdout, stderr *os.File,
        buildEnv cmds.MakeEnvironment, makeExecutor cmds.MakeExecutor) error {
        //报错函数供后续调用
        printErr := func(err error) {
                fmt.Fprintf(stderr, "Error: %s\n", err)
        }
        //将ctx,命令行参数和root(包含所有subcommand)转化为request结构体
        req, errParse := Parse(ctx, cmdline[1:], stdin, root)

        var cancel func()
        //设置超时时间
        if timeoutStr, ok := req.Options[cmds.TimeoutOpt]; ok {
                timeout, err := time.ParseDuration(timeoutStr.(string))
                if err != nil {
                        return err
                }
                req.Context, cancel = context.WithTimeout(req.Context, timeout)
        } else {
                req.Context, cancel = context.WithCancel(req.Context)
        }
        defer cancel()

        // 定义打印函数
        printMetaHelp := func(w io.Writer) {
                cmdPath := strings.Join(req.Path, " ")
                fmt.Fprintf(w, "Use '%s %s --help' for information about this command\n", cmdline[0], cmdPath)
        }
        //定义打印函数
        printHelp := func(long bool, w io.Writer) {
                helpFunc := ShortHelp
                if long {
                        helpFunc = LongHelp
                }

                var path []string
                if req != nil {
                        path = req.Path
                }

                if err := helpFunc(cmdline[0], root, path, w); err != nil {
                        panic(err)
                }
        }

        // 如果命令行是help,则打印返回,否则err=ErrNoHelpRequested,并继续
        err := HandleHelp(cmdline[0], req, stdout)
        if err == nil {
                return nil
        } else if err != ErrNoHelpRequested {
                return err
        }

     
        // 现在处理上头的参数解析错误
        if errParse != nil {
                printErr(errParse)

                // 用户使用错误,直接报错退出
                if req != nil && req.Command != nil {
                        fmt.Fprintln(stderr) 
                        printHelp(false, stderr)
                }

                return err
        }


        // 以下是代码错误:代码未实现相应的子命令函数,打印到标准输出
        if req == nil || req.Command == nil || req.Command.Run == nil {
                printHelp(false, stdout)
                return nil
        }

        // 此时的cmd已经是子命令对用的command结构体
        cmd := req.Command
        // 构建环境信息,这个函数会返回包含配置和IpfsNode构造函数的结构体
        env, err := buildEnv(req.Context, req)
        if err != nil {
                printErr(err)
                return err
        }
        if c, ok := env.(Closer); ok {
                defer c.Close()
        }
        // 这个函数经过一些步骤之后,找到最终要执行的函数
        exctr, err := makeExecutor(req, env)
        if err != nil {
                printErr(err)
                return err
        }

        var (
                re     cmds.ResponseEmitter
                exitCh <-chan int
        )
        //encoding这些可以先不理会
        encTypeStr, _ := req.Options[cmds.EncLong].(string)
        encType := cmds.EncodingType(encTypeStr)

        // 如果子命令没有实现对应文本解析器,使用json
        if _, ok := cmd.Encoders[encType]; encType == cmds.Text && !ok {
                req.Options[cmds.EncLong] = cmds.JSON
        }

        // 生成responseEmitter
        if enc, ok := cmd.Encoders[encType]; ok {
                re, exitCh = NewResponseEmitter(stdout, stderr, enc, req)
        } else if enc, ok := cmds.Encoders[encType]; ok {
                re, exitCh = NewResponseEmitter(stdout, stderr, enc, req)
        } else {
                return fmt.Errorf("could not find matching encoder for enctype %#v", encType)
        }

        errCh := make(chan error, 1)
        //执行子命令的Run函数,跳到真正执行函数的地方
        go func() {
                err := exctr.Execute(req, re, env)
                if err != nil {
                        errCh <- err
                }
        }()

        select {
        case err := <-errCh:
                printErr(err)

                if kiterr, ok := err.(*cmdkit.Error); ok {
                        err = *kiterr
                }
                if kiterr, ok := err.(cmdkit.Error); ok && kiterr.Code == cmdkit.ErrClient {
                        printMetaHelp(stderr)
                }

                return err

        case code := <-exitCh:
                if code != 0 {
                        return ExitError(code)
                }
        }

        return nil
}

现在真正执行子命令的Run函数,假如命令行是ipfs refs local,那么会执行go-ipfs/core/commands/refs.go内的RefsLocalCmd的Run函数

 Run: func(req cmds.Request, res cmds.Response) {
                ctx := req.Context()
                //这里最终会调用buildEnv里头的ConstructNode方法,得到关键性的IpfsNode结构。
                n, err := req.InvocContext().GetNode()
                if err != nil {
                        res.SetError(err, cmdkit.ErrNormal)
                        return
                }

                // 获取本节点所有的keys
                allKeys, err := n.Blockstore.AllKeysChan(ctx)
                if err != nil {
                        res.SetError(err, cmdkit.ErrNormal)
                        return
                }

                out := make(chan interface{})
                res.SetOutput((<-chan interface{})(out))

                go func() {
                        defer close(out)
                // 输出结果
                        for k := range allKeys {
                                select {
                                case out <- &RefWrapper{Ref: k.String()}:
                                case <-req.Context().Done():
                                        return
                                }
                        }
                }()
        },

这样,就会输出对应的结果。

你可能感兴趣的:(IPFS原理及实现-入口)