前言
P2P(Peer-to-Peer)技术是一种点对点通信技术,通过将网络中的每个节点作为等级平等的节点,每个节点都可以作为服务端或客户端,从而可以实现分布式架构,大大提高了网络的可伸缩性和灵活性。
Deno 是一个新兴的 JavaScript/TypeScript 运行时,旨在为现代 Web 应用程序提供更好的开发体验,它的原生支持 ES6+,并且具有更强大的安全特性。本文将介绍如何在 Deno 中实现 P2P 通信。
运行 Demo
我们先来看一看如何运行一个简单的 P2P 示例。
创建两个节点
首先,我们需要创建至少两个 Deno 进程以模拟两个节点之间的通信。在本地终端窗口中用以下命令创建两个进程:
$ deno run --allow-net server.ts
$ deno run --allow-net client.ts
这里,我们创建了两个进程,一个用于监听客户端连接的服务器进程(server.ts
),一个用于与服务器建立连接的客户端进程(client.ts
)。
简单通信
在服务器进程中,您需要监听指定的端口,并处理从客户端发送的消息。以下是服务器代码的示例:
const listener = Deno.listen({ port: 8080 }); console.log("listen on 0.0.0.0:8080"); async function handleConn(conn: Deno.Conn) { console.log("new connection"); const buffer = new Uint8Array(1024); while (true) { const n = await conn.read(buffer); if (n == null) break; await conn.write(buffer.slice(0, n)); } conn.close(); } for await (const conn of listener) { handleConn(conn); }
在客户端进程中,您需要连接到服务器,并发送一些消息。以下是客户端代码:
const conn = await Deno.dial("tcp", "127.0.0.1:8080"); const encoder = new TextEncoder(); const decoder = new TextDecoder(); await conn.write(encoder.encode("hello")); const buffer = new Uint8Array(1024); await conn.read(buffer); console.log(decoder.decode(buffer)); conn.close();
上述代码中,客户端首先连接到服务器,然后发送 hello
消息。服务器端在接收到这条消息并写回之后,客户端读取其中的响应并输出。
在本地窗口中打开两个 Deno 进程时,您应该看到输出如下:
listen on 0.0.0.0:8080 new connection
然后输出如下:
hello
这表示客户端已向服务器发送了消息,并成功接收到了服务器发回的响应消息。
P2P 通信
与简单通信不同的是,我们需要在多个 Deno 进程之间建立 P2P 连接以进行更加复杂的通信。P2P 通信可以通过多个节点之间互相建立连接来实现,这需要某种中心化节点(例如服务器)来进行连接管理。
建立连接
首先,我们需要定义连接管理器来管理 Node 之间的连接。连接管理器应该将一个节点标识为“leader”节点,它应该负责管理所有其他节点之间的连接。以下是连接管理器的示例代码:
class ConnectionManager { #connections: Deno.Conn[] = []; constructor(public readonly address: string) {} async connect(address: string) { const conn = await Deno.dial("tcp", address); this.#connections.push(conn); console.log(`connected to ${address}`); } async listen() { const listener = Deno.listen({ port: 8080 }); for await (const conn of listener) { this.#connections.push(conn); console.log(`new connection from ${conn.remoteAddr}`); } } async broadcast(message: string) { const encoder = new TextEncoder(); const buffer = encoder.encode(message); for (const conn of this.#connections) { try { await conn.write(buffer); } catch (e) { console.error(e.message); this.#connections = this.#connections.filter((c) => c !== conn); } } } async close() { for (const conn of this.#connections) { await conn.close(); } } }
在代码中,connect()
方法建立 P2P 连接到指定地址的其他 Deno 进程,并在连接成功时存储连接。listen()
方法在当前 Deno 进程的端口上监听并等待新的 P2P 连接。broadcast()
方法广播消息到所有已知的连接。close()
方法则用于断开所有连接。
在这个例子中,我们假定 Port 8080 是客户端使用的默认端口。
实现 Leader 节点和 Peer 节点
现在我们已经定义了 P2P 连接管理器,我们可以通过创建 Leader 和 Peer 类来使用它创建 P2P 网络。
Leader 节点应该是一个带有所有其他 Peer 节点信息的中心节点。在这里,我们将为所有连接到 Leader 节点的 Peer 节点分配随机 ID。以下是 Leader 节点的示例代码:
const connectionManager = new ConnectionManager("0.0.0.0:8080"); await connectionManager.listen(); const peers: { [key: string]: Deno.Conn } = {}; async function handleConn(conn: Deno.Conn) { console.log(`new connection from ${conn.remoteAddr}`); const id = Math.random().toString(36).substring(2); peers[id] = conn; await conn.write(new TextEncoder().encode(id)); } for await (const conn of connectionManager) { handleConn(conn); }
在代码中,Leader 创建了一个 connectionManager
,并使用之前定义的 listen()
方法开始监听连接请求。然后,Leader 监听所有传入连接,并为新连接分配 ID。每个连接都将存储在全局 peers
对象中,其中 ID 是键,连接是值。
像这样使用 Math.random()
函数生成 ID 不是最好的方法,但它在这里可以很好地工作。在生产环境中,您应该使用更可靠的方法来生成唯一的 UUID。
Peer 节点应该连接到 Leader 节点,并等待其分配的 ID。以下是 Peer 节点的示例代码:
const conn = await Deno.dial("tcp", "127.0.0.1:8080"); const buffer = new Uint8Array(1024); const n = await conn.read(buffer); const id = new TextDecoder().decode(buffer.subarray(0, n)); console.log(`Connected to leader with ID ${id}`);
在代码中,Peer 首先连接到 Leader,然后等待 Leader 分配一个随机 ID。连接的 ID 将通过网络连接发送,我们等待其传输完毕并打印连接的 ID。
实现 P2P 通信
现在我们已经定义了 Leader 和 Peer 类,我们可以使用 ConnectionManager 和 Peer 和 Leader 类来实现 P2P 通信。
以下是向 ConnectionManager 中添加消息广播功能的 Peer 类:
class Peer { private readonly connectionManager: ConnectionManager; private connectionId?: string; constructor(connectionManager: ConnectionManager) { this.connectionManager = connectionManager; } async connect(address: string) { await this.connectionManager.connect(address); console.log(`connected to ${address}`); } async start() { const conn = await Deno.dial("tcp", "127.0.0.1:8080"); const buffer = new Uint8Array(1024); const n = await conn.read(buffer); this.connectionId = new TextDecoder().decode(buffer.subarray(0, n)); console.log(`Connected to leader with ID ${this.connectionId}`); } async broadcast(message: string) { await this.connectionManager.broadcast( `${this.connectionId}: ${message}\n`, ); } async close() { await this.connectionManager.close(); } }
在代码中,connect()
方法创建 P2P 连接到指定地址。start()
方法连接到 Leader,并等待分配的 Peer ID。broadcast()
方法广播一个带有对等节点 ID 的消息。close()
方法用于关闭连接。
以下是向 ConnectionManager 中添加消息广播功能的 Leader 类:
class Leader { private readonly connectionManager: ConnectionManager; constructor(connectionManager: ConnectionManager) { this.connectionManager = connectionManager; } async start() { await this.connectionManager.listen(); console.log(`listen on ${this.connectionManager.address}`); } async broadcast(message: string) { await this.connectionManager.broadcast(`Leader: ${message}\n`); } async close() { await this.connectionManager.close(); } }
在代码中,我们定义了一个 Leader
类,并重用了 ConnectionManager
。start()
方法开始监听连接,broadcast()
方法广播带有 Leader ID 的消息,close()
方法用于关闭连接。
我们已经定义了 P2P 通信的通用类,现在我们可以创建 Leader 和 Peer 的示例对象并开始测试。以下是使用 Leader 和 Peer 对象创建 P2P 通信的示例:
const connectionManager = new ConnectionManager("0.0.0.0:8080"); await Promise.all([ new Leader(connectionManager).start(), (async () => { const peer = new Peer(connectionManager); await peer.start(); await peer.connect("127.0.0.1:8080"); await peer.broadcast("Hi there!"); })(), ]);
在代码中,我们创建了一个 ConnectionManager 对象,并向其中添加一个 Leader 和一个 Peer 异步启动。
Peer 节点首先启动并连接到 Leader。接下来,它向 ConnectionManager 连接至 127.0.0.1:8080
,并发送一条 Hi there!
消息。
现在,我们已经在 Deno 中实现了 P2P 通信。请记住,在使用 Deno 进行 P2P 通信时必须使用 --allow-net
权限进行触发,这允许向指定的主机地址和端口上传输数据。
总结
本文介绍了如何在 Deno 中实现 P2P 通信。我们用 ConnectionManager 类模拟一组节点,用 Peer 和 Leader 类实现 P2P 通信。我们还介绍了如何使用 Deno 提供的安全特性以及如何使用 TypeScript 编写类型安全的 Node.js 应用程序。希望这篇文章对于您理解 P2P 通信以及如何在 Deno 中实现 P2P 通信有所帮助。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a5df35add4f0e0ffe6ef75