【libp2p-echo案例】

1.host

主机是在集群的顶部管理服务的抽象。它提供了一个清晰的接口,用于连接到给定远程对等节点上的服务。

代码示例:

go 复制代码
//创建一个默认是简单主机
host, err := libp2p.New()
//创建一个拥有各种配置的主机
host2, err := libp2p.New(
		// 使用随机生成的私钥来标识该主机
		libp2p.Identity(priv),
		// 多个监听地址
		libp2p.ListenAddrStrings(
			"/ip4/0.0.0.0/tcp/9000",      // 常规tcp连接
			"/ip4/0.0.0.0/udp/9000/quic", // 用于QUIC传输的UDP端点
		),
		// 支持TLS连接
		libp2p.Security(libp2ptls.ID, libp2ptls.New),
		// 支持noise连接
		libp2p.Security(noise.ID, noise.New),
		// 支持任何其他默认传输(TCP)
		libp2p.DefaultTransports,
		// 通过附加连接管理器来防止对等方具有太多连接。
		libp2p.ConnectionManager(connmgr),
		// 尝试使用uPNP为NAT主机打开端口。
		libp2p.NATPortMap(),
		// 允许此主机使用DHT查找其他主机
		libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
			idht, err = dht.New(ctx, h)
			return idht, err
		}),
		// 如果您想帮助其他对等方确定它们是否在NAT后面,
		// 您还可以启动AutoNAT的服务器端(AutoRelay已经运行客户端)
		// 此服务受到强烈限制,不应导致任何性能问题。
		libp2p.EnableNATService(),
	)

2. 简单的echo程序

此示例可以在监听模式或拨号模式下启动。

在监听模式下,它将等待在/echo/1.0.0协议上接收的传入连接。每当它接收到一个流时,它将在流上写入消息"Hello, world!"并关闭它。

在拨号模式下,节点将启动,连接到给定地址,打开到目标对等体的流,并在/echo/1.0.0协议上读取消息。

这是一个单次连接通信的例子,可以用来传递信息或文件。

2.1 main函数

在该函数中根据给定的命令行参数启动host作为发送者或接受者

go 复制代码
func main() {
	// 使用context包创建上下文,方便控制LibP2P节点的生命周期
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	// LibP2P代码使用golog记录消息。它们使用不同的字符串ID(例如"swarm")。
	// 我们可以通过以下方式控制所有记录器的详细程度:
	golog.SetAllLoggers(golog.LevelInfo) // 更改为INFO以获取额外信息
	// 从命令行解析选项
	listenF := flag.Int("l", 0, "等待传入连接的端口")
	targetF := flag.String("d", "", "要拨号的目标对等体")
	insecureF := flag.Bool("insecure", false, "使用不加密的连接")
	seedF := flag.Int64("seed", 0, "设置用于ID生成的随机种子")
	flag.Parse()
	if *listenF == 0 {
		log.Fatal("请使用 -l 提供要绑定的端口")
	}
	// 创建在给定多地址上侦听的主机
	ha, err := makeBasicHost(*listenF, *insecureF, *seedF)
	if err != nil {
		log.Fatal(err)
	}
	if *targetF == "" {
		// 如果未提供需要连接的目标对等体,则作为监听器启动
		startListener(ctx, ha, *listenF, *insecureF)
		// 运行直到被取消
		<-ctx.Done()
	} else {
		// 如果提供了目标对等体,则作为发送方启动
		runSender(ctx, ha, *targetF)
	}
}

2.2 makeBasicHost

makeBasicHost 创建一个具有随机对等体ID的LibP2P主机,监听给定的多地址。

go 复制代码
// 如果insecure为true,则不加密连接。
func makeBasicHost(listenPort int, insecure bool, randseed int64) (host.Host, error) {
	var r io.Reader
	if randseed == 0 {
		r = rand.Reader
	} else {
		r = mrand.New(mrand.NewSource(randseed))
	}
	// 为此主机生成密钥对。至少我们将用它来获得有效的主机ID。
	priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		return nil, err
	}
	opts := []libp2p.Option{
		libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)),
		libp2p.Identity(priv),
		libp2p.DisableRelay(),
	}
	if insecure {
		opts = append(opts, libp2p.NoSecurity)
	}
	return libp2p.New(opts...)
}

2.3 监听服务开启

将host设置为对某个端口进行监听。

