go-libp2p-example-chat学习

1.案例下载

https://github.com/libp2p/go-libp2p/tree/master/examples

2.chat案例

这段代码是一个简单的基于libp2p的P2P聊天应用程序的示例。它允许两个节点通过P2P连接进行聊天。前提是:

  1. 两者都有私有IP地址(同一网络)。
  2. 至少其中一个具有公共IP地址。

假设如果'A'和'B'在不同的网络上,主机'A'可能有或可能没有公共IP地址,但主机'B'一定有一个公共IP地址。

go 复制代码
//在一个命令行输入
`./chat -sp 3001` 
//在另一个命令后输入一下代码,<MULTIADDR_B>`是前一个与前节点通信的标识
`./chat -d <MULTIADDR_B>` 

运行后效果如下:

3.源码分析

3.1 main函数

go 复制代码
func main() {
	// 创建一个上下文和取消函数以进行 graceful shutdown
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	// 定义命令行参数
	sourcePort := flag.Int("sp", 0, "Source port number")
	dest := flag.String("d", "", "Destination multiaddr string")
	help := flag.Bool("help", false, "Display help")
	debug := flag.Bool("debug", false, "Debug generates the same node ID on every execution")
	// 解析命令行参数
	flag.Parse()
	// 如果传递了-help参数,显示帮助信息并退出
	if *help {
		fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n")
		fmt.Println("Usage: Run './chat -sp <SOURCE_PORT>' where <SOURCE_PORT> can be any port number.")
		fmt.Println("Now run './chat -d <MULTIADDR>' where <MULTIADDR> is multiaddress of previous listener host.")

		os.Exit(0)
	}
	// 如果启用调试模式,则使用常量随机源生成对等方ID。仅用于调试,默认情况下关闭。否则,它将使用 rand.Reader。
	var r io.Reader
	if *debug {
		// 使用端口号作为随机源。
		// 如果使用相同的端口号,这将在多次执行中始终生成相同的主机ID。
		// 在生产代码中永远不要这样做。
		r = mrand.New(mrand.NewSource(int64(*sourcePort)))
	} else {
		r = rand.Reader
	}
	// 创建libp2p主机
	h, err := makeHost(*sourcePort, r)
	if err != nil {
		log.Println(err)
		return
	}
	// 如果未指定目标地址,则作为监听者启动节点
	if *dest == "" {
		startPeer(ctx, h, handleStream)
	} else {
		// 如果指定了目标地址,表明这是一个主动连接的节点。需要创建线程以读取和写入数据
		rw, err := startPeerAndConnect(ctx, h, *dest)
		if err != nil {
			log.Println(err)
			return
		}
		// 创建线程以读取和写入数据
		go writeData(rw)
		go readData(rw)
	}
	// 永久等待
	select {}
}

3.2 makeHost

go 复制代码
// makeHost函数用于创建libp2p主机
func makeHost(port int, randomness io.Reader) (host.Host, error) {
	// 为此主机创建一个新的RSA密钥对,返回私钥、公钥、错误信息
	prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, randomness)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	// 0.0.0.0 将监听任何接口设备。
	sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port))

	// libp2p.New 用于构建一个新的libp2p主机。
	// 这里可以添加其他选项。
	return libp2p.New(
		libp2p.ListenAddrs(sourceMultiAddr),  // 设置主机监听的地址
		libp2p.Identity(prvKey),               // 设置主机的身份,即密钥对
	)
}

3.3 startPeer

go 复制代码
// startPeer函数用于启动作为监听者的节点
func startPeer(ctx context.Context, h host.Host, streamHandler network.StreamHandler) {
	// 设置一个函数作为流处理器。
	// 当节点连接时,此函数被调用,并启动一个带有该协议的流。
	// 仅适用于接收方,这里使用的streamHandler是代码中的handleStream
	h.SetStreamHandler("/chat/1.0.0", streamHandler)
	// 让我们从我们的监听多地址中获取实际的TCP端口,以防我们使用0(默认值;随机可用端口)。
	var port string
	for _, la := range h.Network().ListenAddresses() {
		if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil {
			port = p
			break
		}
	}
	if port == "" {
		log.Println("无法找到实际的本地端口")
		return
	}
	log.Printf("在另一个控制台中运行 './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s'\n", port, h.ID())
	log.Println("您也可以用公共IP替换 127.0.0.1。")
	log.Println("等待传入连接")
	log.Println()
}

3.4 startPeerAndConnect

go 复制代码
// startPeerAndConnect函数用于启动作为主动连接者的节点并连接到目标地址
func startPeerAndConnect(ctx context.Context, h host.Host, destination string) (*bufio.ReadWriter, error) {
	log.Println("此节点的多地址:")
	for _, la := range h.Addrs() {
		log.Printf(" - %v\n", la)
	}
	log.Println()
	// 将目标地址转换为multiaddr。
	maddr, err := multiaddr.NewMultiaddr(destination)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	// 从multiaddr中提取对等方ID。
	info, err := peer.AddrInfoFromP2pAddr(maddr)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	// 重要:在peerstore中添加目标对等方的对等多地址。
	// 这将在libp2p的连接和流创建过程中使用。
	// info.ID是一个peer的唯一标识,根据ID可以找到对应的多地址
	h.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
	// 与目标建立流。
	// 使用 'peerId' 从peerstore中获取目标对等方的多地址。
	s, err := h.NewStream(context.Background(), info.ID, "/chat/1.0.0")
	if err != nil {
		log.Println(err)
		return nil, err
	}
	log.Println("已建立到目标的连接")
	// 创建一个带有缓冲的流,以使读取和写入操作不会阻塞。
	rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
	return rw, nil
}

3.5 handleStream

go 复制代码
func handleStream(s network.Stream) {
	log.Println("Got a new stream!")
	// 创建一个不堵塞的读写缓冲流
	rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
	go readData(rw)
	go writeData(rw)
	// 流 's' 将保持打开状态,直到您关闭它(或另一侧关闭它)。
}

3.6 readData和writeData

go 复制代码
// readData函数用于从读写器中读取数据并在控制台上显示
func readData(rw *bufio.ReadWriter) {
	for {
		// 从读写器中读取字符串,直到遇到换行符 '\n'
		str, _ := rw.ReadString('\n')
		// 如果字符串为空,则退出循环
		if str == "" {
			return
		}
		// 如果字符串不是空行 '\n'
		if str != "\n" {
			// 控制台显示绿色文本:	\x1b[32m
			// 重置控制台文本颜色:	\x1b[0m
			fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
		}
	}
}

// writeData函数用于从标准输入读取数据并将其写入到读写器中
func writeData(rw *bufio.ReadWriter) {
	// 创建一个用于读取标准输入的读取器
	stdReader := bufio.NewReader(os.Stdin)
	for {
		// 提示符
		fmt.Print("> ")
		// 从标准输入读取数据,直到遇到换行符 '\n'
		sendData, err := stdReader.ReadString('\n')
		if err != nil {
			log.Println(err)
			return
		}
		// 将数据写入读写器,并刷新缓冲
		rw.WriteString(fmt.Sprintf("%s\n", sendData))
		rw.Flush()
	}
}
相关推荐
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode