在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄

【本文正在参加金石计划附加挑战赛------第二期命题】

IPC(Inter-Process Communication)即进程间通信,指的是不同进程之间交换数据或信息的方式与机制。在现代操作系统中,进程是彼此独立且受保护的。为了协作完成特定任务,进程需要一种安全且高效的方式来进行通信。IPC 机制正是为了解决这种需求。

进程间通信的目的包括:

  1. 数据共享:允许不同进程访问相同的数据(如数据文件或内存块)。

  2. 资源共享:实现进程间对同一资源(如设备、文件等)的共享。

  3. 事件通知:当某个进程完成特定任务时,它可以通知其他相关进程。

  4. 同步:确保多个进程在执行的过程中遵循特定的执行顺序,避免冲突。

  5. 远程过程调用:允许一个进程调用另一个进程的方法或函数。

在 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 系统上)。

主要流程如下所示:

  1. 启动 Golang Socket 服务器:Golang 程序会监听特定端口,等待客户端(Node.js)连接。连接后,服务器可以接收来自客户端的数据并发送响应。

  2. Node.js 连接到 Socket 服务器:Node.js 作为客户端,连接到 Golang 服务器的 Socket 端口,通过套接字进行通信。

  3. 数据传输:双方可以实现双向通信,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)) // 将响应写入连接,发送给客户端
    }
}

在此代码中:

  1. 服务器在 8080 端口上监听,等待客户端连接。

  2. 每当接收到一个连接时,使用 handleConnection 函数处理,通过 bufio.Reader 读取客户端发送的数据。

  3. 收到的数据经过处理后,发送回客户端。

下面是 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);
});

在此代码中:

  1. net.createConnection()用于创建 Socket 连接,连接到 Golang 服务器的 8080 端口。

  2. client.write()用于发送数据到服务器。

  3. client.on('data')用于接收服务器的响应数据。

  4. client.on('end')监听连接结束事件,处理断开连接的逻辑。

先运行 Golang 服务器代码,启动 Socket 服务器。启动后,服务器会在端口 8080 上等待连接。

bash 复制代码
go run main.go

运行 Node.js 客户端代码,启动客户端连接服务器,并进行通信。

最终输出结果如下图所示:

go 服务这边的控制台输出如下图所示:

接下来我们讲对这些逻辑进行一个详细的讲解:

  1. TCP 连接:通过 TCP 协议建立连接后,数据可以双向传输。Node.js 作为客户端,连接到 Golang 服务器,双方通过相同的连接通道传递数据。

  2. 双向通信:Golang 服务器可以通过 conn.Write 发送数据给 Node.js,Node.js 通过 client.on('data')读取数据。

  3. 事件监听: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,后面还会有很多需求,等这个项目完成之后还会有很多新的并且很有趣的开源项目等着你。

相关推荐
Wh1teR0se2 小时前
[极客大挑战 2019]Secret File--详细解析
前端·web安全·网络安全
ZhaiMou3 小时前
HTML5拖拽API学习 托拽排序和可托拽课程表
前端·javascript·学习·html5
无忧无虑Coding3 小时前
pyinstall 打包Django程序
后端·python·django
求积分不加C4 小时前
Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程
java·spring boot·后端
枫叶_v4 小时前
【SpringBoot】26 实体映射工具(MapStruct)
java·spring boot·后端
2401_857617625 小时前
汽车资讯新趋势:Spring Boot技术解读
java·spring boot·后端
code_shenbing6 小时前
跨平台WPF框架Avalonia教程 三
前端·microsoft·ui·c#·wpf·跨平台·界面设计
小林学习编程6 小时前
从零开始理解Spring Security的认证与授权
java·后端·spring
写bug的羊羊6 小时前
Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
java·spring boot·后端
白臻6 小时前
使用element-plus el-table中使用el-image层级冲突table表格会覆盖预览的图片等问题
前端·vue.js·elementui