【本文正在参加金石计划附加挑战赛------第二期命题】
IPC(Inter-Process Communication)即进程间通信,指的是不同进程之间交换数据或信息的方式与机制。在现代操作系统中,进程是彼此独立且受保护的。为了协作完成特定任务,进程需要一种安全且高效的方式来进行通信。IPC 机制正是为了解决这种需求。
进程间通信的目的包括:
-
数据共享:允许不同进程访问相同的数据(如数据文件或内存块)。
-
资源共享:实现进程间对同一资源(如设备、文件等)的共享。
-
事件通知:当某个进程完成特定任务时,它可以通知其他相关进程。
-
同步:确保多个进程在执行的过程中遵循特定的执行顺序,避免冲突。
-
远程过程调用:允许一个进程调用另一个进程的方法或函数。
在 Node.js 和 Golang 之间进行 IPC 通信,通常会使用以下几种方式,其中最常见的是基于标准输入输出流(标准 IO)和 Socket 套接字的通信。
标准输入输出流
在 Node.js 中,可以利用 child_process 模块来创建子进程与 Go 程序进行 IPC(进程间通信)。通过标准输入(stdin)、标准输出(stdout)、和标准错误输出(stderr)的管道,可以让 Node.js 主进程与 Go 子进程进行双向数据交换。
首先我们先编写如下代码:
go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Error reading input:", err)
return
}
// 去掉输入中的空白字符
input = strings.TrimSpace(input)
// 判断是否收到 "exit" 指令
if input == "exit" {
fmt.Println("Exiting...")
break
}
// 将接收到的数据处理后返回
fmt.Println("Received:", input)
}
}
在这个 Go 程序中:
-
bufio.NewReader(os.Stdin)
创建了一个从标准输入读取数据的缓冲区读取器。 -
通过
reader.ReadString('\n')
来等待从 Node.js 发送的数据。 -
当数据被读取后,程序会判断数据是否为
exit
,如果是则终止循环并退出程序。否则,会返回Received: <message>
。
我们将代码保存为 main.go
,稍后会通过 Node.js 主进程启动这个 Go 程序并与之通信。
接下来我们在 NodeJs 中使用 child_process 模块来启动 Go 程序,并通过管道与它进行数据交换。
js
const { spawn } = require("child_process");
const readline = require("readline");
// 启动 Go 程序作为子进程
const goProcess = spawn("go", ["run", "main.go"]);
// 监听 Go 程序的标准输出
goProcess.stdout.on("data", (data) => {
console.log(`Go 程序输出: ${data}`);
});
// 监听 Go 程序的标准错误输出(错误信息)
goProcess.stderr.on("data", (data) => {
console.error(`Go 程序错误: ${data}`);
});
// 使用 readline 接收用户输入
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// 引导用户输入消息
console.log("请输入要发送给 Go 程序的消息,输入 'exit' 退出:");
const sendMessage = (message) => {
goProcess.stdin.write(`${message}\n`);
};
// 监听用户输入
rl.on("line", (input) => {
if (input.trim() === "exit") {
console.log("退出通信...");
sendMessage("exit");
rl.close();
} else {
sendMessage(input);
}
});
// 监听 Go 程序退出事件
goProcess.on("close", (code) => {
console.log(`Go 程序已退出,退出代码: ${code}`);
});
这段代码启动了一个 Go 程序 main.go
作为子进程,并通过标准输入输出与它进行通信。NodeJs
会监听 Go 程序的输出和错误输出,将 Go 程序的响应打印在控制台。用户可以通过输入消息,将消息发送给 Go 程序并获得响应;输入 exit 后,程序会发送退出指令给 Go 程序,并关闭通信连接。
最终输出结果如下图所示:
Socket 套接字
使用 Socket 套接字是一种更通用的方式,适用于在不同语言编写的进程之间进行通信。可以选择使用 TCP、UDP 协议,或基于本地的 Unix 域套接字(在 Linux 和 macOS 系统上)。
主要流程如下所示:
-
启动 Golang Socket 服务器:Golang 程序会监听特定端口,等待客户端(Node.js)连接。连接后,服务器可以接收来自客户端的数据并发送响应。
-
Node.js 连接到 Socket 服务器:Node.js 作为客户端,连接到 Golang 服务器的 Socket 端口,通过套接字进行通信。
-
数据传输:双方可以实现双向通信,Node.js 可以向 Golang 发送数据,Golang 接收到数据后进行处理,并将结果返回给 Node.js。
以下是一个 Golang TCP 服务器的代码,它监听 8080 端口,等待 Node.js 客户端的连接,并处理数据。
go
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
func main() {
// 创建TCP监听器,在本地的8080端口上监听
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("启动服务器时发生错误:", err)
return
}
defer listener.Close() // 程序退出时关闭监听器
fmt.Println("Golang服务器正在监听8080端口...")
for {
// 接收客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("接受连接时发生错误:", err)
continue
}
// 使用goroutine并发处理每个连接
go handleConnection(conn)
}
}
// 处理连接的函数
func handleConnection(conn net.Conn) {
defer conn.Close() // 在函数结束时关闭连接
reader := bufio.NewReader(conn) // 创建一个读取器,用于读取客户端发送的数据
for {
// 从客户端读取数据,直到遇到换行符
message, err := reader.ReadString('\n')
if err != nil {
fmt.Println("客户端断开连接。")
return
}
// 去除读入字符串的前后空白字符
message = strings.TrimSpace(message)
fmt.Printf("从Node.js接收到的消息: %s\n", message)
// 向客户端发送数据
response := fmt.Sprintf("你好,Node.js! 你发送了: %s\n", message)
conn.Write([]byte(response)) // 将响应写入连接,发送给客户端
}
}
在此代码中:
-
服务器在 8080 端口上监听,等待客户端连接。
-
每当接收到一个连接时,使用 handleConnection 函数处理,通过 bufio.Reader 读取客户端发送的数据。
-
收到的数据经过处理后,发送回客户端。
下面是 Node.js 客户端的代码,通过 net 模块连接到 Golang 服务器。
js
const net = require("net");
// 创建 Socket 连接到 Golang 服务器
const client = net.createConnection({ port: 8080 }, () => {
console.log("已连接到 Golang 服务器");
// 向 Golang 服务器发送数据
client.write("来自 Node.js 的消息\n");
});
// 监听服务器返回的数据
client.on("data", (data) => {
console.log(`收到来自 Golang 的消息: ${data}`);
// 结束连接
client.end();
});
// 监听断开连接事件
client.on("end", () => {
console.log("已断开与服务器的连接");
});
// 监听错误事件
client.on("error", (err) => {
console.error("错误:", err);
});
在此代码中:
-
net.createConnection()用于创建 Socket 连接,连接到 Golang 服务器的 8080 端口。
-
client.write()用于发送数据到服务器。
-
client.on('data')用于接收服务器的响应数据。
-
client.on('end')监听连接结束事件,处理断开连接的逻辑。
先运行 Golang 服务器代码,启动 Socket 服务器。启动后,服务器会在端口 8080 上等待连接。
bash
go run main.go
运行 Node.js 客户端代码,启动客户端连接服务器,并进行通信。
最终输出结果如下图所示:
go 服务这边的控制台输出如下图所示:
接下来我们讲对这些逻辑进行一个详细的讲解:
-
TCP 连接:通过 TCP 协议建立连接后,数据可以双向传输。Node.js 作为客户端,连接到 Golang 服务器,双方通过相同的连接通道传递数据。
-
双向通信:Golang 服务器可以通过 conn.Write 发送数据给 Node.js,Node.js 通过 client.on('data')读取数据。
-
事件监听:Node.js 客户端监听多个事件,如 data 事件(接收数据)、end 事件(连接关闭)、error 事件(异常处理)等,以实现对通信状态的监控。
这种 Socket 套接字通信适用于数据量大、实时性高的 IPC 需求。通过这种方式,可以实现跨平台、跨语言的进程间通信。Node.js 作为客户端连接到 Golang 服务器,通过标准的 Socket 机制,实现了稳定、高效的 IPC 通信。
总结
在 Node.js 和 Golang 之间的 IPC 通信中,标准输入输出和 Socket 套接字是两种常见的方案。标准输入输出适用于本地的进程间通信,Node.js 可以通过 child_process 模块启动 Golang 子进程,双方通过标准输入输出流传输数据。这种方式实现简单,适合短期的数据交换,但在数据量较大时性能会有所限制。相比之下,Socket 套接字更适合需要长时间或跨网络的双向通信。Golang 可以作为服务器监听 TCP 端口,Node.js 作为客户端连接,通过 Socket 传输数据。这种方式支持持久连接,能更好地处理频繁的数据交换,适合实时性要求较高的场景。
最后再来提一下这两个开源项目,它们都是我们目前正在维护的开源项目:
如果你想参与进来开发或者想进群学习,可以添加我微信 yunmz777
,后面还会有很多需求,等这个项目完成之后还会有很多新的并且很有趣的开源项目等着你。