www.9778.comPHP 7.4 的 FFI 将支持更好地在 PHP 中使用 C 函数/数据结构

(文/开源中国)    

swoole_table保存Key值会增加内存占用,如table的size为100万,KEY值存储会增加64M内存占用

require 'ffi'module MyExtModule extend FFI::Library attach_function :printf, [:string], :voidendMyExtModule.printf("Hello FFIn")
  1. www.9778.comPHP 7.4 的 FFI 将支持更好地在 PHP 中使用 C 函数/数据结构。定义库,从库的头文件 hiredis/hredis.h 和 hiredis/read.h 复制/粘贴:preload-redis.h
  2. 对需要预加载的
    RemiRedis 类进行定义:preload-redis.inc
  3. 使用此类的测试脚本:www.9778.com,redis.php

问题修复修复启用消息队列后发生double-free问题重构定时器,修复aftertick定时器偶然出现的core dump的问题定时器使用最小堆数据结构,插入/删除时间复杂度为log(N)修复swoole_process::signal在PHP7下发生core dump的问题修复swoole_async_write在PHP7下发生core dump的问题移除特性移除未支持的特性相关历史遗留代码,如heartbeat_pingdispatch_key_type等移除swoole_server-addtimerswoole_server-deltimerswoole_server-gettimer移除swoole_timer_addswoole_timer_del移除swoole_serveronTimer事件移除task_worker_max配置及相关特性代码移除swoole_server-handler方法

require 'mkmf' create_makefile('printf')

即将在下月底发布的 PHP 7.4
将会引入一个有趣的新功能,那就是期待已久的
FFI(外部函数接口,Foreign Function
Interface)的支持
。此功能使得开发者可以在原生 PHP 代码中调用 C
定义的函数/变量/数据结构。

客户端增加原生异步MySQL客户端增加原生异步Redis客户端,基于Redis官方提供的hiredis库增加原生异步Http客户端增加原生异步WebSocket客户端支持重构底层swClient,异步TCP客户端实现放到swoole内核中增加swoole_client-reuse属性,SWOOLE_KEEP长连接模式下标识是否为复用的连接服务器端重构websocket服务器代码,底层与length_check协议复用相同的处理函数,增强稳定性增加Task进程对tick/after定时器的支持,底层基于高精度的setitimer+信号实现保存构造函数中传入的host、port参数到swoole_server对象属性增加多端口多协议的支持增加swoole_server-defer函数用于延时执行一些函数增加swoole_server-close强制切断连接的选项,设置第二个参数会true会清空发送队列并立即切断连接

用attach_fuction就可以把C语言标准库的printf函数绑定到ruby里面来了,后面的参数分别声明C函数的传入参数和返回值。如果需要引入其他C库,可以预先使用ffi_lib指定库的名称。(我使用ffi_lib未能绑定非标准库,有可能是FFI的bug?也有可能是我没有设置对?)有了FFI,在Ruby语言里面调用C库,就变成了一件异常轻松的事情,完全不需要ruby程序员再去吭哧吭哧啃C语言了,只要你手里捧着C库的API手册能看清楚函数的参数定义就够用了,然后你就可以直接在ruby里面随心所欲的调用它了,在调用之前,只需要用attach_function进行一次函数绑定声明即可。这对于整个ruby社区来说是一件非常棒的好消息。在FFI的源代码树的samples目录下面还有几个小例子,大家可以自己看看:作为一个对比,我们来看看传统的ruby扩展库是如何调用C库的吧。以上面的为例,如果我们希望在ruby里面使用C库的printf函数,那么要先用ruby
API的格式来写一段C代码去封装它:

输出摘要

