准备工作:
- go语言中Channels的用法:在不同的Goroutine中运行的函数之间传递数据,可以使用Channel也称为通道。(Goroutine是协程,和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制)
- 关键字“go”。使用这个关键字使一个函数被并发的执行,如:
go func() { fmt.Println("666") }()
思路整理
首先明确一下我们期待的网络通信是什么样子的,简单点说就是单个节点能接收外部传来的数据并能改变内部数据的状态,又能将内部数据的最新状态广播出去,外部的其它节点都能收到广播的这样一个网络。下面我们具体拆分一下需要实现哪些功能:(我们用一台电脑上开多个终端来模拟多个节点)
- 接收处理其它节点广播信息
- 更新本节点区块数据
- 向其它节点发广播
- 需要一个变量来存放全网的公链 bcServer = make(chan []Block) //chan是表示Channels
- 通过开启一个定时器进行广播
- 开启TCP服务监听端口,看是否有其它节点向这个端口发消息
开启监听:
监听9000端口,如果有消息通过9000端口传过来,则接受消息,并交给handleConn这个函数去处理
server, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatal(err)
}
defer server.Close()
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
接收处理其它节点广播信息:
这个并发的函数中做了四个事情:
- 通过scanner扫描,持续接受TCP中发来的数据,
- 拿接受到的数据去创建新的区块
- 检查区块是否有效并更新本节点区块数据
- 把本地的链指给公链
go func() {
for scanner.Scan() {
content := scanner.Text()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], string(content))
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
}
bcServer <- Blockchain
io.WriteString(conn, "\n请输入任意内容:")
}
}()
定时器进行广播
把本地链数据转成json然后广播,mutex是互斥锁。
go func() {
for {
time.Sleep(30 * time.Second)
mutex.Lock()
output, err := json.Marshal(Blockchain)
if err != nil {
log.Fatal(err)
}
mutex.Unlock()
io.WriteString(conn, string(output))
}
}()
for _ = range bcServer {
spew.Dump(Blockchain)
}
过了30秒之后,收到广播,每个终端都输出了json数据,如图:
总结: