This commit is contained in:
CismonX 2018-04-01 16:35:05 +08:00
commit 9f3a47020d
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
66 changed files with 5272 additions and 0 deletions

26
.travis.yml Normal file
View File

@ -0,0 +1,26 @@
sudo: required
dist: trusty
group: edge
language: php
php:
- 7.0
- 7.1
- 7.2
- nightly
before_install:
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
- sudo apt-get install -qq g++-6
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90
- sudo apt-get install libboost1.55-all-dev
script:
- phpize
- ./configure --enable-asio-strand
- make
after_success:
- make test

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017-2018 CismonX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# php-asio
[![Travis-CI](https://travis-ci.org/CismonX/php-asio.svg?branch=master)](https://travis-ci.org/CismonX/php-asio)
[![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg)](https://opensource.org/licenses/MIT)
## 1. About
This extension is a PHP wrapper for the Boost.Asio library, which provides a high-performance event-driven model for network I/O.
Now php-asio is **in development**, do not use it in production.
Bug reports and pull requests are welcome.
## 2. Documentation
See the [Wiki](https://github.com/CismonX/php-asio/wiki) page for documentation.
Signatures of classes and functions can be found within [stubs/](stubs/) directory, with PHPDoc.
There are also some test cases in the [tests/](tests/) directory.
## 3. TODO list
* Fix memory leak. (Priority: **high**)
* Multi-threading support. (Priority: **medium**)
* Add support for serial ports. (Priority: **low**)
* Add socket `onReadable()` `onWritable()` `onError()` methods (with Boost version 1.66 and above). (Priority: **low**)
* Add support for Windows. (Priority: **low**)

43
config.m4 Normal file
View File

@ -0,0 +1,43 @@
PHP_ARG_ENABLE(asio, for asio support,
[ --enable-asio Enable asio support ])
PHP_ARG_ENABLE(asio-coroutine, for coroutine support,
[ --disable-asio-coroutine Disable coroutine support ], yes, no)
PHP_ARG_ENABLE(asio-strand, for strand support,
[ --enable-asio-strand Enable strand support ], no, no)
PHP_ARG_ENABLE(asio-null-buffers, for null buffer support,
[ --enable-asio-null-buffers Enable null buffers ], no, no)
if test "$PHP_ASIO" != "no"; then
PHP_REQUIRE_CXX()
PHP_ASIO_SRC="src/php_asio.cpp \
src/service.cpp \
src/wrapped_handler.cpp \
src/future.cpp \
src/strand.cpp \
src/timer.cpp \
src/signal.cpp \
src/resolver.cpp \
src/socket.cpp \
src/acceptor.cpp \
src/stream_descriptor.cpp"
PHP_NEW_EXTENSION(asio, $PHP_ASIO_SRC, $ext_shared, cli, -std=c++14, yes)
if test "$PHP_ASIO_COROUTINE" != "no"; then
AC_DEFINE(ENABLE_COROUTINE, 1, [ ])
fi
if test "$PHP_ASIO_STRAND" != "no"; then
AC_DEFINE(ENABLE_STRAND, 1, [ ])
fi
if test "$PHP_ASIO_NULL_BUFFERS" != "no"; then
AC_DEFINE(ENABLE_NULL_BUFFERS, 1, [ ])
fi
PHP_ADD_LIBRARY(boost_system, 1, ASIO_SHARED_LIBADD)
PHP_ADD_LIBRARY(boost_filesystem, 1, ASIO_SHARED_LIBADD)
PHP_SUBST(ASIO_SHARED_LIBADD)
fi

171
src/acceptor.cpp Normal file
View File

@ -0,0 +1,171 @@
/**
* php-asio/acceptor.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "acceptor.hpp"
#include "future.hpp"
#include "io.hpp"
namespace asio
{
template <typename Protocol>
zval* acceptor<Protocol>::handler(const boost::system::error_code& error,
socket<Protocol>* const socket, zval* callback, zval* argument)
{
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_OBJ(&arguments[1], p3::to_zend_object(socket));
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_OBJ, p3::to_zend_object(socket));
}
/* {{{ proto int TcpAcceptor::accept(bool inet6);
* Open socket acceptor. */
template <>
P3_METHOD(tcp_acceptor, open)
{
zend_bool inet6;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_BOOL(inet6)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
acceptor_.open(inet6 ? tcp::v6() : tcp::v4(), ec);
RETVAL_EC(ec)
}
/* }}} */
/* {{{ proto int UnixAcceptor::accept(void);
* Open socket acceptor. */
template <>
P3_METHOD(unix_acceptor, open)
{
boost::system::error_code ec;
acceptor_.open(unix(), ec);
RETVAL_EC(ec)
}
/* }}} */
/* {{{ proto int TcpAcceptor::assign(bool inet6, int|resource fd);
* Assign an existing native socket to the acceptor. */
template <>
P3_METHOD(tcp_acceptor, assign)
{
PHP_ASIO_INET_ASSIGN(acceptor_, tcp);
}
/* }}} */
/* {{{ proto int UnixAcceptor::assign(int|resource fd);
* Assign an existing native socket to the acceptor. */
template <>
P3_METHOD(unix_acceptor, assign)
{
PHP_ASIO_LOCAL_ASSIGN(acceptor_, unix);
}
/* }}} */
/* {{{ proto int TcpAcceptor::bind(string address, int port);
* Bind the acceptor to the specified local endpoint. */
template <>
P3_METHOD(tcp_acceptor, bind)
{
zend_string* address;
zend_long port_num;
ZEND_PARSE_PARAMETERS_START(2, 2);
Z_PARAM_STR(address)
Z_PARAM_LONG(port_num)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
acceptor_.bind({ boost::asio::ip::address::from_string(ZSTR_VAL(address)),
static_cast<unsigned short>(port_num) }, ec);
RETVAL_EC(ec);
}
/* }}} */
/* {{{ proto int TcpAcceptor::bind(string path);
* Bind the acceptor to the specified local endpoint. */
template <>
P3_METHOD(unix_acceptor, bind)
{
zend_string* socket_path;
ZEND_PARSE_PARAMETERS_START(1, 1);
Z_PARAM_STR(socket_path)
ZEND_PARSE_PARAMETERS_END();
namespace fs = boost::filesystem;
if (fs::status(ZSTR_VAL(socket_path)).type() == fs::socket_file)
fs::remove(ZSTR_VAL(socket_path));
boost::system::error_code ec;
acceptor_.bind({ ZSTR_VAL(socket_path) }, ec);
RETVAL_EC(ec);
}
/* }}} */
/* {{{ proto int Acceptor::listen([int backlog]);
* Put the acceptor into the state where it may accept new connections. */
template <typename Protocol>
P3_METHOD(acceptor<Protocol>, listen)
{
zend_long backlog = 0;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(backlog)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
acceptor_.listen(backlog ? static_cast<int>(backlog) :
Protocol::socket::max_connections, ec);
RETVAL_EC(ec);
}
/* }}} */
/* {{{ proto int Acceptor::accept([callable callback], [mixed argument]);
* Asynchronously accept a new connection into a socket. */
template <typename Protocol>
P3_METHOD(acceptor<Protocol>, accept)
{
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_OBJ_ALLOC(accepted_socket, socket<Protocol>, io_service_);
PHP_ASIO_FUTURE_INIT();
auto asio_socket = p3::to_object<socket<Protocol>>(accepted_socket);
future->template on_resolve<NOARG>(boost::bind(
&acceptor::handler, this, _1, asio_socket, cb, args));
acceptor_.async_accept(asio_socket->get_socket(), STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG));
FUTURE_RETURN();
}
/* }}} */
/* {{{ proto int Acceptor::cancel(void);
* Cancel all asynchronous operations on this acceptor. */
template <typename Protocol>
P3_METHOD(acceptor<Protocol>, cancel)
{
boost::system::error_code ec;
RETVAL_EC(acceptor_.cancel(ec));
}
/* }}} */
/* {{{ proto int Acceptor::close(void);
* Stop the acceptor. */
template <typename Protocol>
P3_METHOD(acceptor<Protocol>, close)
{
boost::system::error_code ec;
RETVAL_EC(acceptor_.close(ec));
}
/* }}} */
template <typename Protocol>
zend_class_entry* acceptor<Protocol>::class_entry;
template <typename Protocol>
zend_object_handlers acceptor<Protocol>::handlers;
template class acceptor<tcp>;
template class acceptor<unix>;
}

52
src/acceptor.hpp Normal file
View File

@ -0,0 +1,52 @@
/**
* php-asio/acceptor.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
#include "socket.hpp"
namespace asio
{
/// Wrapper for Boost.Asio stream socket acceptor.
/// Provide TCP services.
template <typename Protocol>
class acceptor : public base
{
/// Boost.Asio acceptor instance.
typename Protocol::acceptor acceptor_;
/// Accept handler.
zval* handler(const boost::system::error_code& error,
socket<Protocol>* socket, zval* callback, zval* argument);
public:
/// Constructor.
explicit acceptor(
boost::asio::io_service& io_service
) : base(io_service), acceptor_(io_service) {}
P3_METHOD_DECLARE(open);
P3_METHOD_DECLARE(assign);
P3_METHOD_DECLARE(bind);
P3_METHOD_DECLARE(listen);
P3_METHOD_DECLARE(accept);
P3_METHOD_DECLARE(cancel);
P3_METHOD_DECLARE(close);
PHP_ASIO_CE_DECLARE();
};
using tcp_acceptor = acceptor<tcp>;
using unix_acceptor = acceptor<unix>;
}

58
src/base.hpp Normal file
View File

@ -0,0 +1,58 @@
/**
* php-asio/base.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#ifdef ZTS
#include <atomic>
#define HANDLER_COUNT_TYPE std::atomic<unsigned short>
#else
#define HANDLER_COUNT_TYPE unsigned short
#endif // ZTS
#include "common.hpp"
namespace asio
{
/// A base class for all I/O objects.
class base
{
protected:
/// IO service of this object.
boost::asio::io_service& io_service_;
/// Count of pending async handlers.
HANDLER_COUNT_TYPE handler_count_;
/// Constructor.
explicit base(boost::asio::io_service& io_service) : io_service_(io_service) {}
public:
/// Deleted default constructor.
base() = delete;
/// Default destructor.
~base() = default;
/// Deleted copy constructor.
base(const base&) = delete;
/// Deleted copy assignment operator.
base& operator=(const base&) = delete;
/// Increment handler count.
unsigned short handler_count_inc()
{
return ++handler_count_;
}
/// Decrement handler count.
unsigned short handler_count_dec()
{
return --handler_count_;
}
};
}

197
src/common.hpp Normal file
View File

@ -0,0 +1,197 @@
/**
* php-asio/common.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "p3.hpp"
#include <boost/asio.hpp>
#include <boost/bind.hpp>
// Compatible with PHP 7.3
#if PHP_VERSION_ID < 70300
#define GC_ADDREF(p) ++GC_REFCOUNT(p)
#define GC_DELREF(p) --GC_REFCOUNT(p)
#endif
#if PHP_VERSION_ID < 70115 || (PHP_VERSION_ID > 70200 && PHP_VERSION_ID < 70203)
#if PHP_VERSION_ID < 70100
#define _zend_wrong_parameters_count_error(throw, ...) zend_wrong_paramers_count_error(__VA_ARGS__)
#elif PHP_VERSION_ID < 70200
#define _zend_wrong_parameters_count_error(throw, ...) zend_wrong_parameters_count_error(__VA_ARGS__)
#else
#define _zend_wrong_parameters_count_error(...) zend_wrong_parameters_count_error(__VA_ARGS__)
#endif
// See https://externals.io/message/101364 for details.
#undef ZEND_PARSE_PARAMETERS_START_EX
#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \
const int _flags = (flags); \
int _min_num_args = (min_num_args); \
int _max_num_args = (max_num_args); \
int _num_args = EX_NUM_ARGS(); \
int _i; \
zval *_real_arg, *_arg = NULL; \
zend_expected_type _expected_type = Z_EXPECTED_LONG; \
char *_error = NULL; \
zend_bool _dummy; \
zend_bool _optional = 0; \
int error_code = ZPP_ERROR_OK; \
((void)_i); \
((void)_real_arg); \
((void)_arg); \
((void)_expected_type); \
((void)_error); \
((void)_dummy); \
((void)_optional); \
do { \
if (UNEXPECTED(_num_args < _min_num_args) || \
(UNEXPECTED(_num_args > _max_num_args) && \
EXPECTED(_max_num_args >= 0))) { \
if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \
_zend_wrong_parameters_count_error(_flags & ZEND_PARSE_PARAMS_THROW, \
_num_args, _min_num_args, _max_num_args); \
} \
error_code = ZPP_ERROR_FAILURE; \
break; \
} \
_i = 0; \
_real_arg = ZEND_CALL_ARG(execute_data, 0);
#endif
#define PHP_ASIO_OBJ_ALLOC(obj, type, arg) \
auto obj = p3::alloc_object<type>(type::class_entry, [this](type* ptr) { \
new(ptr) type(arg); \
})
#define PHP_ASIO_OBJ_DTOR(obj) GC_DELREF(p3::to_zend_object(obj))
#define ZVAL_ALLOC(name) name = static_cast<zval*>(emalloc(sizeof(zval)))
#define ZVAL_PTR_INIT(name) auto ZVAL_ALLOC(name)
#define ZVAL_INIT(name) zval name = {{ 0 }}
#define RETVAL_EC(ec) RETVAL_LONG(static_cast<zend_long>((ec).value()))
#define PHP_ASIO_ERROR(type, msg) php_error_docref(nullptr, type, msg)
#define PHP_ASIO_CE_DECLARE() \
static zend_class_entry* class_entry; \
static zend_object_handlers handlers
#define PHP_ASIO_CE_DEFINE(type) \
zend_class_entry* type::class_entry; \
zend_object_handlers type::handlers
// Handlers with one argument is treated as ones with two arguments.
#define NOARG int
#define ASYNC_HANDLER_SINGLE_ARG \
std::function<void(const boost::system::error_code&)>(boost::bind( \
&future::resolve<std::remove_pointer_t<decltype(this)>, NOARG>, \
future, boost::asio::placeholders::error, 0))
#define ASYNC_HANDLER_DOUBLE_ARG(obj_type) \
std::function<void(const boost::system::error_code&, obj_type)>(boost::bind( \
&future::resolve<std::remove_pointer_t<decltype(this)>, obj_type>, \
future, boost::asio::placeholders::error, _2))
// If you don't need coroutines, you can turn it off for better performance.
#ifdef ENABLE_COROUTINE
#define CORO_REGISTER(value) future::coroutine(value)
#define FUTURE_INIT() \
zend_object* obj; \
auto future = future::add(this, obj);
#define FUTURE_RETURN() RETVAL_OBJ(obj)
#define INIT_RETVAL() \
ZVAL_PTR_INIT(retval); \
ZVAL_NULL(retval)
#define PASS_RETVAL retval
#else
#define CORO_REGISTER(value)
#define FUTURE_INIT() auto future = future::add(this)
#define FUTURE_RETURN()
#define INIT_RETVAL() ZVAL_INIT(retval)
#define PASS_RETVAL &retval
#endif // ENABLE_COROUTINE
#define CORO_RETURN_NULL() \
ZVAL_PTR_INIT(retval); \
ZVAL_NULL(retval); \
return retval
#define CORO_RETURN(type, value) \
ZVAL_PTR_INIT(retval); \
type(retval, value); \
return retval
// If you don't need multi-threading support for I/O objects, you can disable Strand for better performance.
#if defined(ENABLE_STRAND) && !defined(ZTS)
#undef ENABLE_STRAND
#endif
#ifdef ENABLE_STRAND
#define STRAND_UNWRAP() \
callback = future->handle_strand(callback); \
if (future->get_strand()) \
ZVAL_COPY_VALUE(cb, callback); \
else
#define STRAND_RESOLVE(arg) future->get_strand() ? future->get_strand()->wrap(arg) : arg
#else
#define STRAND_UNWRAP()
#define STRAND_RESOLVE(arg) arg
#endif // ENABLE_STRAND
#define PHP_ASIO_INVOKE_CALLBACK_START(argc) \
const auto _argc = argc; \
if (callback && zend_is_callable(callback, 0, nullptr)) { \
zval arguments[_argc] = {{{ 0 }}}; \
ZVAL_OBJ(&arguments[0], p3::to_zend_object(this));
#define PHP_ASIO_INVOKE_CALLBACK() \
ZVAL_LONG(&arguments[_argc - 2], static_cast<zend_long>(error.value())); \
if (argument) \
ZVAL_COPY(&arguments[_argc - 1], argument); \
else \
ZVAL_NULL(&arguments[_argc - 1]); \
INIT_RETVAL(); \
call_user_function(CG(function_table), nullptr, callback, PASS_RETVAL, _argc, arguments)
#define PHP_ASIO_INVOKE_CALLBACK_END() \
CORO_REGISTER(retval); \
zval_ptr_dtor(callback); \
efree(callback); \
} \
if (argument) { \
zval_ptr_dtor(argument); \
efree(argument); \
}
#define PHP_ASIO_INC_HANDLER_COUNT() \
if (handler_count_inc() == 1) \
GC_ADDREF(p3::to_zend_object(this))
#define PHP_ASIO_DEC_HANDLER_COUNT() \
if (handler_count_dec() == 0) \
PHP_ASIO_OBJ_DTOR(this)
// To ensure the callback and the extra arg is still alive when async operation resolves,
// We shall allocate new memory on the heap.
#define PHP_ASIO_FUTURE_INIT() \
PHP_ASIO_INC_HANDLER_COUNT(); \
FUTURE_INIT(); \
zval* cb = nullptr; \
if (callback) { \
ZVAL_ALLOC(cb); \
STRAND_UNWRAP() \
ZVAL_COPY(cb, callback); \
} \
zval* args = nullptr; \
if (argument) { \
ZVAL_ALLOC(args); \
ZVAL_COPY(args, argument); \
}
using boost::asio::ip::tcp;
using boost::asio::ip::udp;
using unix = boost::asio::local::stream_protocol;
using udg = boost::asio::local::datagram_protocol;

133
src/future.cpp Normal file
View File

@ -0,0 +1,133 @@
/**
* php-asio/future.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "future.hpp"
#include "generator.hpp"
#include "timer.hpp"
#include "signal.hpp"
#include "resolver.hpp"
#include "socket.hpp"
#include "acceptor.hpp"
#include "stream_descriptor.hpp"
namespace asio
{
future* future::add(
void* io_object
#ifdef ENABLE_COROUTINE
, zend_object*& obj)
{
obj = p3::alloc_object<future>(class_entry,
[io_object](future* ptr) {
new(ptr) future(io_object);
});
GC_ADDREF(obj);
return p3::to_object<future>(obj);
#else
) {
return new future(io_object);
#endif // ENABLE_COROUTINE
}
template <typename T>
void future::on_resolve(const ASYNC_CALLBACK(T)&& callback)
{
callback_ = new ASYNC_CALLBACK(T)(std::move(callback));
}
template <typename V, typename T>
void future::resolve(const boost::system::error_code& ec, T arg)
{
auto callback = static_cast<ASYNC_CALLBACK(T)*>(callback_);
send_ = (*callback)(ec, arg);
#ifdef ENABLE_COROUTINE
if (yield_) {
last_error_ = static_cast<int64_t>(ec.value());
generator_send(reinterpret_cast<zend_generator*>(Z_OBJ_P(generator_)), send_);
coroutine(generator_);
}
#endif // ENABLE_COROUTINE
zval_ptr_dtor(send_);
efree(send_);
delete callback;
auto io_object = static_cast<V*>(io_object_);
if (io_object->handler_count_dec() == 0)
PHP_ASIO_OBJ_DTOR(io_object);
#ifdef ENABLE_STRAND
if (strand_ && strand_->handler_count_dec() == 0)
PHP_ASIO_OBJ_DTOR(strand_);
#endif // ENABLE_STRAND
#ifdef ENABLE_COROUTINE
PHP_ASIO_OBJ_DTOR(this);
#else
delete this;
#endif // ENABLE_COROUTINE
}
#ifdef ENABLE_STRAND
zval* future::handle_strand(zval* callable)
{
if (callable && Z_TYPE_P(callable) == IS_OBJECT &&
instanceof_function(Z_OBJCE_P(callable), wrapped_handler::class_entry)) {
const auto wrapped_hander = p3::to_object<wrapped_handler>(callable);
strand_ = wrapped_hander->strand_;
return wrapped_hander->callback_;
}
return callable;
}
#endif // ENABLE_STRAND
#ifdef ENABLE_COROUTINE
void future::coroutine(zval* value)
{
if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), zend_ce_generator)) {
const auto generator = reinterpret_cast<zend_generator*>(Z_OBJ_P(value));
if (generator_valid(generator)) {
const auto ret = generator_current(generator);
if (ret && instanceof_function(Z_OBJCE_P(ret), class_entry)) {
const auto future = p3::to_object<asio::future>(ret);
future->generator_ = value;
future->yield_ = true;
return;
}
PHP_ASIO_ERROR(E_WARNING, "Invalid yield value. Future expected.");
}
}
zval_ptr_dtor(value);
efree(value);
}
P3_METHOD(future, lastError)
{
RETVAL_LONG(last_error_)
}
thread_local int64_t future::last_error_ = 0;
PHP_ASIO_CE_DEFINE(future);
#endif // ENABLE_COROUTINE
template void future::on_resolve(const ASYNC_CALLBACK(int)&&);
template void future::on_resolve(const ASYNC_CALLBACK(size_t)&&);
template void future::on_resolve(const ASYNC_CALLBACK(tcp::resolver::iterator)&&);
template void future::on_resolve(const ASYNC_CALLBACK(udp::resolver::iterator)&&);
template void future::resolve<timer>(const boost::system::error_code&, int);
template void future::resolve<signal>(const boost::system::error_code&, int);
template void future::resolve<resolver<tcp>>(
const boost::system::error_code&, tcp::resolver::iterator);
template void future::resolve<resolver<udp>>(
const boost::system::error_code&, udp::resolver::iterator);
template void future::resolve<socket<tcp>>(const boost::system::error_code&, int);
template void future::resolve<socket<tcp>>(const boost::system::error_code&, size_t);
template void future::resolve<socket<unix>>(const boost::system::error_code&, int);
template void future::resolve<socket<unix>>(const boost::system::error_code&, size_t);
template void future::resolve<socket<udp>>(const boost::system::error_code&, size_t);
template void future::resolve<socket<udg>>(const boost::system::error_code&, size_t);
template void future::resolve<acceptor<tcp>>(const boost::system::error_code&, int);
template void future::resolve<acceptor<unix>>(const boost::system::error_code&, int);
template void future::resolve<stream_descriptor>(const boost::system::error_code&, size_t);
}

95
src/future.hpp Normal file
View File

@ -0,0 +1,95 @@
/**
* php-asio/future.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "wrapped_handler.hpp"
#define ASYNC_CALLBACK(type) std::function<zval*(const boost::system::error_code&, type)>
namespace asio
{
/// Class Future.
/// When an asynchronous operation completes, its Future will be resolved.
/// And the corresponding coroutine will resume (if Future was yielded by a Generator).
class future
{
/// Handler callback of the async operation.
void* callback_ = nullptr;
/// Pointer to the I/O object which created this Future.
void* io_object_;
#ifdef ENABLE_COROUTINE
/// Last error code emitted by yielded async operations of this thread.
static thread_local int64_t last_error_;
/// Generator instance which yielded this Future.
zval* generator_ = nullptr;
/// Whether this future is yielded by a Generator.
bool yield_ = false;
#endif // ENABLE_COROUTINE
/// Value which will be sent back to the Generator.
zval* send_ = nullptr;
#ifdef ENABLE_STRAND
/// Pointer to Strand which wrapped this Future.
strand* strand_ = nullptr;
#endif // ENABLE_STRAND
/// Constructor.
explicit future(void* io_object) : io_object_(io_object) {}
public:
/// Create a new Future instance.
static future* add(
void* io_object
#ifdef ENABLE_COROUTINE
, zend_object*& obj
#endif // ENABLE_COROUTINE
);
/// Deleted default constructor.
explicit future() = delete;
/// Deleted copy constructor.
future(const future&) = delete;
/// Deleted copy assignment operator.
future& operator=(const future&) = delete;
/// Set future resolver callback.
template <typename T>
void on_resolve(const ASYNC_CALLBACK(T)&& callback);
/// Resolve the Future upon operation completion.
template <typename V, typename T>
void resolve(const boost::system::error_code& ec, T arg);
#ifdef ENABLE_STRAND
zval* handle_strand(zval* callable);
/// Get the pointer to the strand which wrapped this Future.
boost::asio::strand* get_strand() const
{
return strand_ ? strand_->implmentation() : nullptr;
}
#endif // ENABLE_STRAND
#ifdef ENABLE_COROUTINE
/// Attempt to start/resume a coroutine with a PHP Generator.
static void coroutine(zval* value);
/// Get last error emitted by handler callback within yielded Future.
static P3_METHOD_DECLARE(lastError);
PHP_ASIO_CE_DECLARE();
#endif // ENABLE_COROUTINE
};
}

63
src/generator.hpp Normal file
View File

@ -0,0 +1,63 @@
/**
* php-asio/generator.hpp
*
* Several functions copied from zend_generators.c,
* which provides PHP's generator functionalities.
* Used by Future when coroutine is enabled.
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include <zend_generators.h>
#ifdef ENABLE_COROUTINE
namespace asio
{
/// Ensure that the generator is initialized.
inline void zend_generator_ensure_initialized(zend_generator* generator)
{
if (UNEXPECTED(Z_TYPE(generator->value) == IS_UNDEF) &&
EXPECTED(generator->execute_data) &&
EXPECTED(generator->node.parent == nullptr)) {
generator->flags |= ZEND_GENERATOR_DO_INIT;
zend_generator_resume(generator);
generator->flags &= ~ZEND_GENERATOR_DO_INIT;
generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
}
}
/// Check whether the generator is valid.
inline bool generator_valid(zend_generator* generator)
{
zend_generator_ensure_initialized(generator);
zend_generator_get_current(generator);
return EXPECTED(generator->execute_data != nullptr);
}
/// Get current yield value of the generator.
inline zval* generator_current(zend_generator* generator)
{
zend_generator_ensure_initialized(generator);
const auto root = zend_generator_get_current(generator);
if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->value) != IS_UNDEF))
return &root->value;
return nullptr;
}
/// Send a value to the generator.
inline void generator_send(zend_generator* generator, zval* value)
{
zend_generator_ensure_initialized(generator);
if (UNEXPECTED(!generator->execute_data))
return;
const auto root = zend_generator_get_current(generator);
if (root->send_target)
ZVAL_COPY(root->send_target, value);
zend_generator_resume(generator);
}
}
#endif // ENABLE_COROUTINE

178
src/io.hpp Normal file
View File

@ -0,0 +1,178 @@
/**
* php-asio/io.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#define PHP_ASIO_INET_ASSIGN(obj, p) \
zend_bool inet6; \
zval* fd; \
ZEND_PARSE_PARAMETERS_START(2, 2) \
Z_PARAM_BOOL(inet6) \
Z_PARAM_ZVAL(fd) \
ZEND_PARSE_PARAMETERS_END(); \
boost::system::error_code ec; \
const auto protocol = inet6 ? p::v6() : p::v4(); \
if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG)) \
(obj).assign(protocol, Z_LVAL_P(fd), ec); \
else \
(obj).assign(protocol, resource_to_fd(fd), ec); \
RETVAL_EC(ec)
#define PHP_ASIO_LOCAL_ASSIGN(obj, p) \
zval* fd; \
ZEND_PARSE_PARAMETERS_START(1, 1) \
Z_PARAM_ZVAL(fd) \
ZEND_PARSE_PARAMETERS_END(); \
boost::system::error_code ec; \
if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG)) \
(obj).assign(p(), Z_LVAL_P(fd), ec); \
else \
(obj).assign(p(), resource_to_fd(fd), ec); \
RETVAL_EC(ec)
#if defined(ENABLE_NULL_BUFFERS) && BOOST_VERSION >= 106600
// Null buffers are deprecated as of Boost 1.66.
// Method `async_wait()` on sockets and stream descriptors is preferred.
#undef ENABLE_NULL_BUFFERS
#endif
#ifdef ENABLE_NULL_BUFFERS
#define PHP_ASIO_BUFFER_LEN_VALIDATE() \
if (UNEXPECTED(length < 0)) { \
PHP_ASIO_ERROR(E_WARNING, "Non-negative integer expected."); \
RETURN_NULL(); \
}
#define PHP_ASIO_EMPTY_READ_BUFFER length == 0 ? ZSTR_EMPTY_ALLOC() :
#define PHP_ASIO_ON_READABLE(obj) \
if (length == 0) \
if (read_some) \
(obj).async_read_some(boost::asio::null_buffers(), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else \
async_read(obj, boost::asio::null_buffers(), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else
#define PHP_ASIO_EMPTY_WRITE_BUFFER ZSTR_LEN(data) == 0 ? nullptr :
#define PHP_ASIO_ON_WRITABLE(obj) \
if (ZSTR_LEN(data) == 0) \
if (write_some) \
(obj).async_write_some(boost::asio::null_buffers(), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else \
async_write(obj, boost::asio::null_buffers(), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else
#else
#define PHP_ASIO_BUFFER_LEN_VALIDATE() \
if (UNEXPECTED(length <= 0)) { \
PHP_ASIO_ERROR(E_WARNING, "Positive integer expected."); \
RETURN_NULL(); \
}
#define PHP_ASIO_EMPTY_READ_BUFFER
#define PHP_ASIO_ON_READABLE(obj)
#define PHP_ASIO_EMPTY_WRITE_BUFFER
#define PHP_ASIO_ON_WRITABLE(obj)
#endif // ENABLE_NULL_BUFFERS
#define PHP_ASIO_READ(type, obj) \
zend_long length; \
zend_bool read_some = 1; \
zval* callback = nullptr; \
zval* argument = nullptr; \
ZEND_PARSE_PARAMETERS_START(1, 4) \
Z_PARAM_LONG(length) \
Z_PARAM_OPTIONAL \
Z_PARAM_BOOL(read_some) \
Z_PARAM_ZVAL(callback) \
Z_PARAM_ZVAL(argument) \
ZEND_PARSE_PARAMETERS_END(); \
PHP_ASIO_BUFFER_LEN_VALIDATE(); \
auto buffer_container = PHP_ASIO_EMPTY_READ_BUFFER \
zend_string_alloc(static_cast<size_t>(length), 0); \
PHP_ASIO_FUTURE_INIT(); \
future->template on_resolve<size_t>(boost::bind(&type::read_handler, \
this, _1, _2, buffer_container, cb, args)); \
PHP_ASIO_ON_READABLE(obj) \
if (read_some) \
(obj).async_read_some(mutable_buffer(buffer_container), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else \
async_read(obj, mutable_buffer(buffer_container), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
FUTURE_RETURN()
#define PHP_ASIO_WRITE(type, obj) \
zend_string* data; \
zend_bool write_some = 0; \
zval* callback = nullptr; \
zval* argument = nullptr; \
ZEND_PARSE_PARAMETERS_START(1, 4) \
Z_PARAM_STR(data) \
Z_PARAM_OPTIONAL \
Z_PARAM_BOOL(write_some) \
Z_PARAM_ZVAL(callback) \
Z_PARAM_ZVAL(argument) \
ZEND_PARSE_PARAMETERS_END(); \
auto buffer_container = PHP_ASIO_EMPTY_WRITE_BUFFER \
zend_string_copy(data); \
PHP_ASIO_FUTURE_INIT(); \
future->template on_resolve<size_t>(boost::bind(&type::write_handler, \
this, _1, _2, buffer_container, cb, args)); \
PHP_ASIO_ON_WRITABLE(obj) \
if (write_some) \
(obj).async_write_some(mutable_buffer(buffer_container), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
else \
async_write(obj, mutable_buffer(buffer_container), \
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \
FUTURE_RETURN()
namespace asio
{
/// Wrap a zend string into const buffer.
inline auto const_buffer(const zend_string* str)
{
return boost::asio::const_buffers_1(boost::asio::const_buffer(
ZSTR_LEN(str) ? ZSTR_VAL(str) : nullptr, ZSTR_LEN(str)
));
}
/// Wrap a zend string into mutable buffer.
inline auto mutable_buffer(zend_string* str)
{
return boost::asio::mutable_buffers_1(boost::asio::mutable_buffer(
ZSTR_LEN(str) ? ZSTR_VAL(str) : nullptr, ZSTR_LEN(str)
));
}
/// Extract a valid poll fd from a PHP resource.
inline int resource_to_fd(zval* resource)
{
// The following code is copied from php-uv.
// See https://github.com/bwoebi/php-uv/blob/master/php_uv.c#L401
if (UNEXPECTED(Z_TYPE_P(resource) != IS_RESOURCE))
return -1;
const auto stream = static_cast<php_stream*>(
zend_fetch_resource_ex(resource, nullptr, php_file_le_stream()));
if (stream == nullptr)
return -1;
if (stream->wrapper && !strcmp(stream->wrapper->wops->label, "PHP") &&
(!stream->orig_path ||
strncmp(stream->orig_path, "php://std", sizeof "php://std" - 1) &&
strncmp(stream->orig_path, "php://fd", sizeof "php://fd" - 1)))
return -1;
auto fd = -1;
if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL,
reinterpret_cast<void**>(&fd), 1) == SUCCESS && fd >= 0) {
if (stream->wrapper && !strcmp(stream->wrapper->wops->label, "plainfile"))
return -1;
return fd;
}
return -1;
}
}

130
src/p3.hpp Normal file
View File

@ -0,0 +1,130 @@
/**
* php-asio/p3.hpp
*
* This header is a simple helper for wrapping C++ classes,
* which is borrowed from https://github.com/phplang/p3.
* The casting/cloning/comparing functionalities are removed,
* because they are not needed by php-asio.
*/
#pragma once
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <php.h>
#include <zend_exceptions.h>
#include <new>
#include <type_traits>
#define P3_METHOD_DECLARE(name) \
void zim_##name(INTERNAL_FUNCTION_PARAMETERS)
#define P3_METHOD(cls, name) \
void cls::zim_##name(INTERNAL_FUNCTION_PARAMETERS)
#define P3_ME(cls, name, meth, arginfo, flags) \
ZEND_FENTRY(name, [](INTERNAL_FUNCTION_PARAMETERS) { \
::p3::to_object<cls>(getThis())->zim_##meth(INTERNAL_FUNCTION_PARAM_PASSTHRU); \
}, arginfo, flags)
#define P3_ME_D(cls, meth, arginfo, flags) \
P3_ME(cls, meth, meth, arginfo, flags)
#define P3_STATIC_ME(cls, meth, arginfo, flags) \
ZEND_FENTRY(meth, &cls::zim_##meth, arginfo, flags | ZEND_ACC_STATIC)
#define P3_ABSTRACT_ME(name, arginfo) \
PHP_ABSTRACT_ME("", name, arginfo)
namespace p3 {
/// Native object to Zend object.
template <class T>
zend_object* to_zend_object(T* obj)
{
return reinterpret_cast<zend_object*>(obj + 1);
}
/// Zend object to native object.
template <class T>
T* to_object(zend_object* obj)
{
return reinterpret_cast<T*>(obj) - 1;
}
/// Zval to native object.
template <class T>
T* to_object(zval* obj)
{
return reinterpret_cast<T*>(Z_OBJ_P(obj)) - 1;
}
/// Allocate new object.
template <class T, typename InitFunc>
zend_object* alloc_object(zend_class_entry* ce, InitFunc init)
{
auto ptr = reinterpret_cast<T*>(ecalloc(1, sizeof(T) +
sizeof(zend_object) + zend_object_properties_size(ce)));
init(ptr);
auto zobj = to_zend_object(ptr);
zend_object_std_init(zobj, ce);
zobj->handlers = &T::handlers;
return zobj;
}
/// Allocate new object with default constructor.
template <class T>
typename std::enable_if<std::is_constructible<T>::value, zend_object*>::type
create_object(zend_class_entry* ce)
{
return alloc_object<T>(ce, [](T* ptr) {
new(ptr) T();
});
}
template <class T>
typename std::enable_if<!std::is_constructible<T>::value, zend_object*>::type
create_object(zend_class_entry* ce)
{
assert(false);
return nullptr;
}
/// Destroy an object.
template <class T>
void dtor_object(zend_object *obj)
{
zend_object_std_dtor(obj);
to_object<T>(obj)->~T();
}
/// Fail to create object if there's no default constructor.
inline zend_object* create_object_fail(zend_class_entry* ce) {
php_error_docref(nullptr, E_ERROR,
"%s should not be directly instantiated.", ZSTR_VAL(ce->name));
return zend_objects_new(ce);
}
template <class T>
zend_class_entry* class_init(const char* name, const zend_function_entry* methods)
{
zend_class_entry ce;
INIT_CLASS_ENTRY_EX(ce, name, strlen(name), methods);
T::class_entry = zend_register_internal_class(&ce);
T::class_entry->create_object =
std::is_constructible<T>::value ? create_object<T> : create_object_fail;
memcpy(&T::handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
T::handlers.offset = sizeof(T);
T::handlers.free_obj = dtor_object<T>;
T::handlers.clone_obj = nullptr;
return T::class_entry;
}
inline zend_class_entry* interface_init(const char* name, const zend_function_entry* methods)
{
zend_class_entry ce;
INIT_CLASS_ENTRY_EX(ce, name, strlen(name), methods);
return zend_register_internal_interface(&ce);
}
}

519
src/php_asio.cpp Normal file
View File

@ -0,0 +1,519 @@
/**
* php-asio/php_asio.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include <php.h>
#include <ext/standard/info.h>
#include "php_asio.hpp"
#include "p3.hpp"
#include "service.hpp"
#include "wrapped_handler.hpp"
/* {{{ arg_info */
ZEND_BEGIN_ARG_INFO(service_run_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(service_run_one_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(service_poll_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(service_poll_one_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(service_dispatch_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
#ifdef ENABLE_STRAND
ZEND_BEGIN_ARG_INFO(strand_dispatch_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
#endif // ENABLE_STRAND
ZEND_BEGIN_ARG_INFO(strand_wrap_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(timer_expires_from_now_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, duration, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(timer_expires_at_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, timestamp, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(timer_wait_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(signal_add_arginfo, 0)
ZEND_ARG_VARIADIC_INFO(0, sig_num)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(signal_remove_arginfo, 0)
ZEND_ARG_VARIADIC_INFO(0, sig_num)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(signal_wait_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(resolver_resolve_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, service, IS_STRING, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(socket_available_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(socket_at_mark_arginfo, 0)
ZEND_ARG_INFO(1, ec)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(inet_socket_open_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, inet6, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(inet_socket_bind_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(inet_socket_assign_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, inet6, _IS_BOOL, 0)
ZEND_ARG_INFO(0, native_handle)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(local_socket_assign_arginfo, 0)
ZEND_ARG_INFO(0, native_handle)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(stream_socket_read_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, read_some, _IS_BOOL, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(stream_socket_write_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, write_some, _IS_BOOL, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(local_socket_bind_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(datagram_socket_recv_from_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(tcp_socket_connect_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(unix_socket_connect_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(udp_socket_send_to_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(udg_socket_send_to_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(acceptor_listen_arginfo, 0)
ZEND_ARG_TYPE_INFO(0, backlog, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(acceptor_accept_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, callback, 0)
ZEND_ARG_INFO(0, argument)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(stream_descriptor_assign_arginfo, 0)
ZEND_ARG_INFO(0, native_handle)
ZEND_END_ARG_INFO()
/* }}} */
/* {{{ function_entry */
static zend_function_entry service_methods[] = {
P3_ME_D(asio::service, addTimer, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addSignal, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addTcpResolver, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addUdpResolver, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addTcpSocket, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addUdpSocket, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addUnixSocket, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addUdgSocket, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addTcpAcceptor, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addUnixAcceptor, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, addStreamDescriptor, nullptr, ZEND_ACC_PUBLIC)
#ifdef ENABLE_STRAND
P3_ME_D(asio::service, addStrand, nullptr, ZEND_ACC_PUBLIC)
#endif // ENABLE_STRAND
P3_ME_D(asio::service, run, service_run_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, runOne, service_run_one_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, poll, service_poll_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, pollOne, service_poll_one_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, stop, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, reset, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, stopped, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, post, service_dispatch_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, dispatch, service_dispatch_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, forkPrepare, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, forkParent, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::service, forkChild, nullptr, ZEND_ACC_PUBLIC)
#ifdef ENABLE_COROUTINE
P3_STATIC_ME(asio::future, lastError, nullptr, ZEND_ACC_PUBLIC)
#endif // ENABLE_COROUTINE
PHP_FE_END
};
#ifdef ENABLE_STRAND
static zend_function_entry strand_methods[] = {
P3_ME_D(asio::strand, dispatch, strand_dispatch_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::strand, post, strand_dispatch_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::strand, runningInThisThread, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::strand, wrap, strand_wrap_arginfo, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry wrapped_handler_methods[] = {
P3_ME_D(asio::wrapped_handler, __invoke, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
#endif // ENABLE_STRAND
static zend_function_entry io_object_method[] = {
P3_ABSTRACT_ME(cancel, nullptr)
P3_ABSTRACT_ME(destroy, nullptr)
PHP_FE_END
};
static zend_function_entry timer_methods[] = {
P3_ME_D(asio::timer, expiresFromNow, timer_expires_from_now_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::timer, expiresAt, timer_expires_at_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::timer, wait, timer_wait_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::timer, cancel, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry signal_methods[] = {
P3_ME_D(asio::signal, add, signal_add_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::signal, remove, signal_remove_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::signal, wait, signal_wait_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::signal, clear, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::signal, cancel, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry resolver_methods[] = {
P3_ABSTRACT_ME(resolve, resolver_resolve_arginfo)
P3_ABSTRACT_ME(cancel, nullptr)
PHP_FE_END
};
static zend_function_entry tcp_resolver_methods[] = {
P3_ME_D(asio::tcp_resolver, resolve, resolver_resolve_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_resolver, cancel, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry udp_resolver_methods[] = {
P3_ME_D(asio::udp_resolver, resolve, resolver_resolve_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_resolver, cancel, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry socket_methods[] = {
P3_ABSTRACT_ME(available, socket_available_arginfo)
P3_ABSTRACT_ME(atMark, socket_at_mark_arginfo)
P3_ABSTRACT_ME(close, nullptr)
PHP_FE_END
};
static zend_function_entry inet_socket_methods[] = {
P3_ABSTRACT_ME(open, inet_socket_open_arginfo)
P3_ABSTRACT_ME(assign, inet_socket_assign_arginfo)
P3_ABSTRACT_ME(bind, inet_socket_bind_arginfo)
P3_ABSTRACT_ME(remoteAddr, nullptr)
P3_ABSTRACT_ME(remotePort, nullptr)
PHP_FE_END
};
static zend_function_entry local_socket_methods[] = {
P3_ABSTRACT_ME(open, nullptr)
P3_ABSTRACT_ME(assign, local_socket_assign_arginfo)
P3_ABSTRACT_ME(bind, local_socket_bind_arginfo)
P3_ABSTRACT_ME(remotePath, nullptr)
PHP_FE_END
};
static zend_function_entry stream_socket_methods[] = {
P3_ABSTRACT_ME(read, stream_socket_read_arginfo)
P3_ABSTRACT_ME(write, stream_socket_write_arginfo)
PHP_FE_END
};
static zend_function_entry datagram_socket_methods[] = {
P3_ABSTRACT_ME(recvFrom, datagram_socket_recv_from_arginfo)
PHP_FE_END
};
static zend_function_entry tcp_socket_methods[] = {
P3_ME(asio::tcp_socket, open, open_inet, inet_socket_open_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::tcp_socket, assign, assign_inet, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::tcp_socket, bind, bind_inet, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, connect, tcp_socket_connect_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, remoteAddr, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, remotePort, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_socket, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry udp_socket_methods[] = {
P3_ME(asio::udp_socket, open, open_inet, inet_socket_open_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::udp_socket, assign, assign_inet, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::udp_socket, bind, bind_inet, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, sendTo, udp_socket_send_to_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, recvFrom, datagram_socket_recv_from_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, remoteAddr, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, remotePort, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udp_socket, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry unix_socket_methods[] = {
P3_ME(asio::unix_socket, open, open_local, nullptr, ZEND_ACC_PUBLIC)
P3_ME(asio::unix_socket, assign, assign_local, local_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::unix_socket, bind, bind_local, local_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, connect, unix_socket_connect_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, remotePath, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_socket, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry udg_socket_methods[] = {
P3_ME(asio::udg_socket, open, open_local, nullptr, ZEND_ACC_PUBLIC)
P3_ME(asio::udg_socket, assign, assign_local, local_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME(asio::udg_socket, bind, bind_local, local_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, sendTo, udg_socket_send_to_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, recvFrom, datagram_socket_recv_from_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, remotePath, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::udg_socket, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry acceptor_methods[] = {
P3_ABSTRACT_ME(listen, acceptor_listen_arginfo)
P3_ABSTRACT_ME(accept, acceptor_accept_arginfo)
P3_ABSTRACT_ME(close, nullptr)
PHP_FE_END
};
static zend_function_entry tcp_acceptor_methods[] = {
P3_ME_D(asio::tcp_acceptor, open, inet_socket_open_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, assign, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, bind, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, listen, acceptor_listen_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, accept, acceptor_accept_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::tcp_acceptor, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry unix_acceptor_methods[] = {
P3_ME_D(asio::unix_acceptor, open, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, assign, local_socket_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, bind, local_socket_bind_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, listen, acceptor_listen_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, accept, acceptor_accept_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, cancel, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::unix_acceptor, close, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static zend_function_entry stream_descriptor_methods[] = {
P3_ME_D(asio::stream_descriptor, assign, stream_descriptor_assign_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::stream_descriptor, isOpen, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::stream_descriptor, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::stream_descriptor, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC)
P3_ME_D(asio::stream_descriptor, release, nullptr, ZEND_ACC_PUBLIC)
P3_ME_D(asio::stream_descriptor, cancel, nullptr, ZEND_ACC_PUBLIC)
PHP_FE_END
};
/* }}} */
/* {{{ interfaces class_entry */
auto io_object_ce = p3::interface_init("Asio\\IoObject", io_object_method);
auto resolver_ce = p3::interface_init("Asio\\Resolver", resolver_methods);
auto socket_ce = p3::interface_init("Asio\\Socket", socket_methods);
auto inet_socket_ce = p3::interface_init("Asio\\InetSocket", inet_socket_methods);
auto local_socket_ce = p3::interface_init("Asio\\LocalSocket", local_socket_methods);
auto stream_socket_ce = p3::interface_init("Asio\\StreamSocket", stream_socket_methods);
auto datagram_socket_ce = p3::interface_init("Asio\\DatagramSocket", datagram_socket_methods);
auto acceptor_ce = p3::interface_init("Asio\\Acceptor", acceptor_methods);
/* }}} */
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(asio)
{
socket_ce->parent = io_object_ce;
resolver_ce->parent = io_object_ce;
acceptor_ce->parent = io_object_ce;
inet_socket_ce->parent = socket_ce;
local_socket_ce->parent = socket_ce;
stream_socket_ce->parent = socket_ce;
datagram_socket_ce->parent = socket_ce;
p3::class_init<asio::service>("Asio\\Service", service_methods);
auto ce = p3::class_init<asio::timer>("Asio\\Timer", timer_methods);
zend_class_implements(ce, 1, io_object_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::signal>("Asio\\Signal", signal_methods);
zend_class_implements(ce, 1, io_object_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::tcp_resolver>("Asio\\TcpResolver", tcp_resolver_methods);
zend_class_implements(ce, 1, resolver_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::udp_resolver>("Asio\\UdpResolver", udp_resolver_methods);
zend_class_implements(ce, 1, resolver_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::tcp_socket>("Asio\\TcpSocket", tcp_socket_methods);
zend_class_implements(ce, 2, inet_socket_ce, stream_socket_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::udp_socket>("Asio\\UdpSocket", udp_socket_methods);
zend_class_implements(ce, 2, inet_socket_ce, datagram_socket_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::unix_socket>("Asio\\UnixSocket", unix_socket_methods);
zend_class_implements(ce, 2, local_socket_ce, stream_socket_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::udg_socket>("Asio\\UdgSocket", udg_socket_methods);
zend_class_implements(ce, 2, local_socket_ce, datagram_socket_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::tcp_acceptor>("Asio\\TcpAcceptor", tcp_acceptor_methods);
zend_class_implements(ce, 1, acceptor_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::unix_acceptor>("Asio\\UnixAcceptor", unix_acceptor_methods);
zend_class_implements(ce, 1, acceptor_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::stream_descriptor>("Asio\\StreamDescriptor", stream_descriptor_methods);
zend_class_implements(ce, 1, io_object_ce);
ce->ce_flags |= ZEND_ACC_FINAL;
#ifdef ENABLE_COROUTINE
ce = p3::class_init<asio::future>("Asio\\Future", nullptr);
ce->ce_flags |= ZEND_ACC_FINAL;
#endif // ENABLE_COROUTINE
#ifdef ENABLE_STRAND
ce = p3::class_init<asio::strand>("Asio\\Strand", strand_methods);
ce->ce_flags |= ZEND_ACC_FINAL;
ce = p3::class_init<asio::wrapped_handler>("Asio\\WrappedHandler", wrapped_handler_methods);
ce->ce_flags |= ZEND_ACC_FINAL;
#endif // ENABLE_STRAND
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(asio)
{
php_info_print_table_start();
php_info_print_table_header(2, "Coroutine support",
#ifdef ENABLE_COROUTINE
"enabled"
#else
"disabled"
#endif // ENABLE_COROUTINE
);
php_info_print_table_header(2, "Strand support",
#ifdef ENABLE_STRAND
"enabled"
#else
"disabled"
#endif // ENABLE_STRAND
);
php_info_print_table_header(2, "Null buffers support",
#ifdef ENABLE_NULL_BUFFERS
"enabled"
#else
"disabled"
#endif // ENABLE_NULL_BUFFERS
);
php_info_print_table_end();
}
/* }}} */
/* {{{ asio_module_entry */
zend_module_entry asio_module_entry = {
STANDARD_MODULE_HEADER,
"asio",
nullptr,
PHP_MINIT(asio),
nullptr,
nullptr,
nullptr,
PHP_MINFO(asio),
PHP_ASIO_VERSION,
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_ASIO
ZEND_GET_MODULE(asio)
#endif // COMPILE_DL_ASIO

26
src/php_asio.hpp Normal file
View File

@ -0,0 +1,26 @@
/**
* php-asio/php_asio.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
extern zend_module_entry asio_module_entry;
#define phpext_asio_ptr &asio_module_entry
#define PHP_ASIO_VERSION "0.1.5"
#if defined(__GNUC__) && __GNUC__ >= 4
#define PHP_ASIO_API __attribute__ ((visibility("default")))
#else
#define PHP_ASIO_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif
#if defined(ZTS) && defined(COMPILE_DL_ASIO)
ZEND_TSRMLS_CACHE_EXTERN()
#endif

70
src/resolver.cpp Normal file
View File

@ -0,0 +1,70 @@
/**
* php-asio/resolver.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "resolver.hpp"
#include "future.hpp"
namespace asio
{
template <typename Protocol>
zval* resolver<Protocol>::handler(const boost::system::error_code& error,
iterator iter, zval* callback, zval* argument)
{
iterator end;
ZVAL_INIT(addr_list);
ZVAL_NEW_ARR(&addr_list);
zend_hash_init(Z_ARR(addr_list), 0, nullptr, ZVAL_PTR_DTOR, 0);
ZVAL_INIT(addr_val);
while (iter != end) {
auto addr = (*iter++).endpoint().address().to_string();
ZVAL_STR(&addr_val, zend_string_init(addr.c_str(), addr.length(), 0));
zend_hash_next_index_insert(Z_ARR(addr_list), &addr_val);
}
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_ARR(&arguments[1], Z_ARR(addr_list));
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_ARR, Z_ARR(addr_list));
}
template <typename Protocol>
P3_METHOD(resolver<Protocol>, resolve)
{
zend_string* host;
zend_string* service = nullptr;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(1, 4)
Z_PARAM_STR(host)
Z_PARAM_OPTIONAL
Z_PARAM_STR(service)
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_FUTURE_INIT();
future->template on_resolve<iterator>(boost::bind(
&resolver::handler, this, _1, _2, cb, args));
resolver_.async_resolve({ ZSTR_VAL(host), service ? ZSTR_VAL(service) : "" },
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(iterator)));
FUTURE_RETURN();
}
template <typename Protocol>
P3_METHOD(resolver<Protocol>, cancel)
{
resolver_.cancel();
RETVAL_LONG(0);
}
template <typename Protocol>
zend_class_entry* resolver<Protocol>::class_entry;
template <typename Protocol>
zend_object_handlers resolver<Protocol>::handlers;
template class resolver<tcp>;
template class resolver<udp>;
}

51
src/resolver.hpp Normal file
View File

@ -0,0 +1,51 @@
/**
* php-asio/resolver.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
namespace asio
{
/// Wrapper for Boost.Asio resolver.
/// Provides hostname resolution.
template <typename Protocol>
class resolver : public base
{
/// Resolver iterator which holds an endpoint.
using iterator = typename Protocol::resolver::iterator;
/// Boost.Asio resolver instance.
typename Protocol::resolver resolver_;
/// Resolve handler.
zval* handler(const boost::system::error_code& error,
iterator iter, zval* callback, zval* argument);
public:
/// Constructor.
explicit resolver(
boost::asio::io_service& io_service
) : base(io_service), resolver_(io_service) {}
/* {{{ proto Future Resolver::resolve(string host, [string service = ""],
* [callable callback], [mixed argument]);
* Initiate an asynchronous resolve against the resolver. */
P3_METHOD_DECLARE(resolve);
/* }}} */
/* {{{ proto int Resolver::cancel(void);
* Cancel all asynchronous operations on this resolver. */
P3_METHOD_DECLARE(cancel);
/* }}} */
PHP_ASIO_CE_DECLARE();
};
using tcp_resolver = resolver<tcp>;
using udp_resolver = resolver<udp>;
}

170
src/service.cpp Normal file
View File

@ -0,0 +1,170 @@
/**
* php-asio/service.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "service.hpp"
#define PHP_ASIO_RUN_LOOP(meth) \
zval* error = nullptr; \
ZEND_PARSE_PARAMETERS_START(0, 1) \
Z_PARAM_OPTIONAL \
Z_PARAM_ZVAL(error) \
ZEND_PARSE_PARAMETERS_END(); \
boost::system::error_code ec; \
auto handler_count = meth(ec); \
if (error) { \
ZVAL_DEREF(error); \
ZVAL_LONG(error, static_cast<zend_long>(ec.value())); \
} \
RETVAL_LONG(static_cast<zend_long>(handler_count))
#define PHP_ASIO_NOTIFY_FORK(ev) \
try { \
io_service_.notify_fork(boost::asio::io_service::ev); \
} catch (const boost::system::system_error& err) { \
RETURN_LONG(static_cast<zend_long>(err.code().value())); \
} \
RETVAL_LONG(0)
#define SERVICE_DISPATCH_CALLBACK(meth) PHP_ASIO_DISPATCH_CALLBACK(meth) })
namespace asio
{
P3_METHOD(service, addTimer)
{
PHP_ASIO_OBJ_ALLOC(timer, asio::timer, io_service_);
RETVAL_OBJ(timer);
}
P3_METHOD(service, addSignal)
{
PHP_ASIO_OBJ_ALLOC(signal, asio::signal, io_service_);
RETVAL_OBJ(signal);
}
P3_METHOD(service, addTcpResolver)
{
PHP_ASIO_OBJ_ALLOC(resolver, tcp_resolver, io_service_);
RETVAL_OBJ(resolver);
}
P3_METHOD(service, addUdpResolver)
{
PHP_ASIO_OBJ_ALLOC(resolver, udp_resolver, io_service_);
RETVAL_OBJ(resolver);
}
P3_METHOD(service, addTcpSocket)
{
PHP_ASIO_OBJ_ALLOC(socket, tcp_socket, io_service_);
RETVAL_OBJ(socket);
}
P3_METHOD(service, addUdpSocket)
{
PHP_ASIO_OBJ_ALLOC(socket, udp_socket, io_service_);
RETVAL_OBJ(socket);
}
P3_METHOD(service, addUnixSocket)
{
PHP_ASIO_OBJ_ALLOC(socket, unix_socket, io_service_);
RETVAL_OBJ(socket);
}
P3_METHOD(service, addUdgSocket)
{
PHP_ASIO_OBJ_ALLOC(socket, udg_socket, io_service_);
RETVAL_OBJ(socket);
}
P3_METHOD(service, addTcpAcceptor)
{
PHP_ASIO_OBJ_ALLOC(acceptor, tcp_acceptor, io_service_);
RETVAL_OBJ(acceptor);
}
P3_METHOD(service, addUnixAcceptor)
{
PHP_ASIO_OBJ_ALLOC(acceptor, unix_acceptor, io_service_);
RETVAL_OBJ(acceptor);
}
P3_METHOD(service, addStreamDescriptor)
{
PHP_ASIO_OBJ_ALLOC(descriptor, stream_descriptor, io_service_);
RETVAL_OBJ(descriptor);
}
#ifdef ENABLE_STRAND
P3_METHOD(service, addStrand)
{
PHP_ASIO_OBJ_ALLOC(strand, asio::strand, io_service_);
RETVAL_OBJ(strand);
}
#endif // ENABLE_STRAND
P3_METHOD(service, run)
{
PHP_ASIO_RUN_LOOP(io_service_.run);
}
P3_METHOD(service, runOne)
{
PHP_ASIO_RUN_LOOP(io_service_.run_one);
}
P3_METHOD(service, poll)
{
PHP_ASIO_RUN_LOOP(io_service_.poll);
}
P3_METHOD(service, pollOne)
{
PHP_ASIO_RUN_LOOP(io_service_.poll_one);
}
P3_METHOD(service, stop)
{
io_service_.stop();
}
P3_METHOD(service, reset)
{
io_service_.reset();
}
P3_METHOD(service, stopped) const
{
RETVAL_BOOL(io_service_.stopped());
}
P3_METHOD(service, post)
{
SERVICE_DISPATCH_CALLBACK(io_service_.post);
}
P3_METHOD(service, dispatch)
{
SERVICE_DISPATCH_CALLBACK(io_service_.dispatch);
}
P3_METHOD(service, forkPrepare)
{
PHP_ASIO_NOTIFY_FORK(fork_prepare);
}
P3_METHOD(service, forkParent)
{
PHP_ASIO_NOTIFY_FORK(fork_parent);
}
P3_METHOD(service, forkChild)
{
PHP_ASIO_NOTIFY_FORK(fork_child);
}
PHP_ASIO_CE_DEFINE(service);
}

171
src/service.hpp Normal file
View File

@ -0,0 +1,171 @@
/**
* php-asio/service.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "future.hpp"
#include "timer.hpp"
#include "signal.hpp"
#include "resolver.hpp"
#include "socket.hpp"
#include "acceptor.hpp"
#include "stream_descriptor.hpp"
#include "strand.hpp"
#include "php_asio.hpp"
namespace asio
{
/// Wrapper for Boost.Asio io_service.
/// Provide access to instantiation of I/O objects.
class service
{
/// The io_service of all I/O objects in current instance.
boost::asio::io_service io_service_;
public:
/// Default constructor.
service() = default;
/// Deleted copy constructor.
service(const service&) = delete;
/// Default destructor.
virtual ~service() = default;
/// Deleted copy assignment operator.
service& operator=(const service&) = delete;
/* {{{ proto Timer Service::addTimer(void);
* Add a new timer. */
P3_METHOD_DECLARE(addTimer);
/* }}} */
/* {{{ proto Signal Service::addSignal(void);
* Add a new signal set. */
P3_METHOD_DECLARE(addSignal);
/* }}} */
/* {{{ proto TcpResolver Service::addTcpResolver(void);
* Add a new TCP resolver. */
P3_METHOD_DECLARE(addTcpResolver);
/* }}} */
/* {{{ proto UdpResolver Service::addUdpResolver(void);
* Add a new UDP resolver.*/
P3_METHOD_DECLARE(addUdpResolver);
/* }}} */
/* {{{ proto TcpSocket Service::addUdpSocket(void);
* Add a new TCP socket. */
P3_METHOD_DECLARE(addTcpSocket);
/* }}} */
/* {{{ proto UdpSocket Service::addUdpSocket(void);
* Add a new UDP socket. */
P3_METHOD_DECLARE(addUdpSocket);
/* }}} */
/* {{{ proto UnixSocket Service::addUnixSocket(void);
* Add a new UNIX domain socket (SOCK_STREAM). */
P3_METHOD_DECLARE(addUnixSocket);
/* }}} */
/* {{{ proto UdgSocket Service::addUdgSocket(void);
* Add a new UNIX domain socket (SOCK_DGRAM). */
P3_METHOD_DECLARE(addUdgSocket);
/* }}} */
/* {{{ proto TcpAcceptor Service::addTcpAcceptor(void);
* Add a new TCP acceptor. */
P3_METHOD_DECLARE(addTcpAcceptor);
/* }}} */
/* {{{ proto UnixAcceptor Service::addUnixAcceptor(void);
* Add a new UNIX domain socket acceptor (SOCK_STREAM). */
P3_METHOD_DECLARE(addUnixAcceptor);
/* }}} */
/* {{{ proto StreamDescriptor Service::addStreamDescriptor(void);
* Add a new stream descriptor. */
P3_METHOD_DECLARE(addStreamDescriptor);
/* }}} */
#ifdef ENABLE_STRAND
/* {{{ proto Strand Service::addStrand(void);
* Add a new strand object. */
P3_METHOD_DECLARE(addStrand);
/* }}} */
#endif // ENABLE_STRAND
/* {{{ proto int run([int &ec]);
* Run the event loop until stopped or no more work. */
P3_METHOD_DECLARE(run);
/* }}} */
/* {{{ proto int runOne([int &ec]);
* Run until stopped or one operation is performed. */
P3_METHOD_DECLARE(runOne);
/* }}} */
/* {{{ proto int poll([int &ec]);
* Poll for operations without blocking. */
P3_METHOD_DECLARE(poll);
/* }}} */
/* {{{ proto int pollOne([int &ec]);
* Poll for one operation without blocking. */
P3_METHOD_DECLARE(pollOne);
/* }}} */
/* {{{ proto void stop(void);
* Stop the event processing loop. */
P3_METHOD_DECLARE(stop);
/* }}} */
/* {{{ proto void stop(void);
* Reset in preparation for a subsequent run invocation. */
P3_METHOD_DECLARE(reset);
/* }}} */
/* {{{ proto bool stopped(void);
* Determine whether the io_service is stopped. */
P3_METHOD_DECLARE(stopped) const;
/* }}} */
/* {{{ proto void Service::post(callable callback, [mixed argument]);
* Request invocation of the given handler and return immediately. */
P3_METHOD_DECLARE(post);
/* }}} */
/* {{{ proto void Service::dispatch(callable callback, [mixed argument]);
* Request invocation of the given handler. */
P3_METHOD_DECLARE(dispatch);
/* {{{ proto int Service::forkPrepare(void);
* Notify all services of a fork event. (prepare to fork) */
P3_METHOD_DECLARE(forkPrepare);
/* }}} */
/* {{{ proto int Service::forkParent(void);
* Notify all services of a fork event. (fork parent) */
P3_METHOD_DECLARE(forkParent);
/* }}} */
/* {{{ proto int Service::forkChild(void);
* Notify all services of a fork event. (fork child) */
P3_METHOD_DECLARE(forkChild);
/// Get io_service by reference.
/// Can be used when working on another extension based on Boost.Asio.
PHP_ASIO_API boost::asio::io_service& get_io_service()
{
return io_service_;
}
PHP_ASIO_CE_DECLARE();
};
}

90
src/signal.cpp Normal file
View File

@ -0,0 +1,90 @@
/**
* php-asio/signal.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "signal.hpp"
#include "future.hpp"
namespace asio
{
zval* signal::handler(const boost::system::error_code& error,
int signal, zval* callback, zval* argument)
{
PHP_ASIO_INVOKE_CALLBACK_START(4);
ZVAL_LONG(&arguments[1], static_cast<zend_long>(signal));
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_LONG, static_cast<zend_long>(signal));
}
P3_METHOD(signal, add)
{
size_t sig_len = 0;
zval* sig_num = nullptr;
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_VARIADIC('+', sig_num, sig_len)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
for (size_t i = 0; i < sig_len; i++) {
if (Z_TYPE(sig_num[i]) != IS_LONG) {
PHP_ASIO_ERROR(E_NOTICE, "Integer value expected.");
continue;
}
signal_.add(static_cast<int>(Z_LVAL(sig_num[i])), ec);
if (ec)
break;
}
RETVAL_EC(ec);
}
P3_METHOD(signal, remove)
{
size_t sig_len = 0;
zval* sig_num = nullptr;
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_VARIADIC('+', sig_num, sig_len)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
for (size_t i = 0; i < sig_len; i++) {
if (Z_TYPE(sig_num[i]) != IS_LONG) {
PHP_ASIO_ERROR(E_NOTICE, "Integer value expected.");
continue;
}
signal_.remove(static_cast<int>(Z_LVAL(sig_num[i])), ec);
if (ec)
break;
}
RETVAL_EC(ec);
}
P3_METHOD(signal, wait)
{
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_FUTURE_INIT();
future->on_resolve<int>(boost::bind(&signal::handler, this, _1, _2, cb, args));
signal_.async_wait(STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(int)));
FUTURE_RETURN();
}
P3_METHOD(signal, clear)
{
boost::system::error_code ec;
RETVAL_EC(signal_.clear(ec));
}
P3_METHOD(signal, cancel)
{
boost::system::error_code ec;
RETVAL_EC(signal_.cancel(ec));
}
PHP_ASIO_CE_DEFINE(signal);
}

58
src/signal.hpp Normal file
View File

@ -0,0 +1,58 @@
/**
* php-asio/signal.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
namespace asio
{
/// Wrapper for Boost.Asio signal_set.
/// Provides functionalities for signal handling.
class signal : public base
{
/// Boost.Asio signal_set instance.
boost::asio::signal_set signal_;
/// Signal handler callback.
zval* handler(const boost::system::error_code& error,
int signal, zval* callback, zval* argument);
public:
/// Constructor.
explicit signal(
boost::asio::io_service& io_service
) : base(io_service), signal_(io_service) {}
/* {{{ proto int Signal::add(int sig_num, [int ...]);
* Add the specified signal(s) to the signal set. */
P3_METHOD_DECLARE(add);
/* }}} */
/* {{{ proto int Signal::remove(int sig_num, [int ...]);
* Remove the specified signal(s) from the signal set. */
P3_METHOD_DECLARE(remove);
/* }}} */
/* {{{ proto Future Signal::wait([callable callback], [mixed argument]);
* Initiate an asynchronous wait against the signal set. */
P3_METHOD_DECLARE(wait);
/* }}} */
/* {{{ proto int Signal::clear(void);
* Remove all signals from the signal set. */
P3_METHOD_DECLARE(clear);
/* }}} */
/* {{{ proto int Signal::cancel(void);
* Cancel current signal set. */
P3_METHOD_DECLARE(cancel);
/* }}} */
PHP_ASIO_CE_DECLARE();
};
}

480
src/socket.cpp Normal file
View File

@ -0,0 +1,480 @@
/**
* php-asio/socket.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "socket.hpp"
#include "future.hpp"
#include "io.hpp"
namespace asio
{
template <typename Protocol>
zval* socket<Protocol>::connect_handler(const boost::system::error_code& error,
zval* callback, zval* argument)
{
PHP_ASIO_INVOKE_CALLBACK_START(3)
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN_NULL();
}
template <typename Protocol> template <typename, typename>
zval* socket<Protocol>::read_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument)
{
// Zend strings have to be zero-terminated.
// Otherwise you'll get E_WARNING on zval dtor.
ZSTR_VAL(buffer)[length] = '\0';
ZSTR_LEN(buffer) = length;
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_STR(&arguments[1], buffer);
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_STR, buffer);
}
template <typename Protocol>
zval* socket<Protocol>::write_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument)
{
#ifdef ENABLE_NULL_BUFFERS
if (buffer)
#endif //ENABLE_NULL_BUFFERS
zend_string_release(buffer);
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_LONG(&arguments[1], static_cast<zend_long>(length));
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_LONG, static_cast<zend_long>(length));
}
template <>
zval* udp_socket::recv_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, udp::endpoint* endpoint,
zval* callback, zval* argument)
{
ZSTR_VAL(buffer)[length] = '\0';
ZSTR_LEN(buffer) = length;
#ifdef ENABLE_COROUTINE
last_addr_<> = endpoint->address().to_string();
last_port_<> = endpoint->port();
#endif // ENABLE_COROUTINE
PHP_ASIO_INVOKE_CALLBACK_START(6)
ZVAL_STR(&arguments[1], buffer);
#ifdef ENABLE_COROUTINE
ZVAL_STRING(&arguments[2], last_addr_<>.data());
ZVAL_LONG(&arguments[3], static_cast<zend_long>(last_port_<>));
#else
ZVAL_STRING(&arguments[2], endpoint->address().to_string().data());
ZVAL_LONG(&arguments[3], static_cast<zend_long>(endpoint->port()));
#endif // ENABLE_COROUTINE
PHP_ASIO_INVOKE_CALLBACK();
zval_ptr_dtor(&arguments[2]);
PHP_ASIO_INVOKE_CALLBACK_END();
delete endpoint;
CORO_RETURN(ZVAL_STR, buffer);
}
template <>
zval* udg_socket::recv_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, udg::endpoint* endpoint,
zval* callback, zval* argument)
{
ZSTR_VAL(buffer)[length] = '\0';
ZSTR_LEN(buffer) = length;
#ifdef ENABLE_COROUTINE
last_path_<> = endpoint->path();
#endif // ENABLE_COROUTINE
PHP_ASIO_INVOKE_CALLBACK_START(5)
ZVAL_STR(&arguments[1], buffer);
#ifdef ENABLE_COROUTINE
ZVAL_STRING(&arguments[2], last_path_<>.data());
#else
ZVAL_STRING(&arguments[2], endpoint->path().data());
#endif // ENABLE_COROUTINE
PHP_ASIO_INVOKE_CALLBACK();
zval_ptr_dtor(&arguments[2]);
PHP_ASIO_INVOKE_CALLBACK_END();
delete endpoint;
CORO_RETURN(ZVAL_STR, buffer);
}
template <typename Protocol> template <typename P, ENABLE_IF_INET(P)>
P3_METHOD(socket<Protocol>, open_inet)
{
zend_bool inet6;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_BOOL(inet6)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
socket_.open(inet6 ? Protocol::v6() : Protocol::v4(), ec);
RETVAL_EC(ec);
}
template <typename Protocol> template <typename P, ENABLE_IF_LOCAL(P)>
P3_METHOD(socket<Protocol>, open_local)
{
boost::system::error_code ec;
socket_.open(Protocol(), ec);
RETVAL_EC(ec);
}
template <typename Protocol> template <typename P, ENABLE_IF_INET(P)>
P3_METHOD(socket<Protocol>, assign_inet)
{
PHP_ASIO_INET_ASSIGN(socket_, Protocol);
}
template <typename Protocol> template <typename P, ENABLE_IF_LOCAL(P)>
P3_METHOD(socket<Protocol>, assign_local)
{
PHP_ASIO_LOCAL_ASSIGN(socket_, Protocol);
}
template <typename Protocol> template <typename P, ENABLE_IF_INET(P)>
P3_METHOD(socket<Protocol>, bind_inet)
{
zend_string* address;
zend_long port_num;
ZEND_PARSE_PARAMETERS_START(2, 2);
Z_PARAM_STR(address)
Z_PARAM_LONG(port_num)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
socket_.bind({ boost::asio::ip::address::from_string(ZSTR_VAL(address)),
static_cast<unsigned short>(port_num) }, ec);
RETVAL_EC(ec);
}
template <typename Protocol> template <typename P, ENABLE_IF_LOCAL(P)>
P3_METHOD(socket<Protocol>, bind_local)
{
zend_string* socket_path;
ZEND_PARSE_PARAMETERS_START(1, 1);
Z_PARAM_STR(socket_path)
ZEND_PARSE_PARAMETERS_END();
namespace fs = boost::filesystem;
if (fs::status(ZSTR_VAL(socket_path)).type() == fs::socket_file)
fs::remove(ZSTR_VAL(socket_path));
boost::system::error_code ec;
socket_.bind({ ZSTR_VAL(socket_path) }, ec);
RETVAL_EC(ec);
}
/* {{{ proto Future TcpSocket::connect(string address, int port,
* [callable callback], [mixed argument]);
* Asynchronously connect to a remote endpoint. */
template <>
P3_METHOD(tcp_socket, connect)
{
zend_string* address;
zend_long port;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_STR(address)
Z_PARAM_LONG(port)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_FUTURE_INIT();
future->on_resolve<NOARG>(boost::bind(&socket::connect_handler, this, _1, cb, args));
socket_.async_connect({ boost::asio::ip::address::from_string(ZSTR_VAL(address)),
static_cast<unsigned short>(port) }, STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG));
FUTURE_RETURN();
}
/* }}} */
/* {{{ proto Future UnixSocket::connect(string path, [callable callback], [mixed argument]);
* Asynchronously connect to a remote endpoint. */
template <>
P3_METHOD(unix_socket, connect)
{
zend_string* path;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_STR(path)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_FUTURE_INIT();
future->on_resolve<NOARG>(boost::bind(&socket::connect_handler, this, _1, cb, args));
socket_.async_connect({ ZSTR_VAL(path) }, STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG));
FUTURE_RETURN();
}
/* }}} */
template <typename Protocol> template <typename, typename>
P3_METHOD(socket<Protocol>, read)
{
PHP_ASIO_READ(socket, socket_);
}
template <typename Protocol> template <typename, typename>
P3_METHOD(socket<Protocol>, write)
{
PHP_ASIO_WRITE(socket, socket_);
}
template <typename Protocol> template <typename, typename>
P3_METHOD(socket<Protocol>, recvFrom)
{
zend_long length;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(1, 3);
Z_PARAM_LONG(length)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_BUFFER_LEN_VALIDATE();
auto buffer_container =
#ifdef ENABLE_NULL_BUFFERS
length == 0 ? ZSTR_EMPTY_ALLOC() :
#endif //ENABLE_NULL_BUFFERS
zend_string_alloc(static_cast<size_t>(length), 0);
auto endpoint = new typename Protocol::endpoint;
PHP_ASIO_FUTURE_INIT();
future->template on_resolve<size_t>(boost::bind(&socket::recv_handler,
this, _1, _2, buffer_container, endpoint, cb, args));
#ifdef ENABLE_NULL_BUFFERS
if (length == 0)
socket_.async_receive_from(boost::asio::null_buffers(),
*endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
else
#endif // ENABLE_NULL_BUFFERS
socket_.async_receive_from(mutable_buffer(buffer_container),
*endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
FUTURE_RETURN();
}
/* {{{ proto Future UdpSocket::sendTo(string data, string address, int port,
* [callable callback], [mixed argument]);
* Send asynchronously to UDP socket. */
template <> template <>
P3_METHOD(udp_socket, sendTo)
{
zend_string* data;
zend_string* address;
zend_long port;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_STR(data)
Z_PARAM_STR(address)
Z_PARAM_LONG(port)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
const udp::endpoint endpoint(boost::asio::ip::address::from_string(ZSTR_VAL(address)),
static_cast<unsigned short>(port));
const auto buffer_container =
#ifdef ENABLE_NULL_BUFFERS
ZSTR_LEN(data) == 0 ? nullptr :
#endif //ENABLE_NULL_BUFFERS
zend_string_copy(data);
PHP_ASIO_FUTURE_INIT();
future->on_resolve<size_t>(boost::bind(&socket::write_handler,
this, _1, _2, buffer_container, cb, args));
#ifdef ENABLE_NULL_BUFFERS
if (ZSTR_LEN(data) == 0)
socket_.async_send_to(boost::asio::null_buffers(),
endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
else
#endif // ENABLE_NULL_BUFFERS
socket_.async_send_to(const_buffer(buffer_container),
endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
FUTURE_RETURN();
}
/* }}} */
/* {{{ proto Future UdgSocket::sendTo(string data, string path,
* [callable callback], [mixed argument]);
* Send asynchronously to UNIX datagram socket. */
template <> template <>
P3_METHOD(udg_socket, sendTo)
{
zend_string* data;
zend_string* path;
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_STR(data)
Z_PARAM_STR(path)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
const auto buffer_container =
#ifdef ENABLE_NULL_BUFFERS
ZSTR_LEN(data) == 0 ? nullptr :
#endif //ENABLE_NULL_BUFFERS
zend_string_copy(data);
PHP_ASIO_FUTURE_INIT();
future->on_resolve<size_t>(boost::bind(
&socket::write_handler, this, _1, _2, buffer_container, cb, args));
#ifdef ENABLE_NULL_BUFFERS
if (ZSTR_LEN(data) == 0)
socket_.async_send_to(boost::asio::null_buffers(), { ZSTR_VAL(path) },
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
else
#endif //ENABLE_NULL_BUFFERS
socket_.async_send_to(const_buffer(buffer_container), { ZSTR_VAL(path) },
STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t)));
FUTURE_RETURN();
}
/* }}} */
template <> template <>
P3_METHOD(tcp_socket, remoteAddr) const
{
RETVAL_STRING(socket_.remote_endpoint().address().to_string().c_str());
}
template <> template <>
P3_METHOD(tcp_socket, remotePort) const
{
RETVAL_LONG(static_cast<zend_long>(socket_.remote_endpoint().port()));
}
template <> template <>
P3_METHOD(unix_socket, remotePath) const
{
RETVAL_STRING(socket_.remote_endpoint().path().c_str());
}
template <> template <>
P3_METHOD(udp_socket, remoteAddr) const
{
#ifdef ENABLE_COROUTINE
RETVAL_STRING(last_addr_<>.data());
#else
PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support.");
RETVAL_NULL();
#endif // ENABLE_COROUTINE
}
template <> template <>
P3_METHOD(udp_socket, remotePort) const
{
#ifdef ENABLE_COROUTINE
RETVAL_LONG(static_cast<zend_long>(last_port_<>));
#else
PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support.");
RETVAL_NULL();
#endif // ENABLE_COROUTINE
}
template <> template <>
P3_METHOD(udg_socket, remotePath) const
{
#ifdef ENABLE_COROUTINE
RETVAL_STRING(last_path_<>.data());
#else
PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support.");
RETVAL_NULL();
#endif // ENABLE_COROUTINE
}
template <typename Protocol>
P3_METHOD(socket<Protocol>, available) const
{
zval* error = nullptr;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(error)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
auto bytes = socket_.available(ec);
if (error) {
ZVAL_DEREF(error);
ZVAL_LONG(error, static_cast<zend_long>(ec.value()));
}
RETVAL_LONG(static_cast<zend_long>(bytes));
}
template <typename Protocol>
P3_METHOD(socket<Protocol>, atMark) const
{
zval* error = nullptr;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(error)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
auto at_mark = socket_.at_mark(ec);
if (error) {
ZVAL_DEREF(error);
ZVAL_LONG(error, static_cast<zend_long>(ec.value()));
}
RETVAL_BOOL(at_mark);
}
template <typename Protocol>
P3_METHOD(socket<Protocol>, cancel)
{
boost::system::error_code ec;
RETVAL_EC(socket_.cancel(ec));
}
template <typename Protocol>
P3_METHOD(socket<Protocol>, close)
{
boost::system::error_code ec;
RETVAL_EC(socket_.close(ec));
}
template <typename Protocol>
zend_class_entry* socket<Protocol>::class_entry;
template <typename Protocol>
zend_object_handlers socket<Protocol>::handlers;
#ifdef ENABLE_COROUTINE
template <typename Protocol> template <typename, typename>
thread_local std::string socket<Protocol>::last_addr_;
template <typename Protocol> template <typename, typename>
thread_local unsigned short socket<Protocol>::last_port_;
template <typename Protocol> template <typename, typename>
thread_local std::string socket<Protocol>::last_path_;
#endif // ENABLE_COROUTINE
template class socket<tcp>;
template P3_METHOD(tcp_socket, open_inet);
template P3_METHOD(tcp_socket, assign_inet);
template P3_METHOD(tcp_socket, bind_inet);
template zval* tcp_socket::read_handler(const boost::system::error_code&,
size_t, zend_string*, zval*, zval*);
template P3_METHOD(tcp_socket, read);
template P3_METHOD(tcp_socket, write);
template class socket<unix>;
template P3_METHOD(unix_socket, open_local);
template P3_METHOD(unix_socket, assign_local);
template P3_METHOD(unix_socket, bind_local);
template zval* unix_socket::read_handler(const boost::system::error_code&,
size_t, zend_string*, zval*, zval*);
template P3_METHOD(unix_socket, read);
template P3_METHOD(unix_socket, write);
template class socket<udp>;
template P3_METHOD(udp_socket, open_inet);
template P3_METHOD(udp_socket, assign_inet);
template P3_METHOD(udp_socket, bind_inet);
template P3_METHOD(udp_socket, recvFrom);
template class socket<udg>;
template P3_METHOD(udg_socket, open_local);
template P3_METHOD(udg_socket, assign_local);
template P3_METHOD(udg_socket, bind_local);
template P3_METHOD(udg_socket, recvFrom);
}

188
src/socket.hpp Normal file
View File

@ -0,0 +1,188 @@
/**
* php-asio/socket.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
#include <boost/filesystem.hpp>
#define ENABLE_IF_INET(P) std::string(P::endpoint::*port)()
#define ENABLE_IF_LOCAL(P) std::string(P::endpoint::*path)()
namespace asio
{
/// Wrapper for Boost.Asio socket.
template <typename Protocol>
class socket : public base
{
/// Boost.Asio socket instance.
typename Protocol::socket socket_;
template <typename T1, typename T2>
using enable_if_same = std::enable_if_t<std::is_same<T1, T2>::value>;
#ifdef ENABLE_COROUTINE
/// Address of client who sent the last request.
template <typename P = Protocol, typename = enable_if_same<P, udp>>
static thread_local std::string last_addr_;
/// Port of client who sent the last request.
template <typename P = Protocol, typename = enable_if_same<P, udp>>
static thread_local unsigned short last_port_;
/// Socket path of client who sent the last request.
template <typename P = Protocol, typename = enable_if_same<P, udg>>
static thread_local std::string last_path_;
#endif // ENABLE_COROUTINE
/// Connect handler.
zval* connect_handler(const boost::system::error_code& error,
zval* callback, zval* argument);
/// Read handler for stream socket.
template <typename P = Protocol, typename =
enable_if_same<boost::asio::basic_stream_socket<P>, typename P::socket>>
zval* read_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument);
/// Write/send handler for socket.
zval* write_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument);
/// Receive handler for datagram socket.
zval* recv_handler(const boost::system::error_code& error, size_t length,
zend_string* buffer, typename Protocol::endpoint* endpoint,
zval* callback, zval* argument);
public:
/// Constructor.
explicit socket(
boost::asio::io_service& io_service
) : base(io_service), socket_(io_service) {}
/// Get reference of socket.
typename Protocol::socket& get_socket()
{
return socket_;
}
template <typename P>
using inet_p = typename P::endpoint::port;
/* {{{ proto int InetSocket::open(bool inet6);
* Open INET socket (AF_INET or AF_INET6). */
template <typename P = Protocol, ENABLE_IF_INET(P) = nullptr>
P3_METHOD_DECLARE(open_inet);
/* }}} */
/* {{{ proto int LocalSocket::open(void);
* Open UNIX domain socket. */
template <typename P = Protocol, ENABLE_IF_LOCAL(P) = nullptr>
P3_METHOD_DECLARE(open_local);
/* }}} */
/* {{{ proto int InetSocket::assign(bool inet6, int|resource fd);
* Assign an existing native socket to the socket. */
template <typename P = Protocol, ENABLE_IF_INET(P) = nullptr>
P3_METHOD_DECLARE(assign_inet);
/* }}} */
/* {{{ proto int LocalSocket::assign(int|resource fd);
* Assign an existing native socket to the socket. */
template <typename P = Protocol, ENABLE_IF_LOCAL(P) = nullptr>
P3_METHOD_DECLARE(assign_local);
/* }}} */
/* {{{ proto int InetSocket::bind(string address, int port);
* Bind socket to a local endpoint (AF_INET or AF_INET6). */
template <typename P = Protocol, ENABLE_IF_INET(P) = nullptr>
P3_METHOD_DECLARE(bind_inet);
/* }}} */
/* {{{ proto int LocalSocket::bind(string path);
* Bind socket to a local endpoint (AF_UNIX). */
template <typename P = Protocol, ENABLE_IF_LOCAL(P) = nullptr>
P3_METHOD_DECLARE(bind_local);
/* }}} */
P3_METHOD_DECLARE(connect);
/* {{{ proto Future StreamSocket::read(int length, [bool read_some = true],
* [callable callback], [mixed argument]);
* Read asynchronously from stream socket. */
template <typename P = Protocol, typename =
enable_if_same<boost::asio::basic_stream_socket<P>, typename P::socket>>
P3_METHOD_DECLARE(read);
/* }}} */
/* {{{ proto Future StreamSocket::write(string data, [bool write_some = false],
* [callable callback], [mixed argument]);
* Write asynchronously to stream socket. */
template <typename P = Protocol, typename =
enable_if_same<boost::asio::basic_stream_socket<P>, typename P::socket>>
P3_METHOD_DECLARE(write);
/* }}} */
/* {{{ proto Future DatagramSocket::recvFrom(int length,
* [callable callback], [mixed argument]);
* Receive asynchronously from datagram socket.*/
template <typename P = Protocol, typename =
enable_if_same<boost::asio::basic_datagram_socket<P>, typename P::socket>>
P3_METHOD_DECLARE(recvFrom);
/* }}} */
template <typename P = Protocol, typename =
enable_if_same<boost::asio::basic_datagram_socket<P>, typename P::socket>>
P3_METHOD_DECLARE(sendTo);
/* {{{ proto string InetSocket::remoteAddr(void);
* Get remote address (AF_INET or AF_INET6). */
template <typename P = Protocol, ENABLE_IF_INET(P) = nullptr>
P3_METHOD_DECLARE(remoteAddr) const;
/* }}} */
/* {{{ proto int InetSocket::remotePort(void);
* Get remote port (AF_INET or AF_INET6). */
template <typename P = Protocol, ENABLE_IF_INET(P) = nullptr>
P3_METHOD_DECLARE(remotePort) const;
/* }}} */
/* {{{ proto string LocalSocket::remotePath(void);
* Get socket file path of remote endpoint (AF_UNIX). */
template <typename P = Protocol, ENABLE_IF_LOCAL(P) = nullptr>
P3_METHOD_DECLARE(remotePath) const;
/* }}} */
/* {{{ proto int Socket::available([int &ec]);
* Determine the number of bytes available for reading. */
P3_METHOD_DECLARE(available) const;
/* }}} */
/* {{{ proto bool Socket::atMark([int &ec]);
* Determine whether the socket is at the out-of-band data mark. */
P3_METHOD_DECLARE(atMark) const;
/* }}} */
/* {{{ proto int Socket::cancel(void);
* Cancel all asynchronous operations on the socket. */
P3_METHOD_DECLARE(cancel);
/* }}} */
/* {{{ proto int Socket::close(void);
* Close the socket. */
P3_METHOD_DECLARE(close);
/* }}} */
PHP_ASIO_CE_DECLARE();
};
using tcp_socket = socket<tcp>;
using unix_socket = socket<unix>;
using udp_socket = socket<udp>;
using udg_socket = socket<udg>;
}

50
src/strand.cpp Normal file
View File

@ -0,0 +1,50 @@
/**
* php-asio/strand.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "strand.hpp"
#include "future.hpp"
#ifdef ENABLE_STRAND
#define STRAND_DISPATCH_CALLBACK(meth) \
PHP_ASIO_DISPATCH_CALLBACK(meth) PHP_ASIO_DEC_HANDLER_COUNT(); })
namespace asio
{
P3_METHOD(strand, dispatch)
{
PHP_ASIO_INC_HANDLER_COUNT();
STRAND_DISPATCH_CALLBACK(strand_.dispatch);
}
P3_METHOD(strand, post)
{
PHP_ASIO_INC_HANDLER_COUNT();
STRAND_DISPATCH_CALLBACK(strand_.post);
}
P3_METHOD(strand, runningInThisThread) const
{
RETVAL_BOOL(strand_.running_in_this_thread());
}
P3_METHOD(strand, wrap)
{
zval* callback;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(callback)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_INC_HANDLER_COUNT();
RETVAL_OBJ(p3::alloc_object<wrapped_handler>(wrapped_handler::class_entry,
[this, callback](wrapped_handler* ptr) {
new(ptr) wrapped_handler(this, callback);
}));
}
PHP_ASIO_CE_DEFINE(strand);
}
#endif // ENABLE_STRAND

87
src/strand.hpp Normal file
View File

@ -0,0 +1,87 @@
/**
* php-asio/strand.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
#define PHP_ASIO_DISPATCH_CALLBACK(meth) \
zval* callback; \
zval* argument = nullptr; \
ZEND_PARSE_PARAMETERS_START(1, 2) \
Z_PARAM_ZVAL(callback) \
Z_PARAM_OPTIONAL \
Z_PARAM_ZVAL(argument) \
ZEND_PARSE_PARAMETERS_END(); \
ZVAL_PTR_INIT(cb); \
ZVAL_COPY(cb, callback); \
ZVAL_PTR_INIT(args); \
if (argument) \
ZVAL_COPY(args, argument); \
else \
ZVAL_NULL(args); \
meth([this, cb, args]() { \
INIT_RETVAL(); \
call_user_function(CG(function_table), nullptr, cb, PASS_RETVAL, 1, args); \
CORO_REGISTER(retval); \
zval_ptr_dtor(cb); \
zval_ptr_dtor(args); \
efree(cb); \
efree(args); \
#ifdef ENABLE_STRAND
namespace asio
{
/// Wrapper for Boost.Asio strand.
/// Provides serialized handler execution.
class strand : public base
{
/// Boost.Asio strand instance.
boost::asio::strand strand_;
public:
/// Constructor.
explicit strand(
boost::asio::io_service& io_service
) : base(io_service), strand_(io_service) {}
/// Deleted default constructor.
strand() = delete;
/// Deleted copy constructor.
strand(const strand&) = delete;
/* {{{ proto void Strand::dispatch(callable callback, [mixed argument]);
* Request the strand to invoke the given handler. */
P3_METHOD_DECLARE(dispatch);
/* }}} */
/* {{{ proto void Strand::post(callable callback, [mixed argument]);
* Request the strand to invoke the given handler. */
P3_METHOD_DECLARE(post);
/* }}} */
/* {{{ proto bool Strand::runningInThisThread(void);
* Determine whether the strand is running in the current thread. */
P3_METHOD_DECLARE(runningInThisThread) const;
/* }}} */
/* {{{ proto WrappedHandler wrap([callable callback]);
* Create a new handler that automatically dispatches the wrapped handler on the strand. */
P3_METHOD_DECLARE(wrap);
/* }}} */
/// Returns the wrapped Boost.Asio strand.
boost::asio::strand* implmentation()
{
return &strand_;
}
PHP_ASIO_CE_DECLARE();
};
}
#endif // ENABLE_STRAND

86
src/stream_descriptor.cpp Normal file
View File

@ -0,0 +1,86 @@
/**
* php-asio/stream_descriptor.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "stream_descriptor.hpp"
#include "future.hpp"
#include "io.hpp"
namespace asio
{
zval* stream_descriptor::read_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument)
{
ZSTR_VAL(buffer)[length] = '\0';
ZSTR_LEN(buffer) = length;
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_STR(&arguments[1], buffer);
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_STR, buffer);
}
zval* stream_descriptor::write_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument)
{
#ifdef ENABLE_NULL_BUFFERS
if (buffer)
#endif //ENABLE_NULL_BUFFERS
zend_string_release(buffer);
PHP_ASIO_INVOKE_CALLBACK_START(4)
ZVAL_LONG(&arguments[1], static_cast<zend_long>(length));
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN(ZVAL_LONG, static_cast<zend_long>(length));
}
P3_METHOD(stream_descriptor, assign)
{
zval* fd;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(fd)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG))
stream_descriptor_.assign(Z_LVAL_P(fd), ec);
else
stream_descriptor_.assign(resource_to_fd(fd), ec);
RETVAL_EC(ec);
}
P3_METHOD(stream_descriptor, isOpen)
{
RETVAL_BOOL(stream_descriptor_.is_open());
}
P3_METHOD(stream_descriptor, read)
{
PHP_ASIO_READ(stream_descriptor, stream_descriptor_);
}
P3_METHOD(stream_descriptor, write)
{
PHP_ASIO_WRITE(stream_descriptor, stream_descriptor_);
}
P3_METHOD(stream_descriptor, release)
{
stream_descriptor_.release();
}
P3_METHOD(stream_descriptor, cancel)
{
boost::system::error_code ec;
RETVAL_EC(stream_descriptor_.cancel(ec));
}
P3_METHOD(stream_descriptor, close)
{
boost::system::error_code ec;
RETVAL_EC(stream_descriptor_.close(ec));
}
PHP_ASIO_CE_DEFINE(stream_descriptor);
}

73
src/stream_descriptor.hpp Normal file
View File

@ -0,0 +1,73 @@
/**
* php-asio/stream_descriptor.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
namespace asio
{
/// Wrapper for Boost.Asio stream descriptor.
class stream_descriptor : public base
{
/// Boost.Asio stream descriptor instance.
boost::asio::posix::stream_descriptor stream_descriptor_;
/// Read handler for stream descriptor.
zval* read_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument);
/// Write handler for socket.
zval* write_handler(const boost::system::error_code& error,
size_t length, zend_string* buffer, zval* callback, zval* argument);
public:
/// Constructor.
explicit stream_descriptor(
boost::asio::io_service& io_service
) : base(io_service), stream_descriptor_(io_service) {}
/* {{{ proto int StreamDescriptor::bind(int|resource fd);
* Opens the descriptor to hold an existing native descriptor. */
P3_METHOD_DECLARE(assign);
/* }}} */
/* {{{ proto bool StreamDescriptor::isOpen(void);
* Determine whether the descriptor is open. */
P3_METHOD_DECLARE(isOpen);
/* }}} */
/* {{{ proto Future StreamDescriptor::read(int length, [bool read_some = true],
* [callable callback], [mixed argument]);
* Read asynchronously from stream descriptor. */
P3_METHOD_DECLARE(read);
/* }}} */
/* {{{ proto Future StreamDescriptor::write(string data, [bool write_some = false],
* [callable callback], [mixed argument]);
* Write asynchronously to stream socket. */
P3_METHOD_DECLARE(write);
/* }}} */
/* {{{ proto void StreamDescriptor::release(void);
* Release ownership of the native descriptor implementation. */
P3_METHOD_DECLARE(release);
/* }}} */
/* {{{ proto int StreamDescriptor::cancel(void);
* Cancel all asynchronous operations on the descriptor. */
P3_METHOD_DECLARE(cancel);
/* }}} */
/* {{{ proto int StreamDescriptor::close(void);
* Close the descriptor. */
P3_METHOD_DECLARE(close);
/* }}} */
PHP_ASIO_CE_DECLARE();
};
}

69
src/timer.cpp Normal file
View File

@ -0,0 +1,69 @@
/**
* php-asio/timer.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include <boost/date_time.hpp>
#include "timer.hpp"
#include "future.hpp"
namespace asio
{
zval* timer::handler(const boost::system::error_code& error,
zval* callback, zval* argument)
{
PHP_ASIO_INVOKE_CALLBACK_START(3)
PHP_ASIO_INVOKE_CALLBACK();
PHP_ASIO_INVOKE_CALLBACK_END();
CORO_RETURN_NULL();
}
P3_METHOD(timer, expiresFromNow)
{
zend_long duration;
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_LONG(duration)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
timer_.expires_from_now(boost::posix_time::millisec(duration), ec);
RETVAL_EC(ec);
}
P3_METHOD(timer, expiresAt)
{
zend_long timestamp;
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_LONG(timestamp)
ZEND_PARSE_PARAMETERS_END();
boost::system::error_code ec;
timer_.expires_at(boost::posix_time::from_time_t(timestamp / 1000) +
boost::posix_time::millisec(timestamp % 1000), ec);
RETVAL_EC(ec);
}
P3_METHOD(timer, wait)
{
zval* callback = nullptr;
zval* argument = nullptr;
ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(callback)
Z_PARAM_ZVAL(argument)
ZEND_PARSE_PARAMETERS_END();
PHP_ASIO_FUTURE_INIT();
future->on_resolve<NOARG>(boost::bind(&timer::handler, this, _1, cb, args));
timer_.async_wait(STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG));
FUTURE_RETURN();
}
P3_METHOD(timer, cancel)
{
boost::system::error_code ec;
timer_.cancel(ec);
RETVAL_EC(ec);
}
PHP_ASIO_CE_DEFINE(timer);
}

52
src/timer.hpp Normal file
View File

@ -0,0 +1,52 @@
/**
* php-asio/timer.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "base.hpp"
namespace asio
{
/// Wrapper for Boost.Asio deadline timer.
class timer : public base
{
/// Boost.Asio timer instance.
boost::asio::deadline_timer timer_;
/// Handler for timer callback.
zval* handler(const boost::system::error_code& error,
zval* callback, zval* argument);
public:
/// Constructor.
explicit timer(
boost::asio::io_service& io_service
) : base(io_service), timer_(io_service) {}
/* {{{ proto int Timer::expiresFromNow(int duration);
* Set the timer's expiry time relative to now. */
P3_METHOD_DECLARE(expiresFromNow);
/* }}} */
/* {{{ proto int Timer::expiresFromNow(int timestamp);
* Set the timer's expiry time as an absolute time. */
P3_METHOD_DECLARE(expiresAt);
/* }}} */
/* {{{ proto Future Timer::wait([callable callback], [mixed argument]);
* Initiate an asynchronous wait against the timer. */
P3_METHOD_DECLARE(wait);
/* }}} */
/* {{{ proto int Timer::cancel(void);
* Cancel timer. */
P3_METHOD_DECLARE(cancel);
/* }}} */
PHP_ASIO_CE_DECLARE();
};
}

14
src/wrapped_handler.cpp Normal file
View File

@ -0,0 +1,14 @@
/**
* php-asio/wrapped_handler.cpp
*
* @author CismonX<admin@cismon.net>
*/
#include "wrapped_handler.hpp"
#ifdef ENABLE_STRAND
namespace asio
{
PHP_ASIO_CE_DEFINE(wrapped_handler);
}
#endif // ENABLE_STRAND

48
src/wrapped_handler.hpp Normal file
View File

@ -0,0 +1,48 @@
/**
* php-asio/wrapped_handler.hpp
*
* @author CismonX<admin@cismon.net>
*/
#pragma once
#include "common.hpp"
#include "strand.hpp"
#ifdef ENABLE_STRAND
namespace asio
{
/// Async handler callback wrapped by strand.
class wrapped_handler
{
/// Strand object for this wrapped handler.
strand* strand_;
/// Wrapped handler callback.
zval* callback_;
// Future has access to Strand and handler callback.
friend class future;
public:
/// Constructor.
explicit wrapped_handler(
strand* strand, zval* callback
) : strand_(strand), callback_(callback)
{
GC_ADDREF(Z_COUNTED_P(callback_));
}
/// Deleted default constructor.
wrapped_handler() = delete;
/// Deleted copy constructor.
wrapped_handler(const wrapped_handler&) = delete;
/// Make this object callable.
P3_METHOD_DECLARE(__invoke) {}
PHP_ASIO_CE_DECLARE();
};
}
#endif // ENABLE_STRAND

44
stubs/Acceptor.php Normal file
View File

@ -0,0 +1,44 @@
<?php
/**
* php-asio/stubs/Acceptor.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface Acceptor
*
* @package Asio
*/
interface Acceptor extends IoObject
{
/**
* Put the acceptor into the state where it may accept new connections.
*
* @param int $backlog[optional] : The maximum length of the queue of pending connections.
* Default to `boost::asio::socket_base::max_connections`.
* @return int : Error code
*/
function listen(int $backlog);
/**
* Asynchronously accept a new connection into a socket.
*
* @param callable $callback[optional] : Acceptor callback
* @param mixed $argument
* @return Future : Resolves Socket.
*/
function accept(callable $callback, $argument = null);
/**
* Close the acceptor.
*
* @return int : Error code
*/
function close();
}

29
stubs/DatagramSocket.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* php-asio/stubs/DatagramSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface DatagramSocket
*
* @package Asio
*/
interface DatagramSocket extends Socket
{
/**
* Receive asynchronously from datagram socket.
*
* @param int $length
* @param callable $callback[optional] : Receive handler callback
* @param mixed $argument
* @return Future
*/
function recvFrom(int $length, callable $callback, $argument = null);
}

27
stubs/Future.php Normal file
View File

@ -0,0 +1,27 @@
<?php
/**
* php-asio/stubs/Future.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class Future
*
* When an asynchronous operation completes, its Future will be resolved,
* resuming the corresponding coroutine (if Future was yielded by a Generator).
*
* @package Asio
*/
final class Future
{
/**
* This class can only be instantiated by asynchronous operations.
*/
private function __construct() {}
}

59
stubs/InetSocket.php Normal file
View File

@ -0,0 +1,59 @@
<?php
/**
* php-asio/stubs/InetSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface InetSocket
*
* @package Asio
*/
interface InetSocket extends Socket
{
/**
* Open socket.
*
* @param bool $inet6 : True for AF_INET6, false for AF_INET
* @return int : Error code
*/
function open(bool $inet6);
/**
* Assign an existing native socket to the socket.
*
* @param bool $inet6
* @param int|resource $native_handle
* @return int : Error code
*/
function assign(bool $inet6, $native_handle);
/**
* Bind socket to a local endpoint.
*
* @param string $address
* @param int $port
* @return int : Error code
*/
function bind(string $address, int $port);
/**
* Get remote endpoint address.
*
* @return string
*/
function remoteAddr();
/**
* Get remote endpoint port.
*
* @return mixed
*/
function remotePort();
}

28
stubs/IoObject.php Normal file
View File

@ -0,0 +1,28 @@
<?php
/**
* php-asio/stubs/IoObject.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface IoObject
*
* Base class of all I/O objects.
*
* @package Asio
*/
interface IoObject
{
/**
* Cancel all asynchronous operations on this I/O object.
*
* @return int : Error code
*/
function cancel();
}

49
stubs/LocalSocket.php Normal file
View File

@ -0,0 +1,49 @@
<?php
/**
* php-asio/stubs/LocalSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface LocalSocket
*
* @package Asio
*/
interface LocalSocket extends Socket
{
/**
* Open socket.
*
* @return int : Error code
*/
function open();
/**
* Assign an existing native socket to the socket.
*
* @param int|resource $native_handle
* @return int : Error code
*/
function assign($native_handle);
/**
* Bind socket to a local endpoint.
*
* @param string $path : Path to socket file
* @return mixed
*/
function bind(string $path);
/**
* Get remote endpoint path.
*
* @return string
*/
function remotePath();
}

30
stubs/Resolver.php Normal file
View File

@ -0,0 +1,30 @@
<?php
/**
* php-asio/stubs/Resolver.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface Resolver
*
* @package Asio
*/
interface Resolver extends IoObject
{
/**
* Initiate an asynchronous resolve against the resolver.
*
* @param string $host : Host name
* @param string $service
* @param callable $callback[optional] : Resolver callback
* @param mixed $argument
* @return Future
*/
function resolve(string $host, string $service = '', callable $callback, $argument = null);
}

202
stubs/Service.php Normal file
View File

@ -0,0 +1,202 @@
<?php
/**
* php-asio/stubs/Service.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class Service
*
* Provide access to instantiation of I/O objects.
*
* @package Asio
*/
class Service
{
/**
* Add a new timer.
*
* @return Timer
*/
function addTimer() {}
/**
* Add a new TCP acceptor.
*
* @return TcpAcceptor
*/
function addTcpAcceptor() {}
/**
* Add a new UNIX domain socket acceptor (SOCK_STREAM).
*
* @return UnixAcceptor
*/
function addUnixAcceptor() {}
/**
* Add a TCP resolver.
*
* @return TcpResolver
*/
function addTcpResolver() {}
/**
* Add a UDP resolver.
*
* @return UdpResolver
*/
function addUdpResolver() {}
/**
* Add a new TCP socket.
*
* @return TcpSocket
*/
function addTcpSocket() {}
/**
* Add a new UDP socket.
*
* @return UdpSocket
*/
function addUdpSocket() {}
/**
* Add a new UNIX domain socket (SOCK_STREAM).
*
* @return UnixSocket
*/
function addUnixSocket() {}
/**
* Add a new UNIX domain socket (SOCK_DGRAM).
*
* @return UdgSocket
*/
function addUdgSocket() {}
/**
* Add new signal set.
*
* @return Signal
*/
function addSignal() {}
/**
* Add new stream descriptor.
*
* @return StreamDescriptor
*/
function addStreamDescriptor() {}
/**
* Add new strand.
*
* @return Strand
*/
function addStrand() {}
/**
* Run the event loop until stopped or no more work.
*
* @param int $ec[optional] : Error code
* @return int : The number of handlers executed
*/
function run(int &$ec) {}
/**
* Run until stopped or one operation is performed.
*
* @param int $ec[optional] : Error code
* @return int : The number of handlers executed
*/
function runOne(int &$ec) {}
/**
* Poll for operations without blocking.
*
* @param int $ec[optional] : Error code
* @return int : The number of handlers executed
*/
function poll(int &$ec) {}
/**
* Poll for one operation without blocking.
*
* @param int $ec[optional] : Error code
* @return int : The number of handlers executed
*/
function pollOne(int &$ec) {}
/**
* Request invocation of the given handler and return immediately.
*
* @param callable $callback
* @param mixed $argument
* @return int : The number of handlers executed
*/
function post(callable $callback, $argument = null) {}
/**
* Request invocation of the given handler.
*
* @param callable $callback
* @param mixed $argument
* @return int : The number of handlers executed
*/
function dispatch(callable $callback, $argument = null) {}
/**
* Notify all services of a fork event. (prepare to fork)
*
* @return int : Error code
*/
function forkPrepare() {}
/**
* Notify all services of a fork event. (fork parent)
*
* @return int : Error code
*/
function forkParent() {}
/**
* Notify all services of a fork event. (fork child)
*
* @return int : Error code
*/
function forkChild() {}
/**
* Stop the event processing loop.
*
* @return void
*/
function stop() {}
/**
* Reset in preparation for a subsequent run invocation.
*
* @return void
*/
function reset() {}
/**
* Determine whether the io_service is stopped.
*/
function stopped() {}
/**
* Get last error code emitted by yielded async operations of this thread.
*
* @return int
*/
static function lastError() {}
}

63
stubs/Signal.php Normal file
View File

@ -0,0 +1,63 @@
<?php
/**
* php-asio/stubs/Signal.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class Signal
*
* Provides signal handling functionality.
*
* @package Asio
*/
final class Signal implements IoObject
{
/**
* This class can only be instantiated using `Service::addSignal()`.
*/
private function __construct() {}
/**
* Add the specified signal(s) to the signal set.
*
* @param int[] ...$signals[optional]
* @return int : Error code
*/
function add(int... $signals) {}
/**
* Initiate an asynchronous wait against the signal set.
*
* @param callable $callback[optional]
* @param mixed $argument
* @return Future
*/
function wait(callable $callback, $argument = null) {}
/**
* Remove the specified signal(s) from the signal set.
*
* @param int[] ...$signals[optional]
* @return int : Error code
*/
function remove(int... $signals) {}
/**
* Remove all signals from the signal set.
*
* @return int : Error code
*/
function clear() {}
/**
* {@inheritdoc}
*/
function cancel() {}
}

42
stubs/Socket.php Normal file
View File

@ -0,0 +1,42 @@
<?php
/**
* php-asio/stubs/Socket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface Socket
*
* @package Asio
*/
interface Socket extends IoObject
{
/**
* Determine the number of bytes available for reading.
*
* @param int $ec[optional] : Error code
* @return int
*/
function available(int &$ec);
/**
* Determine whether the socket is at the out-of-band data mark.
*
* @param int $ec[optional] : Error code
* @return bool
*/
function atMark(int &$ec);
/**
* Close socket.
*
* @return int : Error code
*/
function close();
}

59
stubs/Strand.php Normal file
View File

@ -0,0 +1,59 @@
<?php
/**
* php-asio/stubs/Strand.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class Strand
*
* Provides serialized handler execution.
*
* @package Asio
*/
final class Strand
{
/**
* This class can only be instantiated using `Service::addStrand()`.
*/
private function __construct() {}
/**
* Request the strand to invoke the given handler.
*
* @param callable $callback
* @param mixed $argument
* @return void
*/
function dispatch(callable $callback, $argument = null) {}
/**
* Request the strand to invoke the given handler and return immediately.
*
* @param callable $callback
* @param mixed $argument
* @return void
*/
function post(callable $callback, $argument = null) {}
/**
* Determine whether the strand is running in the current thread.
*
* @return bool
*/
function runningInThisThread() {}
/**
* Create a new handler that automatically dispatches the wrapped handler on the strand.
*
* @param callable $callback[optional]
* @return WrappedHandler
*/
function wrap(callable $callback) {}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* php-asio/stubs/StreamDescriptor.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class StreamDescriptor
*
* Provides stream-oriented descriptor functionality.
*
* @package Asio
*/
final class StreamDescriptor implements IoObject
{
/**
* This class can only be instantiated using `Service::addStreamDescriptor()`.
*/
private function __construct() {}
/**
* Opens the descriptor to hold an existing native descriptor.
*
* @param int|resource $native_handle
* @return int : Error code
*/
function assign($native_handle) {}
/**
* Determine whether the descriptor is open.
*
* @return bool
*/
function isOpen() {}
/**
* Read asynchronously from stream descriptor.
*
* @param int $length : Max number of bytes to be read
* @param bool $read_some
* @param callable $callback[optional] : Read handler callback
* @param mixed $argument
* @return Future : Resolves received data(string)
*/
function read(int $length, bool $read_some = true, callable $callback, $argument = null) {}
/**
* Write asynchronously to stream descriptor.
*
* @param string $data : Write buffer
* @param bool $write_some
* @param callable $callback[optional] : Write handler callback
* @param mixed $argument
* @return Future : Resolves bytes transferred(int)
*/
function write(string $data, bool $write_some = false, callable $callback, $argument = null) {}
/**
* Release ownership of the native descriptor implementation.
*
* @return void
*/
function release() {}
/**
* {@inheritdoc}
*/
function cancel() {}
}

41
stubs/StreamSocket.php Normal file
View File

@ -0,0 +1,41 @@
<?php
/**
* php-asio/stubs/StreamSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Interface StreamSocket
*
* @package Asio
*/
interface StreamSocket extends Socket
{
/**
* Read asynchronously from stream socket.
*
* @param int $length : Max number of bytes to be read
* @param bool $read_some
* @param callable $callback[optional] : Read handler callback
* @param mixed $argument
* @return Future : Resolves received data(string)
*/
function read(int $length, bool $read_some = true, callable $callback, $argument = null);
/**
* Write asynchronously to stream socket.
*
* @param string $data : Write buffer
* @param bool $write_some
* @param callable $callback[optional] : Write handler callback
* @param mixed $argument
* @return Future : Resolves bytes transferred(int)
*/
function write(string $data, bool $write_some = false, callable $callback, $argument = null);
}

72
stubs/TcpAcceptor.php Normal file
View File

@ -0,0 +1,72 @@
<?php
/**
* php-asio/stubs/TcpAcceptor.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class TcpAcceptor
*
* Used for accepting new TCP connections.
*
* @package Asio
*/
final class TcpAcceptor implements Acceptor
{
/**
* This class can only be instantiated using `Service::addTcpAcceptor()`.
*/
private function __construct() {}
/**
* Open acceptor.
*
* @param bool $use_ipv6 : True for AF_INET6, false for AF_INET
* @return int : Error code
*/
function open(bool $use_ipv6) {}
/**
* Assign an existing native socket to the acceptor.
*
* @param bool $inet6
* @param int|resource $native_handle
* @return int : Error code
*/
function assign(bool $inet6, $native_handle) {}
/**
* Bind acceptor to local endpoint.
*
* @param string $address
* @param int $port
* @return int : Error code
*/
function bind(string $address, int $port) {}
/**
* {@inheritdoc}
*/
function listen(int $backlog = null) {}
/**
* {@inheritdoc}
*/
function accept(callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

37
stubs/TcpResolver.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* php-asio/stubs/TcpResolver.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class TcpResolver
*
* Provides the ability to resolve a query to an array of endpoints.
* Remote service should use TCP as transport protocol.
*
* @package Asio
*/
final class TcpResolver implements Resolver
{
/**
* This class can only be instantiated using `Service::addTcpResolver()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function resolve(string $host, string $service = '', callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
}

91
stubs/TcpSocket.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* php-asio/stubs/TcpSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class TcpSocket
*
* Provides operations on TCP sockets.
*
* @package Asio
*/
final class TcpSocket implements StreamSocket, InetSocket
{
/**
* This class can only be instantiated using `Service::addTcpSocket()` and `TcpAcceptor::Accept()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function open(bool $inet6) {}
/**
* {@inheritdoc}
*/
function assign(bool $inet6, $native_handle) {}
/**
* {@inheritdoc}
*/
function bind(string $address, int $port) {}
/**
* Connect to a remote endpoint.
*
* @param string $address : Remote address
* @param int $port : Remote port
* @param callable $callback[optional]
* @param mixed $argument
*/
function connect(string $address, int $port, callable $callback, $argument = null) {}
/**
* {@inheritdoc}
*/
function read(int $length, bool $read_some = true, callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function write(string $data, bool $write_some = false, callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function remoteAddr() {}
/**
* {@inheritdoc}
*/
function remotePort() {}
/**
* {@inheritdoc}
*/
function available(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function atMark(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

54
stubs/Timer.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/**
* php-asio/stubs/Timer.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Wrapper for Boost.Asio deadline_timer.
*
* @package Asio
*/
final class Timer implements IoObject
{
/**
* This class can only be instantiated using "Service::addTimer()".
*/
private function __construct() {}
/**
* Set the timer's expiry time relative to now.
*
* @param int $duration : Milliseconds from now.
* @return int : Error code.
*/
function expiresFromNow(int $duration) {}
/**
* Set the timer's expiry time as an absolute time.
*
* @param int $timestamp : UNIX timestamp(in milliseconds).
* @return int : Error code.
*/
function expiresAt(int $timestamp) {}
/**
* Initiate an asynchronous wait against the timer.
*
* @param callable $callback[optional]
* @param mixed $argument
* @return Future : Resolves null
*/
function wait(callable $callback, $argument = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
}

82
stubs/UdgSocket.php Normal file
View File

@ -0,0 +1,82 @@
<?php
/**
* php-asio/stubs/UdgSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class UdgSocket
*
*
* @package Asio
*/
final class UdgSocket implements DatagramSocket, LocalSocket
{
/**
* This class can only be instantiated using `Service::addUdgSocket()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function open() {}
/**
* {@inheritdoc}
*/
function assign($native_handle) {}
/**
* {@inheritdoc}
*/
function bind(string $path) {}
/**
* {@inheritdoc}
*/
function recvFrom(int $length, callable $callback = null, $argument = null) {}
/**
* Send asynchronously to UNIX datagram socket.
*
* @param string $data : Data to send to datagram socket.
* @param string $path : Socket path of remote endpoint.
* @param callable $callback[optional]
* @param mixed $argument
* @return Future
*/
function sendTo(string $data, string $path, callable $callback, $argument = null) {}
/**
* {@inheritdoc}
*/
function remotePath() {}
/**
* {@inheritdoc}
*/
function available(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function atMark(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

37
stubs/UdpResolver.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* php-asio/stubs/UdpResolver.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class UdpResolver
*
* Provides the ability to resolve a query to an array of endpoints.
* Remote service should use UDP as transport protocol.
*
* @package Asio
*/
final class UdpResolver implements Resolver
{
/**
* This class can only be instantiated using `Service::addTcpResolver()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function resolve(string $host, string $service = '', callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
}

89
stubs/UdpSocket.php Normal file
View File

@ -0,0 +1,89 @@
<?php
/**
* php-asio/stubs/UdpSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class UdpSocket
*
* Provides operations on UDP sockets.
*
* @package Asio
*/
final class UdpSocket implements InetSocket, DatagramSocket
{
/**
* This class can only be instantiated using `Service::addUdpSocket()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function open(bool $inet6) {}
/**
* {@inheritdoc}
*/
function assign(bool $inet6, $native_handle) {}
/**
* {@inheritdoc}
*/
function bind(string $address, int $port) {}
/**
* {@inheritdoc}
*/
function recvFrom(int $length, callable $callback = null, $argument = null) {}
/**
* Send asynchronously to UNIX datagram socket.
*
* @param string $data : Data to send to datagram socket.
* @param string $address : Remote address
* @param int $port : Remote port
* @param callable $callback[optional]
* @param mixed $argument
* @return Future
*/
function sendTo(string $data, string $address, int $port, callable $callback, $argument = null) {}
/**
* {@inheritdoc}
*/
function remoteAddr() {}
/**
* {@inheritdoc}
*/
function remotePort() {}
/**
* {@inheritdoc}
*/
function available(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function atMark(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

69
stubs/UnixAcceptor.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/**
* php-asio/stubs/UnixAcceptor.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class UnixAcceptor
*
* Used for accepting new UNIX domain socket connections.
*
* @package Asio
*/
final class UnixAcceptor implements Acceptor
{
/**
* This class can only be instantiated using `Service::addUnixAcceptor()`.
*/
private function __construct() {}
/**
* Open acceptor.
*
* @return int : Error code.
*/
function open() {}
/**
* Assign an existing native socket to the acceptor.
*
* @param int|resource $native_handle
* @return int : Error code
*/
function assign($native_handle) {}
/**
* Bind acceptor to local endpoint.
*
* @param string $path
* @return int : Error code.
*/
function bind(string $path) {}
/**
* {@inheritdoc}
*/
function listen(int $backlog = null) {}
/**
* {@inheritdoc}
*/
function accept(callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

86
stubs/UnixSocket.php Normal file
View File

@ -0,0 +1,86 @@
<?php
/**
* php-asio/stubs/UnixSocket.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class UnixSocket
*
* Provides operations on UNIX domain sockets (SOCK_STREAM).
*
* @package Asio
*/
final class UnixSocket implements StreamSocket, LocalSocket
{
/**
* This class can only be instantiated using `Service::addUnixSocket()` and `UnixAcceptor::Accept()`.
*/
private function __construct() {}
/**
* {@inheritdoc}
*/
function open() {}
/**
* {@inheritdoc}
*/
function assign($native_handle) {}
/**
* {@inheritdoc}
*/
function bind(string $path) {}
/**
* Connect to a remote endpoint.
*
* @param string $path : Remote socket file path
* @param callable $callback[optional]
* @param mixed $argument
* @return int : Error code
*/
function connect(string $path, callable $callback, $argument = null) {}
/**
* {@inheritdoc}
*/
function read(int $length, bool $read_some = true, callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function write(string $data, bool $write_some = false, callable $callback = null, $argument = null) {}
/**
* {@inheritdoc}
*/
function remotePath() {}
/**
* {@inheritdoc}
*/
function available(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function atMark(int &$ec = null) {}
/**
* {@inheritdoc}
*/
function cancel() {}
/**
* {@inheritdoc}
*/
function close() {}
}

33
stubs/WrappedHandler.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/**
* php-asio/stubs/WrappedHandler.php
*
* This file is a stub of php-asio, which is implemented in extension asio.so.
* For source code of asio.so, see src/ directory.
*
* @author CismonX<admin@cismon.net>
*/
namespace Asio;
/**
* Class WrappedHandler
*
* Async handler callback wrapped by strand.
*
* @package Asio
*/
final class WrappedHandler
{
/**
* This class can only be instantiated using `Strand::wrap()`.
*/
private function __construct() {}
/**
* Making object callable.
*
* Note: Calling directly has no effect.
*/
function __invoke() {}
}

8
tests/00-load.phpt Normal file
View File

@ -0,0 +1,8 @@
--TEST--
Check for whether php-asio is loaded.
--FILE--
<?php
if (!extension_loaded('asio'))
echo 'php-asio not loaded.'
?>
--EXPECT--

18
tests/01-service.phpt Normal file
View File

@ -0,0 +1,18 @@
--TEST--
Test for `Service`.
--FILE--
<?php
$service = new Asio\Service();
$service->dispatch(function () use ($service) {
echo 'Service::dispatch()', PHP_EOL;
$service->post(function ($arg) use ($service) {
echo "Service::post($arg)";
}, 'foo');
});
$service->run($ec);
if ($ec)
echo 'Error on Service::run(). ', posix_strerror($ec);
?>
--EXPECT--
Service::dispatch()
Service::post(foo)

22
tests/02-timer.phpt Normal file
View File

@ -0,0 +1,22 @@
--TEST--
Test for `Timer`.
--FILE--
<?php
$service = new Asio\Service;
$timer = $service->addTimer();
if ($ec = $timer->expiresFromNow(200))
die('Timer::expire() failed. '.posix_strerror($ec));
$start_time = microtime(true);
$timer->wait(function ($timer, $ec) use ($start_time) {
if ($ec) {
echo 'Error on Timer::wait(). ', posix_strerror($ec);
return;
}
$end_time = microtime(true);
$duration = intval(1000 * ($end_time - $start_time));
if ($duration != 200)
echo "Bad duration. Expected 200ms, got ${duration}ms";
});
$service->run($ec);
?>
--EXPECT--

23
tests/03-signal.phpt Normal file
View File

@ -0,0 +1,23 @@
--TEST--
Test for `Signal`.
--FILE--
<?php
$service = new Asio\Service;
$signal = $service->addSignal();
if ($ec = $signal->add(SIGINT))
die('Signal::add() failed. '.posix_strerror($ec));
$signal->wait(function ($signal, $sig_num, $ec) use ($service) {
if ($ec) {
echo 'Error on Timer::wait(). ', posix_strerror($ec);
return;
}
echo $sig_num;
});
$service->post(function () {
if (!posix_kill(posix_getpid(), SIGINT))
exit;
});
$service->run();
?>
--EXPECT--
2

View File

@ -0,0 +1,46 @@
--TEST--
Test for binding socket to an endpoint and opening socket.
--ENV--
SOCK_FILE=/tmp/test-asio-socket-bind.sock
--SKIPIF--
<?php
if (!stream_socket_server('tcp://127.0.0.1:21348', $ec))
echo 'skip ', posix_strerror($ec);
?>
--FILE--
<?php
$service = new Asio\Service;
$socket = $service->addTcpSocket();
if ($ec = $socket->open(false))
die('TcpSocket::open() failed. '.posix_strerror($ec));
if ($ec = $socket->bind('127.0.0.1', 21348))
die('TcpSocket::bind() failed. '.posix_strerror($ec));
$socket->close();
$socket = $service->addUdpSocket();
if ($ec = $socket->open(false))
die('UdpSocket::open() failed. '.posix_strerror($ec));
if ($ec = $socket->bind('127.0.0.1', 21348))
die('UdpSocket::bind() failed. '.posix_strerror($ec));
$socket->close();
$socket = $service->addUnixSocket();
if ($ec = $socket->open())
die('UnixSocket::open() failed. '.posix_strerror($ec));
if ($ec = $socket->bind(getenv('SOCK_FILE')))
die('UnixSocket::bind() failed. '.posix_strerror($ec));
$socket->close();
$socket = $service->addUdgSocket();
if ($ec = $socket->open())
die('UdgSocket::open() failed. '.posix_strerror($ec));
if ($ec = $socket->bind(getenv('SOCK_FILE')))
die('UdgSocket::bind() failed. '.posix_strerror($ec));
$socket->close();
?>
--CLEAN--
<?php
unlink(getenv('SOCK_FILE'));
?>
--EXPECT--

63
tests/05-acceptor.phpt Normal file
View File

@ -0,0 +1,63 @@
--TEST--
Test for `Acceptor`.
--ENV--
SOCK_FILE=/tmp/test-asio-socket-bind.sock
--SKIPIF--
<?php
if (!stream_socket_server('tcp://127.0.0.1:21348', $ec))
echo 'skip ', posix_strerror($ec);
?>
--FILE--
<?php
$service = new Asio\Service;
$acceptor = $service->addTcpAcceptor();
if ($ec = $acceptor->open(false))
die('TcpAcceptor::open() failed. '.posix_strerror($ec));
if ($ec = $acceptor->bind('127.0.0.1', 21348))
die('TcpAcceptor::bind() failed. '.posix_strerror($ec));
if ($ec = $acceptor->listen())
die('TcpAcceptor::listen() failed. '.posix_strerror($ec));
$acceptor->accept(function ($acceptor, $socket, $ec) use ($service) {
if ($ec) {
echo 'Error on TcpAcceptor::accept. ', posix_strerror($ec);
return;
}
echo 'Accepted', PHP_EOL;
$service->post(function () use ($service) {
$acceptor = $service->addUnixAcceptor();
if ($ec = $acceptor->open())
die('UnixAcceptor::open() failed. '.posix_strerror($ec));
if ($ec = $acceptor->bind(getenv('SOCK_FILE')))
die('UnixAcceptor::bind() failed. '.posix_strerror($ec));
if ($ec = $acceptor->listen())
die('UnixAcceptor::listen() failed. '.posix_strerror($ec));
$acceptor->accept(function ($acceptor, $socket, $ec) {
if ($ec) {
echo 'Error on UnixAcceptor::accept. ', posix_strerror($ec);
return;
}
echo 'Accepted';
});
$service->post(function () use ($service) {
if ($handle = stream_socket_client('unix://'.getenv('SOCK_FILE')))
fclose($handle);
else
exit;
});
});
});
$service->post(function () use ($service) {
if ($handle = stream_socket_client('tcp://127.0.0.1:21348'))
fclose($handle);
else
exit;
});
$service->run();
?>
--CLEAN--
<?php
unlink(getenv('SOCK_FILE'));
?>
--EXPECT--
Accepted
Accepted

View File

@ -0,0 +1,53 @@
--TEST--
Test for `DatagramSocket::sendTo()` and `DatagramSocket::recvFrom()`.
--ENV--
SOCK_FILE_1=/tmp/test-asio-socket-bind-1.sock
SOCK_FILE_2=/tmp/test-asio-socket-bind-2.sock
--FILE--
<?php
$service = new Asio\Service;
$service->post(function () use ($service) {
$socket = $service->addUdgSocket();
$socket->open();
$socket->bind(getenv('SOCK_FILE_1'));
$socket->recvFrom(100,
function ($socket, $data, $remote_path, $ec) {
if ($ec) {
echo 'UdgSocket::recvFrom() failed. ', posix_strerror($ec);
$socket->close();
return;
}
echo $data;
$socket->sendTo('world', $remote_path, function ($socket, $length, $ec) {
if ($ec) {
echo 'UdgSocket::sendTo() failed. ', posix_strerror($ec);
$socket->close();
return;
}
$socket->close();
});
});
$socket = $service->addUdgSocket();
$socket->open();
$socket->bind(getenv('SOCK_FILE_2'));
$socket->sendTo('hello', getenv('SOCK_FILE_1'), function ($socket) {
$socket->recvFrom(100, function ($socket, $data, $remote_path, $ec) {
if ($ec) {
echo 'UdgSocket::recvFrom() failed. ', posix_strerror($ec);
$socket->close();
return;
}
echo $data;
$socket->close();
});
});
});
$service->run();
?>
--CLEAN--
<?php
unlink(getenv('SOCK_FILE_1'));
unlink(getenv('SOCK_FILE_2'));
?>
--EXPECT--
helloworld

22
tests/07-future.phpt Normal file
View File

@ -0,0 +1,22 @@
--TEST--
Test for `Future` and `Service::lastError()`.
--SKIPIF--
<?php
if (!class_exists('Asio\Future'))
echo 'skip coroutine is disabled';
?>
--FILE--
<?php
$service = new Asio\Service;
$service->post(function () use ($service) {
$signal = $service->addSignal();
$signal->add(SIGINT);
$service->post(function () {
posix_kill(posix_getpid(), SIGINT);
});
echo yield $signal->wait();
});
$service->run();
?>
--EXPECT--
2

View File

@ -0,0 +1,41 @@
--TEST--
Test for `assign()` on `Socket`, `Acceptor` and `StreamDescriptor`.
Test for `StreamSocket::read()` and `StreamSocket::write()`.
--SKIPIF--
<?php
if (!stream_socket_server('tcp://127.0.0.1:21348', $ec))
echo 'skip ', posix_strerror($ec);
?>
--FILE--
<?php
$handle = stream_socket_server('tcp://127.0.0.1:21348');
$service = new Asio\Service;
$acceptor = $service->addTcpAcceptor();
if ($ec = $acceptor->assign(false, $handle))
die('Acceptor::assign() failed. '.posix_strerror($ec));
$acceptor->accept(function ($acceptor, $socket, $ec) use ($service) {
if ($ec) {
echo 'Error on TcpAcceptor::accept(). ', posix_strerror($ec);
return;
}
$socket->read(5, false, function ($socket, $data, $ec) use ($service) {
if ($ec) {
echo 'TcpSocket::read() failed.', posix_strerror($ec);
return;
}
echo $data;
$stream_descriptor = $service->addStreamDescriptor();
$stream_descriptor->assign(STDOUT);
$stream_descriptor->write('world');
});
});
$service->post(function () use ($service) {
$handle = stream_socket_client('tcp://127.0.0.1:21348');
$socket = $service->addTcpSocket();
$socket->assign(false, $handle);
$socket->write('hello');
});
$service->run();
?>
--EXPECT--
helloworld

19
tests/09-resolver.phpt Normal file
View File

@ -0,0 +1,19 @@
--TEST--
Test for `Resolver`.
--FILE--
<?php
$service = new Asio\Service;
$service->post(function () use ($service) {
$resolver = $service->addTcpResolver();
$resolver->resolve('github.com', 'http', function ($resolver, $arr, $ec) {
if ($ec = Asio\Service::lastError()) {
echo 'Resolver::resolve() failed. ', posix_strerror($ec);
return;
}
echo boolval(count($arr));
});
});
$service->run();
?>
--EXPECT--
1

38
tests/10-fork.phpt Normal file
View File

@ -0,0 +1,38 @@
--TEST--
Test for forking.
--FILE--
<?php
$service = new Asio\Service;
function fork_worker($service, $signal)
{
if ($ec = $service->forkPrepare())
die('Service::forkParent() failed. '. posix_strerror($ec));
if (pcntl_fork()) {
if ($ec = $service->forkParent())
die('Service::forkParent() failed. '. posix_strerror($ec));
$signal->wait(function () {
if (pcntl_wait($status, WUNTRACED) < 1)
die('An error occurred during wait().');
echo $status;
});
echo 'parent ';
} else {
if ($ec = $service->forkChild())
die('Service::forkParent() failed. '. posix_strerror($ec));
$signal->cancel();
$timer = $service->addTimer();
$timer->expiresFromNow(100);
$timer->wait($timer_cb = function ($timer) use (&$timer_cb) {
echo 'child ';
});
}
}
$service->post(function () use ($service) {
$signal = $service->addSignal();
$signal->add(SIGCHLD);
fork_worker($service, $signal);
});
$service->run();
?>
--EXPECT--
parent child 0

24
tests/11-strand.phpt Normal file
View File

@ -0,0 +1,24 @@
--TEST--
Test for `Strand`.
--SKIPIF--
<?php
if (!class_exists(Asio\Strand::class))
echo 'skip Strand is not enabled';
?>
--FILE--
<?php
// Just test whether Strand works.
// We do not test multi-threading because is not yet supported.
$service = new Asio\Service;
$strand = $service->addStrand();
$strand->post(function () use ($service, $strand) {
$timer = $service->addTimer();
$timer->expiresFromNow(100);
$timer->wait($strand->wrap(function () {
echo 'ok';
}));
});
$service->run();
?>
--EXPECT--
ok