go-libp2p-example-chat学习

news2024/11/24 19:30:56

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地址。

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

运行后效果如下:
在这里插入图片描述

3.源码分析

3.1 main函数

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

// 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

// 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

// 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

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

// 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()
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1305897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机视觉】Harris角点检测

角点指的是窗口延任意方向移动&#xff0c;都有很大变化量的点。 用数学公式表示为&#xff1a; 这个公式表示移动后的窗口&#xff0c;与移动前的窗口对应元素相减的平方&#xff0c;为每个像素点的权重 反映了如何移动窗口&#xff0c;以及移动窗口后的响应值 为了让 和 直…

海思越影系列3516DV500/3519DV500/3519AV200/SD3403平台的AI一体化工业相机设计思路

随着工业自动化的发展&#xff0c;生产线对机器视觉的数量要求越来越多&#xff0c;由于数量的增加&#xff0c;视觉系统占的空间也越来越大&#xff0c;给生产线的布局带来困扰。 另一方面随着视觉SOC的发展&#xff0c;越来越多的视觉SOC都逐渐带有一定的算力&#xff0c;一体…

头歌——HBase 开发:使用Java操作HBase

第1关&#xff1a;创建表 题目 任务描述 本关任务&#xff1a;使用Java代码在HBase中创建表。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.如何使用Java连接HBase数据库&#xff0c;2.如何使用Java代码在HBase中创建表。 如何使用Java连接HBase数据库…

玩转大数据15:常用的分类算法和聚类算法

前言 分类算法和聚类算法是数据挖掘和机器学习中的两种常见方法。它们的主要区别在于处理数据的方式和目标。 分类算法是在已知类别标签的数据集上训练的&#xff0c;用于预测新的数据点的类别。聚类算法则是在没有任何类别标签的情况下&#xff0c;通过分析数据点之间的相似性…

微信小程序改变checkbox大小

.weui-cell__hd {transform: scale(0.6,0.6);} <checkbox color"#447189" />

stm32 HAL库 发送接受 到了一定的字符串后就卡在.s文件中

问题介绍&#xff1a; 某个项目开发过程中&#xff0c;串口接收中断&#xff0c;开启了DMA数据传输&#xff0c;开启了DMA中断&#xff0c;开启DMA半满中断。然后程序运行的过程中&#xff0c;接收了一部分数据后就会卡在启动文件的DMA1_Ch4_7_DMA2_Ch3_5_IRQHandler 中断里。…

源码角度简单介绍LinkedList

LinkedList是一种常见的数据结构&#xff0c;但是大多数开发者并不了解其底层实现原理&#xff0c;以至于存在很多误解&#xff0c;在这篇文章中&#xff0c;将带大家一块深入剖析LinkedList的源码&#xff0c;并为你揭露它们背后的真相。首先想几个问题&#xff0c;例如&#…

抖音怎么设置自动点赞视频和评论呢?

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 你是否曾被抖音那令人眼花缭乱的短视频所吸引&#xff0c;却苦于无法自动点赞和评论而错过那些精彩的瞬间&#xff1f;现在&#xff0c;让我们一起揭开抖音自动点赞和评论的神秘面…

centos卸载mysql库全流程

&#xff08;1&#xff09;暂停服务 systemctl stop mysqld &#xff08;2&#xff09;查看所有的安装包&#xff0c;将其卸载 rpm -qa |grep mysql rpm -q ( or --query) options -a 查询所有安装的软件包 &#xff08;3&#xff09;使用yum卸载安装的mysql [rootbo /…

数据结构之优先级队列(堆)及top-k问题讲解

&#x1f495;"哪里会有人喜欢孤独&#xff0c;不过是不喜欢失望。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据结构之优先级队列(堆) 一.优先级队列 1.概念 我们已经学习过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff…

Flask维护者:李辉

Flask维护者&#xff1a;李辉&#xff0c; 最近看b站的flask相关&#xff0c;发现了这个视频&#xff1a;[PyCon China 2023] 濒危 Flask 扩展拯救计划 - 李辉_哔哩哔哩_bilibili 李辉讲他在维护flask之余&#xff0c;开发了apiflask这个依托flask的框架。GitHub - apiflask/a…

电商淘宝爬虫API与淘宝官方开放平台API的区别以及如何选择适合自己的API接口

随着数字化时代的到来&#xff0c;数据已经成为企业竞争力的重要因素。为了获取数据&#xff0c;企业或个人常常需要使用API接口。常见的API接口包括爬虫API和官方开放平台API。本文将详细介绍这两种API接口的区别以及如何选择适合自己的API接口。 一、爬虫API与官方开放平台A…

Docker部署Nacos集群并用nginx反向代理负载均衡

首先找到Nacos官网给的Github仓库&#xff0c;里面有docker compose可以快速启动Nacos集群。 文章目录 一. 脚本概况二. 自定义修改1. example/cluster-hostname.yaml2. example/.env3. env/mysql.env4. env/nacos-hostname.env 三、运行四、nginx反向代理&#xff0c;负载均衡…

1844_高边驱动以及低边驱动的选择

Grey 全部学习内容汇总&#xff1a;GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1844_高边驱动以及低边驱动的…

HarmonyOS鸿蒙应用开发——数据持久化Preferences

文章目录 数据持久化简述基本使用与封装测试用例参考 数据持久化简述 数据持久化就是将内存数据通过文件或者数据库的方式保存到设备中。HarmonyOS提供两两种持久化方案&#xff1a; Preferences&#xff1a;主要用于保存一些配置信息&#xff0c;是通过文本的形式存储的&…

装饰模式-设计模式

装饰模式 1.动机 一般有两种方式可以实现给一个类或对象增加行为&#xff1a; 继承机制&#xff0c;使用继承机制是给现有类添加功能的一种有效途径&#xff0c;通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的&#xff0c;用户不…

【Flink系列七】TableAPI和FlinkSQL初体验

Apache Flink 有两种关系型 API 来做流批统一处理&#xff1a;Table API 和 SQL Table API 是用于 Scala 和 Java 语言的查询API&#xff0c;它可以用一种非常直观的方式来组合使用选取、过滤、join 等关系型算子。 Flink SQL 是基于 Apache Calcite 来实现的标准 SQL。无论输…

C++ 11 异常

在C语言中&#xff0c;我们也有不少处理错误的方式&#xff0c;但是我们将这些处理错误的方式带到C 中&#xff0c;随着C不断更新的语法规则和内容下&#xff0c;这些C语言的处理方式还够用吗&#xff1f; 一.C语言的错误处理方式 C语言处理错误的方式大概有两种&#xff1a; …

环境安全之配置管理及配置安全设置指导

一、前言 IT运维过程中&#xff0c;配置的变更和管理是一件非常重要且必要的事&#xff0c;除了一般宏观层面的配置管理&#xff0c;还有应用配置参数的配置优化&#xff0c;本文手机整理常用应用组件配置项配置&#xff0c;尤其安全层面&#xff0c;以提供安全加固指导实践。…

mysqlclient安装失败

错误代码如下: 原因&#xff1a;缺少依赖项 从您所提供的错误日志中可以看出&#xff0c;尝试安装mysqlclient时出现了问题。错误的核心部分是&#xff1a; Can not find valid pkg-config name. Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually 这表…