建议和反馈

请填写你的反馈内容

比特币网络编程,第2部分

2020-01-14 ·1555次阅读 ·读完需要7分钟

到目前为止,我们构建了一个非常基本的比特币节点,该节点连接到网络并发送version消息。在添加新功能和消息之前,我们需要确保消息序列化是可靠的,并且将对我们即将添加的任何新消息都有效。

同样,为了能够处理来自网络中其他节点的消息,我们需要实现消息反序列化。反序列化与序列化相反:我们从其他节点接收字节序列,并将其解码为数据结构。但是,反序列化将略有不同,因为字节是通过TCP连接发送的,而TCP连接是流的。

在本文的结尾,我们将实现verack消息以确保一切正常,添加新消息就像创建Golang结构一样容易。

让我们开始!

改善消息序列化

这是我们现阶段序列化消息的方式:

func (m Message) Serialize() ([]byte, error) {	var buf bytes.Buffer
	if _, err := buf.Write(m.Magic[:]); err != nil {		return nil, err
	}
	if _, err := buf.Write(m.Command[:]); err != nil {		return nil, err
	}
	if err := binary.Write(&buf, binary.LittleEndian, m.Length); err != nil {		return nil, err
	}
	if _, err := buf.Write(m.Checksum[:]); err != nil {		return nil, err
	}
	if _, err := buf.Write(m.Payload); err != nil {		return nil, err
	}
	return buf.Bytes(), nil}

也就是说,我们正在为每条消息实现序列化。但是关键是序列化是基于类型的,而不是基于消息的。这种类型具有唯一的(尽管对于大多数而言,它们是相同的)序列化方法,而不是结构。

最终,我们将收到比类型更多的消息,并且由于消息由类型组成,因此实现基于消息的序列化是没有意义的。相反,我们应该实现一种更通用的序列化方式,因此将来会自动添加每条新消息,并且不需要任何Serialize方法(除非它包含我们尚未实现序列化的类型,除非它包含需要唯一的序列化方法,例如IPv4类型)。

我们想拥有什么?

Golang提供了用于常见编码/序列化算法的接口,功能和方法。因此,不发明轮子而是遵循这些做法非常有意义。

Golang提供的最著名和最常用的编码库是encoding/json它提供了两种数据编码和解码方式:

  1. 流一个,由一对实现方法DecoderEncoder结构实现,允许DecodeEcode解码来自输入流的原始数据并将已编码的数据写入输出流。

  2. 非流式的,由众所周知的MarshalerUnmarshaler接口,MarshalJSONUnmarshalJSON方法和MarshalUnmarshal功能实现。我确定您已经熟悉它们。

这种方法简单明了,因此我们也要使用它。

对于序列化,我们将实现非流式处理方法,因为在开始序列化之前,我们已经拥有了所需的所有数据。

对于反序列化,我们将实现流方法,因为该节点将从TCP连接(即流)读取序列化的消息。

binary

创建binary子文件夹并marshaler.go在其中:

// binary/marshaler.gofunc Marshal(v interface{}) ([]byte, error) {    	var buf bytes.Buffer
  1. 我们正在使用与中相同的函数签名json.Marshal

  2. buf 将包含编码数据。

    	switch val := v.(type) {	case uint8, int32, uint32, int64, uint64, bool:		if err := binary.Write(&buf, binary.LittleEndian, val); err != nil {			return nil, err
    		}

基本上,该功能唯一要做的就是检查类型v并选择适当的编码算法。在这里,我们使用binary.Write(来自标准encoding/binary包)对整数和布尔类型进行编码。

	case uint16:		if err := binary.Write(&buf, binary.BigEndian, val); err != nil {			return nil, err
		}

接下来,我们要编码端口号。端口号始终存储为uint16类型,并且始终使用big-endian字节顺序编码(与上述整数不同)。

	// const (
	//      commandLength          = 12
	//      magicAndChecksumLength = 4
	// )
	case [magicAndChecksumLength]byte:		if _, err := buf.Write(val[:]); err != nil {			return nil, err
		}
	case [commandLength]byte:		if _, err := buf.Write(val[:]); err != nil {			return nil, err
		}
	case []byte:		if _, err := buf.Write(val); err != nil {			return nil, err
		}

接下来,我们将对魔术字节,校验和,命令和字节切片进行编码。所有数组和字节片均相同地序列化:字节直接写入缓冲区,无需另外编码。

	case string:		if _, err := buf.Write([]byte(val)); err != nil {			return nil, err
        }

字符串将转换为字节片,然后直接写入缓冲区。

	case Marshaler:
		b, err := val.MarshalBinary()
		if err != nil {			return nil, err
		}
		if _, err := buf.Write(b); err != nil {			return nil, err
        }

这是一个有趣的部分:如果该类型实现了Marshaler接口,则调用MarshalBinary方法对其进行序列化。我们之前已经讨论过:我们需要针对某些类型的自定义序列化算法。而且Marshaler界面很简单,只要:

type Marshaler interface {
	MarshalBinary() ([]byte, error)}

Marshal功能的最后一部分

	default:		if reflect.ValueOf(v).Kind() == reflect.Struct {
			b, err := marshalStruct(v)
			if err != nil {				return nil, err
			}
			if _, err := buf.Write(b); err != nil {				return nil, err
			}
			break
		}
		return nil, fmt.Errorf("unsupported type %s", reflect.TypeOf(v).String())
	}
	return buf.Bytes(), nil}