$serv = new swoole_server("0.0.0.0", 9501);$port2 = $serv-listen('127.0.0.1', 9502, SWOOLE_SOCK_TCP);$port2-set(array( 'open_length_check' = true, 'package_length_type' = 'N', 'package_length_offset' = 0, //第N个字节是包长度的值 'package_body_offset' = 4, //第几个字节开始计算长度 'package_max_length' = 2000000, //协议最大长度));$port2-on('receive', function (swoole_server $serv, $fd, $from_id, $data) { echo "ServerPort2n";});$serv-on('connect', function ($serv, $fd, $from_id){ echo "[#".posix_getpid()."]tClient@[$fd:$from_id]: Connect.n";});$serv-on('receive', function (swoole_server $serv, $fd, $from_id, $data) { echo "[#".$serv-worker_id."]tClient[$fd]: $datan"; if ($serv-send($fd, "hellon") == false) { echo "errorn"; }});$serv-on('close', function ($serv, $fd, $from_id) { echo "[#".posix_getpid()."]tClient@[$fd:$from_id]: Close.n";});$serv-start();
gem install ffi

Redis 客户端

其他增加swoole_table对key值的存储,foreach遍历table时可以获取到key值更改swoole_table的key对比模式,从crc32比对改为直接进行字符串对比更新utlist.h库到1.9.9版本

当然你必须对ruby.h里面定义的ruby内部数据结构和ruby内部的函数调用有充分的了解。然后你要创建一个extconf.rb文件:

$ php74 -d ffi.preload=preload-redis.h -d opcache.preload=preload-redis.inc redis.php
...
+ RemiRedis::__construct(localhost, 6379)
+ RemiRedis::initFFI()
+ RemiRedis::del(foo)
int(1)
+ RemiRedis::get(foo)
NULL
+ RemiRedis::set(foo, 2019/10/23 12:45:03)
string(2) "OK"
+ RemiRedis::get(foo)
string(19) "2019/10/23 12:45:03"
+ RemiRedis::__destruct

多端口多协议示例:

#include ruby.h static VALUEdummy_printf(VALUE self, VALUE format, VALUE num){ int rlt = printf(RSTRING_PTR(format), NUM2INT(num)); return INT2FIX(rlt);} void Init_printf(){ /* register the method to the Kernel module */ rb_define_method(rb_mKernel, "dummy_printf", RUBY_METHOD_FUNC(dummy_printf), 2);}

下面摘录一个简单的例子:

PHP的高性能异步网络通信框架Swoole已发布 1.8.0
版本,此版本是一个里程碑式新版本,新增了多项新特性、多项核心功能优化以及问题修复、移除了无效的特性。更新内容如下:

使用FFI也很简单,比方说调用C语言标准库的printf函数,我们可以这样来写:

使用 PHP FFI 应该可以减少为 C 库/程序接口编写新 PHP
模块的需求,因为现在可以使用外部函数接口来完成了。如果你对 PHP 7.4 FFI
的示例代码感到好奇,负责 Fedora/RedHat 的 PHP 打包程序工程师 Remi Collet
撰写了一篇新的博客文章,其中概述了一些带有示例代码的用例。

Ruby解析器的性能一向被人垢病,很多性能敏感的程序,都必须用C语言来编写,然后使用Ruby去调用C库。但糟糕的是,Ruby调用C库并不是一件轻松的活,需要你对Ruby的内部数据结构有比较深入的了解,甚至需要你仔细阅读Ruby相关的源代码,然后用C语言编程作为黏合剂,用Ruby特有的C
API去给外部的C库增加一层封装。以上的过程,相当于你要打开Ruby解析器的内部进行API调用了,对程序员的要求很高。而且一旦Ruby内部的数据结构随着版本升级发生变动,你的这些黏合剂程序就必须重写。比方说现在Ruby
1.9出来以后,绝大部分Ruby的C扩展库统统无法正常编译。基于以上这些原因,给Ruby写C的扩展库一件非常痛苦的事情,而且也是Ruby社区程序员一直抱怨的问题之一。然而随着FFI的出现,我们即将告别这些痛苦的历史!FFI即Foreign
Function
Interface,外部函数调用接口,并非Ruby独有的概念,只不过因为Ruby扩展库带来的痛苦,使得Ruby的FFI显得格外迫切。FFI最早已经在Rubinius虚拟机平台上实现了,随后在JRuby上面也得以实现,而今天:2008年11月1日,在Ruby官方版本的解析器Ruby
1.8.6/1.8.7和1.9版本上也可以使用FFI了。安装FFI很简单:

如果希望了解有关 PHP 7.4 FFI
的更多信息,可以查看 PHP.net 上的文档及其基本示例。在发布
PHP 7.4 GA 前估计会经历多个 RC 版本,GA 预计将在 11 月 28 日左右发布。

运行这个文件:ruby
extconf.rb,会创建出来编译这段C代码的Makefile,然后你再编译和安装,最终这个共享链接库会被拷贝到你的ruby的本地扩展库目录下面。最后你才可以在ruby里面调用这个封装好的dummy_printf方法。另外,对于通过FFI去调用比较复杂的C库的时候,会涉及到不同语言之间的数据类型转换问题,关于数据类型的对照表,以及FFI使用更详细的介绍请看张驰原(他也是rmmseg的作者)的文章:

目前存在使用 C 或 PHP 编写的 Redis
客户端的各种实现,此示例演示了使用
FFI 来访问 hiredis 库的函数。

FFI 与预加载功能都将是 PHP 7.4 的重要新功能,FFI 还增强了 PHP FPM
systemd 服务、用于 OpenSSL 流的 TLS 1.3 以及许多其他更改。PHP 7.4 的 FFI
甚至可与其他语言的 FFI 实现相媲美。