golang支持并发事件处理的嵌入式脚本语言插件ecal的使用

Golang支持并发事件处理的嵌入式脚本语言插件ECAL的使用

ECAL简介

ECAL Logo

ECAL是一种用于并发事件处理的ECA(事件条件动作)语言。ECAL可以使用由事件触发的规则定义基于事件的系统。ECAL旨在嵌入到其他软件中,提供一个易于使用的脚本语言,可以对外部事件做出反应。

主要特性

  • 简单直观的语法
  • 极简的基础语言(默认只支持写入日志)
  • 语言可以轻松扩展,通过自动生成到Go函数的桥接适配器或在标准库(stdlib)中添加自定义函数
  • 外部事件可以轻松推送到解释器中,用ECAL编写的脚本可以对这些事件做出反应
  • 简单但功能强大的基于事件的并发处理,支持优先级和控制流范围
  • 处理事件规则可以匹配事件状态,规则可以相互抑制

快速开始

基本使用

启动交互式会话:

./ecal

编写简单的一行语句并求值:

>>>a:=2;b:=a*4;a+b
10
>>>"Result is {{a+b}}"
Result is 10

示例项目

examples/fib目录中有2个ECAL文件:

lib.ecal

# Library for fib

/*
fib calculates the fibonacci series using recursion.
*/
func fib(n) {
    if (n <= 1) {
        return n
    }
    return fib(n-1) + fib(n-2)
}

fib.ecal

import "lib.ecal" as lib

for a in range(2, 20, 2) {
  log("fib({{a}}) = ", lib.fib(a))
}

运行程序:

sh run.sh

输出示例:

2000/01/01 12:12:01 fib(2) = 1
2000/01/01 12:12:01 fib(4) = 3
2000/01/01 12:12:01 fib(6) = 8
2000/01/01 12:12:01 fib(8) = 21
2000/01/01 12:12:01 fib(10) = 55
2000/01/01 12:12:01 fib(12) = 144
2000/01/01 12:12:02 fib(14) = 377
2000/01/01 12:12:02 fib(16) = 987
2000/01/01 12:12:02 fib(18) = 2584
2000/01/01 12:12:02 fib(20) = 6765

嵌入ECAL和使用事件处理

ECAL的主要目的是成为一种简单的多用途语言,可以嵌入到其他软件中:

  • 它具有最小化(相当通用)的语法
  • 默认情况下,语言只能通过返回值、注入事件或日志记录到达解释器外部
  • 外部系统可以通过事件与代码交互,这些事件可能在具有不同复杂性的接收器系统中处理
  • 标准库函数可以轻松创建,通过生成标准Go函数的代理代码或添加简单的直接函数对象

嵌入示例

ECAL解释器的核心是运行时提供者对象,它使用给定的记录器和导入定位器构造:

logger := util.NewStdOutLogger()
importLocator := &util.FileImportLocator{Root: "/somedir"}
rtp := interpreter.NewECALRuntimeProvider("Some Program Title", importLocator, logger)

解析ECAL代码为抽象语法树:

ast, err := parser.ParseWithRuntime("sourcefilename", code, rtp)

执行代码:

err = ast.Runtime.Validate()
vs := scope.NewScope(scope.GlobalScope)
res, err := ast.Runtime.Eval(vs, make(map[string]interface{}), threadId)

事件处理

如果要使用事件,需要先启动运行时提供者的处理器:

rtp.Processor.Start()

然后可以注入事件到解释器中:

monitor, err := rtp.Processor.AddEventAndWait(engine.NewEvent("MyEvent", []string{"foo", "bar", "myevent"}, map[interface{}]interface{}{
  "data1": 123,
  "data2": "123",
}), nil)

收集错误:

monitor.RootMonitor().AllErrors()

ECAL中处理事件的接收器示例:

sink mysink
  kindmatch [ "foo.bar.myevent" ],
{
  log("Got event: ", event)
}