由于struct不是类型,因此我们需要使用更高级的方法来检查是否v为结构。标准reflect包装在这里确实有帮助。

我们如何序列化结构?好的,结构是字段的集合,因此我们需要遍历所有字段,分别序列化每个字段,然后连接结果。这可以通过一个不错的小型函数来完成:

func marshalStruct(v interface{}) ([]byte, error) {	var buf bytes.Buffer
	vv := reflect.ValueOf(v)
	for i := 0; i < vv.NumField(); i++ {
		s, err := Marshal(reflect.Indirect(vv.Field(i)).Interface())
		if err != nil {
			f := reflect.TypeOf(v).Field(i).Name
			return nil, fmt.Errorf("failed to marshal field %s: %v", f, err)
		}
		if _, err := buf.Write(s); err != nil {			return nil, err
		}
	}
	return buf.Bytes(), nil}

同样,我们使用reflect包来获取结构中的字段数并分别获取每个字段。传递一个struct reflect.ValueOf将返回该结构本身,但作为一种reflect.Value类型,我们可以对其进行反思。

评论(0)问答(0)
请先登录或注册

请先登陆或注册

相关推荐

如何解释“我篡改了区块链”这个问题

区块链数据“全局一致”、“难以篡改”这两个特性已经广为人知,是区块链营造“信任”的基石。为了达到这两个效果,区块链的共识、同步、校验等技术细节足可大书特书,而本文要从“我篡改了区块链数据”讲起。“我篡......
Moon · 2020-07-03
1810阅读 · 0赞赏 · 0问答

漏洞分析 | 一千种死法之 智能合约函数调用错误

6.18期间正愁”千手观音“还不够剁,如果一觉醒来,突然发现爸妈的账户可以随便用了,前男友的钱因为权限设置错误而面对你以及他所有前女友开放了。不是天上掉馅饼,而是老天可能漏了个洞。我的是我的,你的还是......
Rooney · 2020-06-27
1332阅读 · 0赞赏 · 0问答

波卡启动 NPoS,几个知识点了解一下?

Polkadot 今日启动 NPoS(验证人选举),其技术方案也再次引发关注。关于 Polkadot 的几大技术知识点,我们都总结在此了:· Polkadot 网络,中继链、平行链和转接桥· XCMP......
多来A梦 · 2020-06-19
1732阅读 · 0赞赏 · 0问答

区块链 Go并发编程之Channel生产者-消费者模型及单向Channel

生产者与消费者关系在channel中生产者消费者关系可以简单的理解为:生产者和消费者一定是一一配对的状态存在先看一个简单的案例,生产者与消费者关系为1:2package mainimport......
· 2020-06-17
2755阅读 · 0赞赏 · 0问答

区块链 Go并发编程 之 管道 Channel

GO 管道 channel什么是管道管道是Go语言在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。channel是进程内的通讯方式,是......
Diva · 2020-06-17
2572阅读 · 0赞赏 · 0问答

区块链 GO 并发编程 之 主线程与子线程

基础概念什么是进程?进程:一个正在运行的程序一般是一个进程,一个进程可以包含多个线程 每个进程都有自己的独立的地址空间(内存空间),简单的理解一个运行程序为一个进程什么是线程线程:一条有序的CPU命令......
wind · 2020-06-16
2290阅读 · 0赞赏 · 0问答

4966

LK币

22

粉丝

120

笔记

感谢"区块链开发"

这篇精彩的笔记,目前已经帮助

  • 0
  • 1
  • 5
  • 5
  • 5
喜欢0
链客社群 加入

微博进入

商务合作>

广告投放>

公司名称:北京链客行科技有限公司

联系方式:010-67707199

ICP备案号:京ICP备18032136号

Copyright:链客区块链技术问答社区 版权所有

感谢您的提问,问题被社区永久收入以便新人查看。一定要记得采纳最佳答案哦!加油!

感谢您的善举,每一次解答会成为新人的灯塔,回答被采纳后获得20算力和相应的LK币奖励

您将赞赏给对方2LK币的奖励哦!感谢您的赞赏!

您将赞赏给对方2LK币的奖励哦!感谢您的赞赏!