建议和反馈

请填写你的反馈内容

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

2020-01-14 ·569次阅读 ·读完需要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)
请先登录或注册

请先登陆或注册

相关推荐

如何使用CGMiner挖比特币

虽然许多人发现简单地购买比特币更容易,但更多的人却发现,比特币挖矿是一件有趣且具有挑战性的事情。比特币挖矿既有趣又有利可图,你可能会认为,上手实际上更容易。在本文中我将解释如何使用GPU和CGMine......
区块链社区 · 2020-01-17
181阅读 · 0赞赏 · 0问答

session工作原理:如何使用、修改及删除等操作

session和Cookie之间存在某种关系,它们的作用有些相同,并且在一定程度上可以互换使用。session已集成到PHP中,是一种在用户浏览网站上的一系列页面或页面迭代时为用户存储和跟踪数据的一种......
社区菲菲 · 2020-01-16
340阅读 · 0赞赏 · 0问答

ubuntu安装golang的七个步骤

在Ubuntu上安装Go(Golang)可能很棘手。版本12.04和14.04的存储库已过时,并安装了较旧的Go版本。幸运的是,有一个名为Go Version Manager(gvm)的工具可以帮助安......
BTC · 2020-01-16
389阅读 · 0赞赏 · 0问答

Linux echo命令示例

Linux echo命令用于在屏幕上显示一行文本。这是通过将字符串回显到标准输出来完成的。Linux echo命令让我们看看Linux手册页如何描述echo命令。可以通过键入以下命令来访问手册页:回声......
波bobo · 2020-01-15
411阅读 · 0赞赏 · 0问答

GoProxy教程:作用、下载及安装

GoProxy是一种高性能的http代理,https代理,socks5代理,ss代理,websocket代理,tcp代理,udp代理,游戏盾,游戏代理。支持正向代理,反向代理,透明代理,Interne......
社区菲菲 · 2020-01-15
413阅读 · 0赞赏 · 0问答

如何使用Python和Flask轻松实现区块链

区块链的基本目的是基本上以难以伪造的格式存储时序交易数据。即使通过第三方引用余额,也几乎不可能伪造余额。该块包含如下数据:块:块号hash:块的哈希值prev_hash:上一个块的哈希值交易:交易数据......
区块链网 · 2020-01-15
453阅读 · 0赞赏 · 0问答

2414

LK币

22

粉丝

78

笔记

感谢"区块链开发"

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

  • 0
  • 0
  • 5
  • 6
  • 9
喜欢0
链客社群 加入

微博进入

商务合作>

广告投放>

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

联系方式:010-67707199

ICP备案号:京ICP备18032136号

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

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

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

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

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