sink mysink2
  kindmatch [ "foo.*.*" ],
{
  log("Got event: ", event)
}

在ECAL中使用Go插件

ECAL支持通过Go插件扩展标准库(stdlib)函数。此功能的目的是允许轻松扩展标准库,即使是平台相关的代码。

插件示例在examples/plugin目录中。插件.so文件可以用buildplugin.sh编译(Go编译器必须与编译解释器二进制文件的版本相同)。运行示例run.sh将使ECAL解释器在执行ECAL代码之前加载编译的插件。

ECAL插件函数必须符合以下接口:

/*
ECALPluginFunction models a callable function in ECAL which can be imported via a plugin.
*/
type ECALPluginFunction interface {

	/*
		Run executes this function with a given list of arguments.
	*/
	Run(args []interface{}) (interface{}, error)

	/*
	   DocString returns a descriptive text about this function.
	*/
	DocString() string
}

更多关于golang支持并发事件处理的嵌入式脚本语言插件ecal的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang支持并发事件处理的嵌入式脚本语言插件ecal的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 中使用 eCAL 进行并发事件处理的嵌入式脚本语言插件

eCAL (Efficient Communication Abstraction Layer) 是一个开源的中间件,专为高性能分布式系统设计。在 Golang 中使用 eCAL 可以很好地支持并发事件处理,虽然 eCAL 本身不是一个脚本语言,但我们可以结合 Golang 的插件系统和脚本引擎来实现类似功能。

eCAL 简介

eCAL 提供了发布/订阅模式的消息传递,支持多种语言包括 C++, C#, Python 和 Go。它的主要特点包括:

  • 低延迟进程间通信
  • 支持多播和共享内存传输
  • 自动服务发现
  • 支持多种序列化格式

在 Golang 中使用 eCAL

首先需要安装 eCAL 的 Go 绑定:

go get github.com/continental/ecal-go

基本使用示例

1. 发布者示例

package main

import (
	"fmt"
	"time"
	"github.com/continental/ecal-go"
)

func main() {
	// 初始化 eCAL
	ecal.Initialize([]string{}, "Go Publisher")
	defer ecal.Finalize()

	// 设置进程状态
	ecal.SetProcessState(1, 1, "I feel good")

	// 创建发布者
	pub, err := ecal.NewPublisher("topic1", "base", "string", 1)
	if err != nil {
		fmt.Println("Failed to create publisher:", err)
		return
	}
	defer pub.Destroy()

	// 发布消息
	count := 0
	for ecal.Ok() {
		msg := fmt.Sprintf("Hello eCAL %d", count)
		err := pub.Send([]byte(msg), -1)
		if err != nil {
			fmt.Println("Failed to send message:", err)
		} else {
			fmt.Println("Sent:", msg)
		}
		count++
		time.Sleep(500 * time.Millisecond)
	}
}

2. 订阅者示例

package main

import (
	"fmt"
	"github.com/continental/ecal-go"
)

func main() {
	// 初始化 eCAL
	ecal.Initialize([]string{}, "Go Subscriber")
	defer ecal.Finalize()

	// 创建订阅者
	sub, err := ecal.NewSubscriber("topic1", "base", "string")
	if err != nil {
		fmt.Println("Failed to create subscriber:", err)
		return
	}
	defer sub.Destroy()

	// 设置接收回调
	sub.SetCallback(func(topicName string, msg *ecal.ReceivedMessageData) {
		fmt.Printf("Received on %s: %s\n", topicName, string(msg.Payload))
	})

	// 保持运行
	for ecal.Ok() {
		// 主线程可以在这里做其他工作
		// 或者只是 sleep
		ecal.SleepMS(100)
	}
}

结合脚本引擎实现插件系统

要实现嵌入式脚本语言插件,可以结合 Go 的插件系统和脚本引擎如 goja (JavaScript) 或 gopher-lua (Lua)。以下是使用 goja 的示例:

1. 安装 goja