有意思的是,在端口之上,还建立了"/echo/1.0.0"。

在传统的客户端-服务器模型中,一个端口通常只能由一个应用程序监听,因此可能会出现端口冲突问题。但在P2P网络中,通过协议ID,即使多个节点在同一端口上监听,它们也可以使用不同的协议进行通信,避免了直接的端口冲突。

go 复制代码
func startListener(ctx context.Context, ha host.Host, listenPort int, insecure bool) {
	// 获取主机的完整地址
	fullAddr := getHostAddress(ha)
	log.Printf("我是 %s\n", fullAddr)
	// 在主机A上设置一个流处理程序。/echo/1.0.0 是
	// 用户定义的协议名称,以及建立连接后的处理方式。
	ha.SetStreamHandler("/echo/1.0.0", func(s network.Stream) {
		log.Println("监听器收到新的流")
		if err := doEcho(s); err != nil {
			log.Println(err)
			s.Reset()
		} else {
			s.Close()
		}
	})
	log.Println("等待连接")
	if insecure {
		// 如果是不安全连接,则提醒在不同的终端上运行带有相应参数的命令
		log.Printf("现在在不同的终端上运行 \"./echo -l %d -d %s -insecure\"\n", listenPort+1, fullAddr)
	} else {
		// 如果是安全连接,则提醒在不同的终端上运行带有相应参数的命令
		log.Printf("现在在不同的终端上运行 \"./echo -l %d -d %s\"\n", listenPort+1, fullAddr)
	}
}

func getHostAddress(ha host.Host) string {
	// 构建一个主机的多地址
	hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", ha.ID()))
	// 获取ip/端口的部分
	addr := ha.Addrs()[0]
	// 现在我们可以建造一个完整的多地址,通过对多地址进行近一步的封装
	return addr.Encapsulate(hostAddr).String()
}

2.4 连接服务启动

go 复制代码
func runSender(ctx context.Context, ha host.Host, targetPeer string) {
	// 获取本主机的完整地址
	fullAddr := getHostAddress(ha)
	log.Printf("我是 %s\n", fullAddr)
	// 将目标对等体转换为Multiaddr。
	maddr, err := ma.NewMultiaddr(targetPeer)
	if err != nil {
		log.Println(err)
		return
	}
	// 从Multiaddr中提取对等体ID。
	info, err := peer.AddrInfoFromP2pAddr(maddr)
	if err != nil {
		log.Println(err)
		return
	}
	// 我们有了对等体ID和目标地址,因此将其添加到Peerstore
	// 以便本地的主机通过LibP2P知道如何与其联系
	ha.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
	log.Println("sender opening stream")
	
	// 从主机B到主机A通过ID来创建一个连接
	// 它应该由我们在主机A上设置的处理程序处,所以我们使用相同的 /echo/1.0.0 协议
	s, err := ha.NewStream(context.Background(), info.ID, "/echo/1.0.0")
	if err != nil {
		log.Println(err)
		return
	}
	//通过数据流s来发送数据
	log.Println("发送方发送问候")
	_, err = s.Write([]byte("Hello, world!\n"))
	if err != nil {
		log.Println(err)
		return
	}
	//通过数据流s来读取回复
	out, err := io.ReadAll(s)
	if err != nil {
		log.Println(err)
		return
	}

	log.Printf("读取回复:%q\n", out)
}

案例解读完毕。

相关推荐
IMPYLH3 分钟前
Linux 的 b2sum 命令
linux·运维·服务器·bash
celeste031025 分钟前
Redis Summary
linux·运维·服务器·redis·笔记
林姜泽樾1 小时前
Linux入门第十三章,chmod命令和权限控制信息
linux·运维·服务器·centos
m0_694845572 小时前
Oh My Zsh 使用指南:Zsh 终端配置与插件管理教程
服务器·前端·小程序·开源·github
huaqianzkh3 小时前
两个 ASP.NET Core Web API 模板核心区别
前端·后端·asp.net
阿常呓语3 小时前
Linux命令 date详解
linux·运维·服务器·linux command
虾..3 小时前
Linux HTTP服务器
linux·服务器·http
MrSYJ4 小时前
Netty异常传播机制
java·服务器·netty
REDcker5 小时前
Linux Core Dump 配置与分析指南
linux·运维·服务器
IMPYLH5 小时前
Linux 的 chcon 命令
linux·运维·服务器