symplely / uv-ffi
跨平台事件驱动异步I/O库`libuv`的外部函数接口(FFI)。
Requires
- php: >7.4
- ext-ffi: *
- symplely/zend-ffi: ~0.12.7
Conflicts
- ext-uv: *
README
这是一个PHP的外部函数接口 (FFI),用于libuv跨平台事件驱动异步I/O库。
这个libuv ffi实现基于PHP扩展ext-uv。除了uv_queue_work外,所有ext-uv 0.3.0的测试和函数都已实现。
- 在Windows、Linux和Apple macOS上,功能工作正常,支持PHP 7.4到8.2。
- 所有功能都依赖于zend-ffi。
ext-uv 0.3.0中uv_queue_work的实际线程功能已暂停。与FFI一起使用原生PThreads需要更多的调查,可能还需要PHP源代码的C开发。似乎有人已经开始做类似的事情https://github.com/mrsuh/php-pthreads。
更新:由于amphp/ext-uv的维护者停止了进展。请参阅symplely/ext-uv以获取更新,新的版本0.4.2实现了uv_queue_work()的线程功能,修复了其他错误和构建问题。大多数平台都提供了预构建的二进制文件,可在https://github.com/symplely/ext-uv/releases找到,不需要整个pecl构建系统,支持PHP版本7.4到8.2。
欢迎PR,请参阅[文档]和[贡献]。
uv-ffi的后续版本(超出ext-uv 0.3.0)将包括所有当前的libuv功能。
安装
有两种方式:composer require symplely/uv-ffi和:composer create-project symplely/uv-ffi .cdef/libuv
对于Windows和Apple macOS,这个包/存储库是自包含的,这意味着它有GitHub Actions构建libuv的二进制文件.dll & .dylib,并将其提交回存储库。其他平台将使用包含的libuv二进制版本。
create-project将设置不同的加载/安装区域。这个功能仍在进行中。
最低php.ini设置
extension=ffi extension=openssl extension=sockets zend_extension=opcache [opcache] ; Determines if Zend OPCache is enabled opcache.enable=1 ; Determines if Zend OPCache is enabled for the CLI version of PHP opcache.enable_cli=1 [ffi] ; FFI API restriction. Possible values: ; "preload" - enabled in CLI scripts and preloaded files (default) ; "false" - always disabled ; "true" - always enabled ffi.enable="true" ; List of headers files to preload, wildcard patterns allowed. `ffi.preload` has no effect on Windows. ; Replace `your-platform` with: windows, centos7, centos8+, macos, pi, ubuntu18.04, or ubuntu20.04 ; This feature is untested, since not enabled for Windows. ;ffi.preload=path/to/vendor/symplely/uv-ffi/headers/uv_your-platform_generated.h ;This feature is untested, since not enabled for Windows. ;opcache.preload==path/to/vendor/symplely/uv-ffi/preload.php
如何使用
- 文件UVFunctions.php中的函数是访问libuv功能的唯一方式。
以下是将uv book中的C代码转换为PHP代码的tcp echo server示例。大多数所需的C设置和清理代码都是自动完成的。
如果访问,将在控制台打印"Got a connection!"。
require 'vendor/autoload.php'; $loop = uv_default_loop(); define('DEFAULT_PORT', 7000); define('DEFAULT_BACKLOG', 128); $echo_write = function (UV $req, int $status) { if ($status) { printf("Write error %s\n", uv_strerror($status)); } print "Got a connection!\n"; }; $echo_read = function (UVStream $client, int $nRead, string $buf = null) use ($echo_write) { if ($nRead > 0) { uv_write($client, $buf, $echo_write); return; } if ($nRead < 0) { if ($nRead != UV::EOF) printf("Read error %s\n", uv_err_name($nRead)); uv_close($client); } }; $on_new_connection = function (UVStream $server, int $status) use ($echo_read, $loop) { if ($status < 0) { printf("New connection error %s\n", uv_strerror($status)); // error! return; } $client = uv_tcp_init($loop); if (uv_accept($server, $client) == 0) { uv_read_start($client, $echo_read); } else { uv_close($client); } }; $server = uv_tcp_init($loop); $addr = uv_ip4_addr("0.0.0.0", DEFAULT_PORT); uv_tcp_bind($server, $addr); $r = uv_listen($server, DEFAULT_BACKLOG, $on_new_connection); if ($r) { printf("Listen error %s\n", uv_strerror($r)); return 1; } uv_run($loop, UV::RUN_DEFAULT);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#define DEFAULT_PORT 7000
#define DEFAULT_BACKLOG 128
uv_loop_t *loop;
struct sockaddr_in addr;
typedef struct {
uv_write_t req;
uv_buf_t buf;
} write_req_t;
void free_write_req(uv_write_t *req) {
write_req_t *wr = (write_req_t*) req;
free(wr->buf.base);
free(wr);
}
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
buf->base = (char*) malloc(suggested_size);
buf->len = suggested_size;
}
void echo_write(uv_write_t *req, int status) {
if (status) {
fprintf(stderr, "Write error %s\n", uv_strerror(status));
}
free_write_req(req);
}
void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
if (nread > 0) {
write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
req->buf = uv_buf_init(buf->base, nread);
uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);
return;
}
if (nread < 0) {
if (nread != UV_EOF)
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
uv_close((uv_handle_t*) client, NULL);
}
free(buf->base);
}
void on_new_connection(uv_stream_t *server, int status) {
if (status < 0) {
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
// error!
return;
}
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
if (uv_accept(server, (uv_stream_t*) client) == 0) {
uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
}
else {
uv_close((uv_handle_t*) client, NULL);
}
}
int main() {
loop = uv_default_loop();
uv_tcp_t server;
uv_tcp_init(loop, &server);
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
错误处理
初始化函数*_init()或同步函数可能失败,将返回负数表示错误。可能失败的异步函数将向其回调传递状态参数。错误消息定义为UV::E*常量。
您可以使用uv_strerror(int)和uv_err_name(int)函数获取描述错误或错误名称的字符串。
I/O读取回调(例如文件和套接字)传递一个参数nread。如果nread小于0,表示发生错误(UV::EOF是文件结束错误,您可能希望以不同的方式处理)。
通常,函数和状态参数包含实际错误代码,成功时为0,错误时为负数。
文档
所有函数/方法/类都有其原始的Libuv 文档,签名嵌入在DOC-BLOCKS中。
为了更深入地理解使用,请参阅libuv简介。
以下函数存在于Windows中,但在Linux ubuntu 20.04及以上版本中不存在,可能需要重新检查。
void uv_library_shutdown(void);
int uv_pipe(uv_file fds[2], int read_flags, int write_flags);
int uv_socketpair(int type,
int protocol,
uv_os_sock_t socket_vector[2],
int flags0,
int flags1);
int uv_try_write2(uv_stream_t* handle,
const uv_buf_t bufs[],
unsigned int nbufs,
uv_stream_t* send_handle);
int uv_udp_using_recvmmsg(const uv_udp_t* handle);
uint64_t uv_timer_get_due_in(const uv_timer_t* handle);
unsigned int uv_available_parallelism(void);
uint64_t uv_metrics_idle_time(uv_loop_t* loop);
int uv_fs_get_system_error(const uv_fs_t*);
int uv_fs_lutime(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
double atime,
double mtime,
uv_fs_cb cb);
int uv_ip_name(const struct sockaddr* src, char* dst, size_t size);
// ubuntu 18.04
char *uv_strerror_r(int err, char *buf, size_t buflen);
char *uv_err_name_r(int err, char *buf, size_t buflen);
uv_handle_type uv_handle_get_type(const uv_handle_t *handle);
const char *uv_handle_type_name(uv_handle_type type);
uv_handle_type uv_handle_get_type(const uv_handle_t *handle);
const char *uv_handle_type_name(uv_handle_type type);
void *uv_handle_get_data(const uv_handle_t *handle);
uv_loop_t *uv_handle_get_loop(const uv_handle_t *handle);
void uv_handle_set_data(uv_handle_t *handle, void *data);
void *uv_req_get_data(const uv_req_t *req);
void uv_req_set_data(uv_req_t *req, void *data);
uv_req_type uv_req_get_type(const uv_req_t *req);
const char *uv_req_type_name(uv_req_type type);
size_t uv_stream_get_write_queue_size(const uv_stream_t *stream);
int uv_tcp_close_reset(uv_tcp_t *handle, uv_close_cb close_cb);
int uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr);
int uv_udp_getpeername(const uv_udp_t *handle,
struct sockaddr *name,
int *namelen);
int uv_udp_set_source_membership(uv_udp_t *handle,
const char *multicast_addr,
const char *interface_addr,
const char *source_addr,
uv_membership membership);
size_t uv_udp_get_send_queue_size(const uv_udp_t *handle);
size_t uv_udp_get_send_queue_count(const uv_udp_t *handle);
void uv_tty_set_vterm_state(uv_tty_vtermstate_t state);
int uv_tty_get_vterm_state(uv_tty_vtermstate_t *state);
uv_pid_t uv_process_get_pid(const uv_process_t *);
int uv_open_osfhandle(uv_os_fd_t os_fd);
int uv_os_getpriority(uv_pid_t pid, int *priority);
int uv_os_setpriority(uv_pid_t pid, int priority);
int uv_os_environ(uv_env_item_t **envitems, int *count);
void uv_os_free_environ(uv_env_item_t *envitems, int count);
int uv_os_uname(uv_utsname_t *buffer);
uv_fs_type uv_fs_get_type(const uv_fs_t *);
ssize_t uv_fs_get_result(const uv_fs_t *);
void *uv_fs_get_ptr(const uv_fs_t *);
const char *uv_fs_get_path(const uv_fs_t *);
uv_stat_t *uv_fs_get_statbuf(uv_fs_t *);
int uv_fs_mkstemp(uv_loop_t *loop,
uv_fs_t *req,
const char *tpl,
uv_fs_cb cb);
int uv_fs_opendir(uv_loop_t *loop,
uv_fs_t *req,
const char *path,
uv_fs_cb cb);
int uv_fs_readdir(uv_loop_t *loop,
uv_fs_t *req,
uv_dir_t *dir,
uv_fs_cb cb);
int uv_fs_closedir(uv_loop_t *loop,
uv_fs_t *req,
uv_dir_t *dir,
uv_fs_cb cb);
int uv_fs_lchown(uv_loop_t *loop,
uv_fs_t *req,
const char *path,
uv_uid_t uid,
uv_gid_t gid,
uv_fs_cb cb);
int uv_fs_statfs(uv_loop_t *loop,
uv_fs_t *req,
const char *path,
uv_fs_cb cb);
int uv_random(uv_loop_t *loop,
uv_random_t *req,
void *buf,
size_t buflen,
unsigned flags,
uv_random_cb cb);
uint64_t uv_get_constrained_memory(void);
void uv_sleep(unsigned int msec);
int uv_gettimeofday(uv_timeval64_t *tv);
int uv_thread_create_ex(uv_thread_t *tid,
const uv_thread_options_t *params,
uv_thread_cb entry,
void *arg);
void *uv_loop_get_data(const uv_loop_t *);
void uv_loop_set_data(uv_loop_t *, void *data);
贡献
鼓励和欢迎贡献;我总是很高兴在GitHub上收到反馈或拉取请求 :) 为错误和新功能创建GitHub问题,并对您感兴趣的问题进行评论。
许可
MIT许可证(MIT)。有关更多信息,请参阅许可文件。