0
点赞
收藏
分享

微信扫一扫

跨语言调用解决方案

捌柒陆壹 2022-09-01 阅读 70

跨语言调用就是要解决A语言写的函数想在B语言里调用的问题。

解决方案一种是将函数做成一个服务,通过进程间通信(IPC)或网络协议通信(RPC, RESTful等);另一种是直接通过 FFI 调用。

跨语言主要的问题:

  1. 如何在一个语言去调用另一个语言的函数?如何传递参数,如何返回值?
  2. 如果传递的是指针怎么办(或者说有大块内存要传递)。
  3. 如果调用是异步的要怎么办?

FFI

像C/C++/Rust这种,可以编译出动态库/静态库,导出C ABI的语言,都比较容易被其他语言直接通过FFI调用。C ABI(platform specific) 可以说是FFI(Foreign Function Interface)的事实标准了。几乎所有语言都会提供调用C语言库的方式。

不严谨的说

  • 如果你的程序可以导出C接口,就可以通过FFI被其他语言调用
  • 如果你的程序可以接入C接口,就可以调用其他语言写的库
语言 调用C接口的方式
Java JNI
Python C

但是FFI也是存在问题的

  1. 怎么把数据变成C类型?如Python中的数字是不定长的。一般来说需要一些约定,或者可以通过下面介绍的一些通用编码方式绕过。
  2. C接口注定不支持面向对象。如果需要提供面向对象的接口,还需要对C接口做进一步的封装。
  3. 如果小规模数据,只通过栈和寄存器传递就好。但是有时有大规模数据,通过堆来传输,这个时候就要关注两个语言内存模型的不同。不泄漏,自己回收自己。

RPC&IPC

我们考虑两个事情

  1. 怎么把参数传递过去
  2. 怎么对参数进行编码

两者的第二点是通用的,我们在数据编码一节介绍。来看第一点

IPC的方式:

  1. Pipes (Same Process) This allows flow of data in one direction only. Analogous to simplex systems (Keyboard). Data from the output is usually buffered until input process receives it which must have a common origin.
  2. Names Pipes (Different Processes) This is a pipe with a specific name it can be used in processes that don’t have a shared common process origin. E.g. is FIFO where the details written to a pipe is first named.
  3. Message Queuing This allows messages to be passed between processes using either a single queue or several message queue. This is managed by system kernel these messages are coordinated using an API.
  4. Semaphores This is used in solving problems associated with synchronization and to avoid race condition. These are integer values which are greater than or equal to 0.
  5. Shared memory This allows the interchange of data through a defined area of memory. Semaphore values have to be obtained before data can get access to shared memory.
  6. Sockets This method is mostly used to communicate over a network between a client and a server. It allows for a standard connection which is computer and OS independent. (这个基本也就是RPC了,Socket不在乎两端是否在一台机器上)

RPC的方式:通常使用HTTP,或者在TCP/QUIC上自定义应用层协议。

数据编码

RPC/IPC是逃不开对数据进行编码的,而用FFI的方式其实也可以用下面的方式进行编码,把所有对外暴露的功能都抽象成下面的形式,用command指定要调用的函数,bufferlength传递编码的数据

void handler(int command, char* buffer, size_t length);

JSON & XML

非常简单KV编码方式。很多语言都有相关的序列化库。

Protocol Buffer

Protocol buffer(下面简称pb)是谷歌开发的语言中立,平台无关,可扩展的序列化数据的格式(看到这里也许你想起了JSON/XML),可用于通信协议,数据存储等。Protocol buffers 很适合做数据存储或 RPC 数据交换格式

他有着以下优势:

  • 数据量更小 相比于JSON/XML,他更高效,压缩后的数据,比 JSON 小 50% 左右。
  • 安全性更好 需要拿到.proto文件才能解析数据
  • 操作更简单 只需要定义IDL就可以生成多种语言的序列化和反序列化代码
  • 兼容性很强 如果我们需要修改数据的格式,在满足一定条件下我们可以不更改之前的代码(也能跑,但是逻辑的正确性不保证)

定义

在 pb 中,所有结构化的数据都被称为 消息(message)。例如下面的例子定义了消息 helloworld,定义了两个字段,必要的int32类型的id编号为1,和可选的string类型的opt编号为7。

message helloworld 
{ 
   required int32 id = 1;   
   optional string opt = 7;
}

每个消息定义中的每个字段都有唯一的编号。这些字段编号用于标识消息二进制格式中的字段。

可以指定的最小字段编号为1,最大字段编号为2^29^-1(原因之后就会知道)。19000 到 19999(FieldDescriptor :: kFirstReservedNumber 到 FieldDescriptor :: kLastReservedNumber)也不能用,因为它们是为 Protocol Buffers实现保留的。

每个字段也有类型。

编码格式

pb将数据压缩成二进制格式。

Variant编码方式,它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。而日常使用的数字大都较小(视情况而定),所以能够节省空间,如果都是大数字反而会浪费空间。

Thrift

protobuf可以作为FFI/RPC的数据交换格式,thrift也是FFI/RPC的利器。

举报

相关推荐

0 条评论