go get github.com/dop251/goja

2. 脚本插件系统示例

package main

import (
	"fmt"
	"github.com/continental/ecal-go"
	"github.com/dop251/goja"
	"sync"
)

// ScriptPlugin 表示一个脚本插件
type ScriptPlugin struct {
	vm     *goja.Runtime
	cancel chan struct{}
	wg     sync.WaitGroup
}

// NewScriptPlugin 创建新的脚本插件
func NewScriptPlugin(script string) (*ScriptPlugin, error) {
	vm := goja.New()
	plugin := &ScriptPlugin{
		vm:     vm,
		cancel: make(chan struct{}),
	}

	// 注册 eCAL 相关函数到 VM
	vm.Set("ecalPublish", func(topic, msg string) {
		pub, _ := ecal.NewPublisher(topic, "base", "string", 1)
		defer pub.Destroy()
		pub.Send([]byte(msg), -1)
	})

	// 执行脚本
	_, err := vm.RunString(script)
	if err != nil {
		return nil, err
	}

	return plugin, nil
}

// Run 运行插件
func (p *ScriptPlugin) Run() {
	p.wg.Add(1)
	go func() {
		defer p.wg.Done()
		for {
			select {
			case <-p.cancel:
				return
			default:
				// 调用脚本中的 main 函数
				if fn, ok := goja.AssertFunction(p.vm.Get("main")); ok {
					fn(goja.Undefined())
				}
			}
		}
	}()
}

// Stop 停止插件
func (p *ScriptPlugin) Stop() {
	close(p.cancel)
	p.wg.Wait()
}

func main() {
	// 初始化 eCAL
	ecal.Initialize([]string{}, "Script Plugin Host")
	defer ecal.Finalize()

	// 示例脚本
	script := `
		let count = 0;
		function main() {
			ecalPublish("script_topic", "Message from script " + count++);
		}
	`

	// 创建并运行插件
	plugin, err := NewScriptPlugin(script)
	if err != nil {
		fmt.Println("Error creating plugin:", err)
		return
	}
	defer plugin.Stop()

	plugin.Run()

	// 创建订阅者来验证
	sub, _ := ecal.NewSubscriber("script_topic", "base", "string")
	defer sub.Destroy()
	sub.SetCallback(func(topic string, msg *ecal.ReceivedMessageData) {
		fmt.Println("Received:", string(msg.Payload))
	})

	// 主循环
	for ecal.Ok() {
		ecal.SleepMS(500)
	}
}

并发事件处理

eCAL 本身支持并发,Go 的 goroutine 也能很好地处理并发事件。以下是并发处理多个订阅的示例:

package main

import (
	"fmt"
	"sync"
	"github.com/continental/ecal-go"
)

func main() {
	ecal.Initialize([]string{}, "Concurrent Subscriber")
	defer ecal.Finalize()

	var wg sync.WaitGroup
	topics := []string{"topic1", "topic2", "topic3"}

	for _, topic := range topics {
		wg.Add(1)
		go func(t string) {
			defer wg.Done()
			
			sub, err := ecal.NewSubscriber(t, "base", "string")
			if err != nil {
				fmt.Printf("Error creating subscriber for %s: %v\n", t, err)
				return
			}
			defer sub.Destroy()

			sub.SetCallback(func(topicName string, msg *ecal.ReceivedMessageData) {
				fmt.Printf("[%s] Received: %s\n", topicName, string(msg.Payload))
			})

			for ecal.Ok() {
				ecal.SleepMS(100)
			}
		}(topic)
	}

	wg.Wait()
}

总结

通过结合 eCAL 和 Go 的并发特性,可以构建高效的分布式事件处理系统。嵌入脚本语言插件可以提供动态扩展能力,而 Go 的 goroutine 和 channel 可以很好地管理并发事件流。

这种架构适用于需要高性能、分布式处理,同时又需要灵活性的场景,如物联网数据处理、实时分析系统等。

回到顶部