整理目录
144
docs/note/learn/PHP扩展开发/01-骨架.md
Normal file
@ -0,0 +1,144 @@
|
||||
---
|
||||
title: PHP 扩展开发(一)-骨架
|
||||
---
|
||||
|
||||
> 学习了这么久的 php,还一直停留在 CURD 也太捞了,来接触一下扩展开发
|
||||
> 官方的文档:[http://php.net/manual/zh/internals2.php](http://php.net/manual/zh/internals2.php) 可以 mark 一下
|
||||
|
||||
## 环境
|
||||
|
||||
- php7.2
|
||||
- ubuntu18.04
|
||||
- gcc 7.3.0
|
||||
- make 4.1
|
||||
|
||||
## 开始
|
||||
|
||||
### ext_skel
|
||||
|
||||
> [http://php.net/manual/zh/internals2.buildsys.skeleton.php](http://php.net/manual/zh/internals2.buildsys.skeleton.php)
|
||||
|
||||
首先我们要利用 php 给我们提供的 ext_skel 脚本工具生成我们扩展的骨架,这个文件一般在 php 的源码的 ext 目录下面
|
||||
|
||||
这里我弄一个扩展名字为**study**的扩展 --extname 这个参数为扩展名字,还有其他的参数可以在上面的链接文档中看到
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext$ sudo ./ext_skel --extname=study
|
||||
Creating directory study
|
||||
Creating basic files: config.m4 config.w32 .gitignore study.c php_study.h CREDITS EXPERIMENTAL tests/001.phpt study.php [done].
|
||||
|
||||
To use your new extension, you will have to execute the following steps:
|
||||
|
||||
1. $ cd ..
|
||||
2. $ vi ext/study/config.m4
|
||||
3. $ ./buildconf
|
||||
4. $ ./configure --[with|enable]-study
|
||||
5. $ make
|
||||
6. $ ./sapi/cli/php -f ext/study/study.php
|
||||
7. $ vi ext/study/study.c
|
||||
8. $ make
|
||||
|
||||
Repeat steps 3-6 until you are satisfied with ext/study/config.m4 and
|
||||
step 6 confirms that your module is compiled into PHP. Then, start writing
|
||||
code and repeat the last two steps as often as necessary.
|
||||
|
||||
```
|
||||
|
||||
这里创建完成之后,就多了一个 study 的目录,我们可以进入这个扩展目录,进行一些操作,这里的话,因为我的用户没有权限,所以我直接的给了 777 权限
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext$ sudo chmod -R 777 study/
|
||||
```
|
||||
|
||||
### 编译安装
|
||||
|
||||
#### 目录结构
|
||||
|
||||
之后我们进入这个扩展目录,有这些文件,官方的文档:[http://php.net/manual/zh/internals2.structure.files.php](http://php.net/manual/zh/internals2.structure.files.php)
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext$ cd study/
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ ls
|
||||
config.m4 CREDITS php_study.h study.php
|
||||
config.w32 EXPERIMENTAL study.c tests
|
||||
```
|
||||
|
||||
等下我们需要修改**config.m4**文件,这相当于是一个编译配置的文档,是 Unix 下的,还有一个**config.w32**看名字就知道是 Windows 下的
|
||||
|
||||
**study.c**和**php_study.h**这是依照我们的扩展名称来帮我们生成的两个源文件,包括了一些宏的定义和函数声明等等
|
||||
|
||||
**study.php**可以用 php cli 来测试我们的扩展是否安装成功
|
||||
|
||||
#### config.m4
|
||||
|
||||
> [http://php.net/manual/zh/internals2.buildsys.configunix.php](http://php.net/manual/zh/internals2.buildsys.configunix.php)
|
||||
|
||||
要进行一下修改,是动态编译成 so 库还是静态编译进 php 里面,这里我们扩展开发自然是动态编译成 so 库,不然还得重新编译 php
|
||||
|
||||
去掉前面的 dnl,例如下面这个
|
||||
|
||||
```
|
||||
dnl If your extension references something external, use with:
|
||||
# 编译成so库
|
||||
PHP_ARG_WITH(study, for study support,
|
||||
Make sure that the comment is aligned:
|
||||
[ --with-study Include study support])
|
||||
|
||||
dnl Otherwise use enable:
|
||||
# 静态编译
|
||||
dnl PHP_ARG_ENABLE(study, whether to enable study support,
|
||||
dnl Make sure that the comment is aligned:
|
||||
dnl [ --enable-study Enable study support])
|
||||
```
|
||||
|
||||
### 编译
|
||||
|
||||
我们需要用**phpize**生成编译的配置的文件**configure**,然后**make**,**make install**就完成了,make install 的时候注意用 sudo
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ phpize
|
||||
Configuring for:
|
||||
PHP Api Version: 20170718
|
||||
Zend Module Api No: 20170718
|
||||
Zend Extension Api No: 320170718
|
||||
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ ./configure --with-php-config=/www/server/php/72/bin/php-config
|
||||
# 这里注意配置php的配置文件
|
||||
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ make
|
||||
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ sudo make install
|
||||
Installing shared extensions: /www/server/php/72/lib/php/extensions/no-debug-non-zts-20170718/
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
完成之后我们要在**php.ini**文件中加载扩展,不然我们运行`php study.php`的时候不会成功
|
||||
|
||||
这样可以看 ini 文件在哪里
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ php --ini
|
||||
Configuration File (php.ini) Path: /www/server/php/72/etc
|
||||
Loaded Configuration File: /www/server/php/72/etc/php.ini
|
||||
Scan for additional .ini files in: (none)
|
||||
Additional .ini files parsed: (none)
|
||||
```
|
||||
|
||||
增加一行
|
||||
|
||||
```
|
||||
extension=study.so
|
||||
```
|
||||
|
||||
## 完成
|
||||
|
||||
这时候我们执行目录下的那个`study.php`
|
||||
|
||||
```shell
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ php study.php
|
||||
Functions available in the test extension:
|
||||
confirm_study_compiled
|
||||
|
||||
Congratulations! You have successfully modified ext/study/config.m4. Module study is now compiled into PHP.
|
||||
```
|
237
docs/note/learn/PHP扩展开发/02-函数.md
Normal file
@ -0,0 +1,237 @@
|
||||
---
|
||||
title: PHP扩展开发(二)-函数
|
||||
---
|
||||
|
||||
> 弄好骨架之后,我们得给我们的扩展增加些 php 能够调用的函数,这里我们使用 vscode 进行开发
|
||||
|
||||
## 开发环境
|
||||
|
||||
> 给我们的 vscode 装好扩展,然后配置一下 include 路径
|
||||
|
||||

|
||||
|
||||
点击,生成一个配置文件,我的配置如下,php 的路径看自己的来定,我这里是宝塔安装的,路径为:**/www/server/php/72/include/php**,主要是自动提示
|
||||
|
||||
```json
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/www/server/php/72/include/php",
|
||||
"/www/server/php/72/include/php/main",
|
||||
"/www/server/php/72/include/php/Zend",
|
||||
"/www/server/php/72/include/php/TSRM"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
```
|
||||
|
||||
## 开发
|
||||
|
||||
### 返回和输出
|
||||
|
||||
> 假设我这里想添加一个输出一些东西的函数
|
||||
|
||||
这里用到了三个宏,**PHP_FUNCTION**,**RETURN\_\***,**PHP_FE**
|
||||
|
||||
php_printf 是 php 提供的一个输出函数
|
||||
|
||||
```c
|
||||
PHP_FUNCTION(study_ext_print) {
|
||||
php_printf("我是输出到页面的内容\n");
|
||||
RETURN_STRING("学习PHP扩展开发~~");
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ study_functions[]
|
||||
*
|
||||
* Every user visible function must have an entry in study_functions[].
|
||||
*/
|
||||
const zend_function_entry study_functions[] = {
|
||||
PHP_FE(confirm_study_compiled, NULL) /* For testing, remove later. */
|
||||
PHP_FE(study_ext_print, NULL) /* 学习插件输出 */
|
||||
PHP_FE_END /* Must be the last line in study_functions[] */
|
||||
};
|
||||
/* }}} */
|
||||
```
|
||||
|
||||
然后再 make,sudo make install
|
||||
|
||||
#### 调试跑一次
|
||||
|
||||
修改我们目录下的`study.php`,在最后写一行,我们刚刚编写的这个函数
|
||||
|
||||
```php
|
||||
echo study_ext_print()."\n";
|
||||
```
|
||||
|
||||
输出,成功
|
||||
|
||||
```
|
||||
huanl@huanl-CN15S:/www/server/php/72/src/ext/study$ php study.php
|
||||
Functions available in the test extension:
|
||||
confirm_study_compiled
|
||||
study_ext_print
|
||||
|
||||
Congratulations! You have successfully modified ext/study/config.m4. Module study is now compiled into PHP.
|
||||
我是输出到页面的内容
|
||||
学习PHP扩展开发~~
|
||||
```
|
||||
|
||||
#### 这里再说下这三个宏
|
||||
|
||||
##### PHP_FUNCTION
|
||||
|
||||
使用这个宏会将我们的函数最终定义成如下的形式
|
||||
|
||||
```cpp
|
||||
void zif_study_ext_print(zend_execute_data *execute_data, zval *return_value)
|
||||
```
|
||||
|
||||
~~官网上的是 php5.3 的版本,我这里是 php7,所以是这样,如果有什么错误,还望指出~~
|
||||
然后因为这里的返回值是 void,所以在我们写函数的时候`return`不能带值
|
||||
|
||||
##### RETURN\_\*
|
||||
|
||||
这个宏一看就知道是 php 给我们的返回值,除了我上面所写的`RETURN_STR`外还有其他的类型
|
||||
|
||||
```c
|
||||
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
|
||||
#define RETURN_NULL() { RETVAL_NULL(); return;}
|
||||
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
|
||||
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
|
||||
#define RETURN_STR(s) { RETVAL_STR(s); return; }
|
||||
#define RETURN_INTERNED_STR(s) { RETVAL_INTERNED_STR(s); return; }
|
||||
#define RETURN_NEW_STR(s) { RETVAL_NEW_STR(s); return; }
|
||||
#define RETURN_STR_COPY(s) { RETVAL_STR_COPY(s); return; }
|
||||
#define RETURN_STRING(s) { RETVAL_STRING(s); return; }
|
||||
#define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; }
|
||||
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
|
||||
#define RETURN_RES(r) { RETVAL_RES(r); return; }
|
||||
#define RETURN_ARR(r) { RETVAL_ARR(r); return; }
|
||||
#define RETURN_OBJ(r) { RETVAL_OBJ(r); return; }
|
||||
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
|
||||
#define RETURN_FALSE { RETVAL_FALSE; return; }
|
||||
#define RETURN_TRUE { RETVAL_TRUE; return; }
|
||||
```
|
||||
|
||||
##### PHP_FE
|
||||
|
||||
这个宏帮助我们生成一个和 php 函数相关的结构体
|
||||
|
||||
```c
|
||||
//宏如下,PHP_替换成了ZEND_
|
||||
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
|
||||
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
|
||||
//感觉就是帮我们省事了,不需要我们去重写结构体,上面那些结构体也是
|
||||
typedef struct _zend_function_entry {
|
||||
const char *fname;//我们的php函数名
|
||||
zif_handler handler;//相当于再调用一次PHP_FUNCTION,c中函数的指针
|
||||
const struct _zend_internal_arg_info *arg_info;//参数的信息,就上一个函数来说,我们是NULL
|
||||
uint32_t num_args;//参数个数
|
||||
uint32_t flags;//flags这里是0
|
||||
} zend_function_entry;
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
> 假设来一个两数相加的函数,这时候我们就需要传送参数了
|
||||
> 参考:[https://wiki.php.net/rfc/fast_zpp](https://wiki.php.net/rfc/fast_zpp)
|
||||
|
||||
我的 c 代码如下
|
||||
|
||||
```cpp
|
||||
//定义参数结构
|
||||
ZEND_BEGIN_ARG_INFO(add_param,0)
|
||||
ZEND_ARG_INFO(0,num1)
|
||||
ZEND_ARG_INFO(0,num2)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
//函数
|
||||
PHP_FUNCTION(study_add)
|
||||
{
|
||||
long long num1=0,num2=0;
|
||||
if(zend_parse_parameters(ZEND_NUM_ARGS() , "ll", &num1,&num2)==FAILURE){
|
||||
RETURN_LONG(-1)
|
||||
}
|
||||
RETURN_LONG(num1+num2)
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ study_functions[]
|
||||
*
|
||||
* Every user visible function must have an entry in study_functions[].
|
||||
*/
|
||||
const zend_function_entry study_functions[] = {
|
||||
PHP_FE(confirm_study_compiled, NULL) /* For testing, remove later. */
|
||||
PHP_FE(study_ext_print, NULL) /* 学习插件输出 */
|
||||
PHP_FE(study_add, add_param) /* 两数相加 */
|
||||
PHP_FE_END /* Must be the last line in study_functions[] */
|
||||
};
|
||||
```
|
||||
|
||||
```php
|
||||
echo "study_add:".study_add(10,123456);
|
||||
```
|
||||
|
||||
#### 宏
|
||||
|
||||
##### ZEND_BEGIN_ARG_INFO,ZEND_ARG_INFO,ZEND_END_ARG_INFO
|
||||
|
||||
定义参数,我把源码贴上来
|
||||
|
||||
```cpp
|
||||
#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
|
||||
static const zend_internal_arg_info name[] = { \
|
||||
{ (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 },
|
||||
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
|
||||
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
|
||||
#define ZEND_END_ARG_INFO() };
|
||||
|
||||
#define ZEND_ARG_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 0},
|
||||
```
|
||||
|
||||
那么通过这些宏的转换,变成了这样
|
||||
|
||||
```cpp
|
||||
static const zend_internal_arg_info add_param[] = {
|
||||
{ (const char*)(zend_uintptr_t)(-1), 0, 0, 0 },
|
||||
{ "num1", 0, 0, 0},
|
||||
{ "num2", 0, 0, 0},
|
||||
};
|
||||
```
|
||||
|
||||
#### zend_parse_parameters
|
||||
|
||||
获取参数
|
||||
|
||||
```cpp
|
||||
//声明
|
||||
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...);
|
||||
```
|
||||
|
||||
第一个参数是我们要获取的参数的个数,第二个是 参数的格式化字符串,后面的是变量指针
|
||||
|
||||
| 数据类型 | 字符 | c 对应类型 |
|
||||
| -------- | ---- | ----------- |
|
||||
| Boolean | b | zend_bool |
|
||||
| Long | l | long |
|
||||
| Double | d | double |
|
||||
| String | s | char\*, int |
|
||||
| Resource | r | zval\* |
|
||||
| Array | a | zval\* |
|
||||
| Object | o | zval\* |
|
||||
| zval | z | zval\* |
|
||||
|
||||
#### ZEND_NUM_ARGS
|
||||
|
||||
参数个数,一般这样填就好了
|
385
docs/note/learn/PHP扩展开发/03-类.md
Normal file
@ -0,0 +1,385 @@
|
||||
---
|
||||
title: PHP扩展开发(三)---类
|
||||
---
|
||||
|
||||
> 前面已经了解了函数和参数,今天来了解一下类
|
||||
|
||||
## 例子
|
||||
|
||||
定义了一个 **study_ext_class** 类,里面只有一个 **print** 方法
|
||||
|
||||
类使用 **PHP_ME**和**PHP_METHOD** 宏,与方法最大的不同的地方是类需要注册
|
||||
|
||||
这里我写了一个 **init_class** 方法,**PHP_MINIT_FUNCTION**中调用,主要是需要注册类
|
||||
|
||||
```c
|
||||
/* {{{ PHP_MINIT_FUNCTION
|
||||
*/
|
||||
PHP_MINIT_FUNCTION(study)
|
||||
{
|
||||
/* If you have INI entries, uncomment these lines
|
||||
REGISTER_INI_ENTRIES();
|
||||
*/
|
||||
init_class();
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
PHP_METHOD(study_ext_class,print)
|
||||
{
|
||||
php_printf("你调用了study_ext_class的print方法\n");
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
const zend_function_entry study_class_method[]={
|
||||
PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
|
||||
PHP_FE_END
|
||||
};
|
||||
|
||||
zend_class_entry *study_ce;
|
||||
void init_class(){
|
||||
zend_class_entry ce;
|
||||
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
|
||||
study_ce = zend_register_internal_class(&ce);
|
||||
}
|
||||
```
|
||||
|
||||
### PHP_MINIT_FUNCTION
|
||||
|
||||
这是我们扩展启动时会执行的一个函数,所以在这里注册类
|
||||
|
||||
```c
|
||||
#define ZEND_MODULE_STARTUP_N(module) zm_startup_##module
|
||||
|
||||
#define INIT_FUNC_ARGS int type, int module_number
|
||||
|
||||
#define ZEND_MODULE_STARTUP_D(module) int ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
|
||||
//之后
|
||||
int zm_startup_study(int type, int module_number);
|
||||
```
|
||||
|
||||
### PHP_METHOD
|
||||
|
||||
这两个宏和我们前面函数哪里的 **PHP_FE** ,**PHP_FUNCTION** 差不多
|
||||
|
||||
```c
|
||||
#define ZEND_MN(name) zim_##name
|
||||
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
|
||||
|
||||
//PHP_METHOD 最终定义成了这样
|
||||
void zim_study_ext_class_print(zend_execute_data *execute_data, zval *return_value);
|
||||
```
|
||||
|
||||
### PHP_ME
|
||||
|
||||
**PHP_ME** 相比原来的 **PHP_FE** 多了几个参数,主要是方法的属性和类名
|
||||
|
||||
```c
|
||||
#define ZEND_ME(classname, name, arg_info, flags) ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)
|
||||
```
|
||||
|
||||
flags 是方法的属性,我们可以用 **|** 连接它们
|
||||
|
||||
```c
|
||||
/* method flags (types)方法类型 */
|
||||
#define ZEND_ACC_STATIC 0x01
|
||||
#define ZEND_ACC_ABSTRACT 0x02
|
||||
#define ZEND_ACC_FINAL 0x04
|
||||
#define ZEND_ACC_IMPLEMENTED_ABSTRACT 0x08
|
||||
|
||||
/* method flags (visibility)访问属性 */
|
||||
/* The order of those must be kept - public < protected < private */
|
||||
#define ZEND_ACC_PUBLIC 0x100
|
||||
#define ZEND_ACC_PROTECTED 0x200
|
||||
#define ZEND_ACC_PRIVATE 0x400
|
||||
#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
|
||||
|
||||
#define ZEND_ACC_CHANGED 0x800
|
||||
#define ZEND_ACC_IMPLICIT_PUBLIC 0x1000
|
||||
|
||||
/* method flags (special method detection)构造函数和析构 */
|
||||
#define ZEND_ACC_CTOR 0x2000
|
||||
#define ZEND_ACC_DTOR 0x4000
|
||||
|
||||
/* method flag used by Closure::__invoke() */
|
||||
#define ZEND_ACC_USER_ARG_INFO 0x80
|
||||
|
||||
/* method flag (bc only), any method that has this flag can be used statically and non statically. */
|
||||
#define ZEND_ACC_ALLOW_STATIC 0x10000
|
||||
```
|
||||
|
||||
### 类注册
|
||||
|
||||
完成了上面的,编译&安装,实例化我们的类是是会报错的,因为 php 不知道你有那些类
|
||||
|
||||
方法的话,在最后面通过 **ZEND_GET_MODULE** 生成了一个 **get_module** 的接口将方法进行了返回
|
||||
|
||||
```c
|
||||
zend_class_entry *study_ce;
|
||||
void init_class(){
|
||||
zend_class_entry ce;
|
||||
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
|
||||
study_ce = zend_register_internal_class(&ce);
|
||||
}
|
||||
```
|
||||
|
||||
#### INIT_CLASS_ENTRY
|
||||
|
||||
这一个宏的作用是生成这个类的结构,包括类的名称,方法,然后返回在 ce 这个变量中
|
||||
|
||||
```c
|
||||
//太长了,就只贴了一部分
|
||||
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \
|
||||
INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, sizeof(class_name)-1, functions, handle_fcall, handle_propget, handle_propset, NULL, NULL)
|
||||
|
||||
#define INIT_CLASS_ENTRY(class_container, class_name, functions) \
|
||||
INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL)
|
||||
```
|
||||
|
||||
#### zend_register_internal_class
|
||||
|
||||
显而易见的是将我们的类的信息告诉 php,注册进去,还有一个 **zend_register_internal_class_ex** 的函数,可以指定父类,然后这个函数返回我们的这个类的指针
|
||||
|
||||
## 构造和析构
|
||||
|
||||
函数列表哪里标记一下 **ZEND_ACC_CTOR** 或者 **ZEND_ACC_DTOR** 就好了
|
||||
|
||||
```c
|
||||
PHP_METHOD(study_ext_class,__construct)
|
||||
{
|
||||
php_printf("study_ext_class构造函数\n");
|
||||
}
|
||||
PHP_METHOD(study_ext_class,__destruct)
|
||||
{
|
||||
php_printf("study_ext_class析构函数\n");
|
||||
}
|
||||
const zend_function_entry study_class_method[]={
|
||||
PHP_ME(study_ext_class,__construct,NULL,ZEND_ACC_CTOR)/* 构造 */
|
||||
PHP_ME(study_ext_class,__destruct,NULL,ZEND_ACC_DTOR)/* 析构 */
|
||||
PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
|
||||
PHP_FE_END
|
||||
};
|
||||
```
|
||||
|
||||
## 类属性
|
||||
|
||||
在初始化注册类的时候,使用 **zend*declare_property*\*** 给我们的类添加属性,还可以给他们赋予默认值
|
||||
|
||||
```c
|
||||
void init_class(){
|
||||
zend_class_entry ce;
|
||||
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
|
||||
study_ce = zend_register_internal_class(&ce);
|
||||
zend_declare_property_null(study_ce,"attr",sizeof("attr")-1,ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_long(study_ce,"num",sizeof("num")-1,100,ZEND_ACC_STATIC|ZEND_ACC_PUBLIC);
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
$a->attr="2333s";
|
||||
echo '静态属性:study_ext_class::$num:'.study_ext_class::$num."\n";
|
||||
echo '属性:study_ext_class::$attr:'.$a->attr."\n";
|
||||
```
|
||||
|
||||
输出
|
||||
|
||||
```
|
||||
静态属性:study_ext_class::$num:100
|
||||
属性:study_ext_class::$attr:2333s
|
||||
```
|
||||
|
||||
### 类指针和属性读取
|
||||
|
||||
上面写了怎么去定义属性,但是如果是在类里面要怎么使用属性呢?我们需要用一个 **getThis** 的宏来获取当前这个类的指针,我就偷懒直接在原来的 **print** 中添加了
|
||||
|
||||
```c
|
||||
zval *attr;
|
||||
attr=zend_read_property(Z_OBJCE_P(getThis()),getThis(),"attr",sizeof("attr")-1,0,NULL);
|
||||
php_var_dump(attr, 1);
|
||||
if(Z_TYPE_P(attr)==IS_STRING){
|
||||
php_printf("attr的值为:%s\n",attr->value.str->val);
|
||||
}
|
||||
```
|
||||
|
||||
#### zend_read_property
|
||||
|
||||
这个函数用于获取属性,还有`zend_read_static_property`,用法相同,不过这个是获取静态的属性,关于更新属性可以使用`zend_update_property`
|
||||
|
||||
```c
|
||||
ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv);
|
||||
```
|
||||
|
||||
第一个参数 **scope** 是这个类的指针,在之前的`study_ce = zend_register_internal_class(&ce);`获取,不过也可以这样获取`Z_OBJCE_P(getThis())`
|
||||
|
||||
第二个参数 **object** 是当前的对象,我们可以用`getThis`这个宏获取
|
||||
|
||||
第三个参数和第四个参数分别是 属性的名称和属性的长度
|
||||
|
||||
第五个参数 **silent** 用于是假设属性不存在的情况下是否报错
|
||||
|
||||
最后一个参数 **rv** 为魔术方法所返回的,如果不是魔术方法所返回的是一个**NULL**值,可以看我下面这个例子
|
||||
|
||||
```c
|
||||
zval *attr,*rv=NULL;
|
||||
attr=zend_read_property(study_ce,getThis(),"attr",sizeof("attr")-1,0,rv);
|
||||
if(Z_TYPE_P(attr)==IS_STRING){
|
||||
php_printf("attr的值为:%s,%d\n",attr->value.str->val,rv);
|
||||
}
|
||||
```
|
||||
|
||||
#### getThis
|
||||
|
||||
获取对象指针,不多说了
|
||||
|
||||
```c
|
||||
#define EX(element) ((execute_data)->element)
|
||||
|
||||
#define getThis() ((Z_TYPE(EX(This)) == IS_OBJECT) ? &EX(This) : NULL)
|
||||
|
||||
```
|
||||
|
||||
### 类参数
|
||||
|
||||
其实和函数的参数一样,还有一个类似的`zend_parse_method_parameters`我用的时候总是错误,还没明白这个函数是干什么的,而且找不到说明的资料=\_=,后面附上两个源码的区别再看看
|
||||
|
||||
```c
|
||||
PHP_METHOD(study_ext_class,sum)
|
||||
{
|
||||
zend_long parma_num=0;
|
||||
zval* this=getThis();
|
||||
zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
|
||||
if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&parma_num)==FAILURE){
|
||||
RETURN_LONG(-1)
|
||||
}
|
||||
if(Z_TYPE_P(static_num)==IS_LONG){
|
||||
RETURN_LONG(static_num->value.lval+parma_num)
|
||||
}
|
||||
RETURN_LONG(-1)
|
||||
}
|
||||
ZEND_BEGIN_ARG_INFO(sum_arg,0)
|
||||
ZEND_ARG_INFO(0,num)
|
||||
ZEND_END_ARG_INFO()
|
||||
```
|
||||
|
||||
### 探究
|
||||
|
||||
如果我们的第二个参数**this_ptr**为 NULL 或者不是**OBJECT**类型的话,那么效果和**zend_parse_parameters**一样,我之前填的是 this 指针,所以跳到了 else 分支
|
||||
|
||||
else 分之第一句就是`p++;`表示字符串往后面移动一位,我填的参数是是一个单独的 **l** 然后一移动....没啦,后面还有两个 va_arg
|
||||
|
||||
通过后面这两个得知,我们的两个参数,一个是 **zval** 的,一个是 **zend_class_entry\*** 我们传入的 **this_ptr** 参数会赋值给 **object** 也就是我们后面的第四个参数,第五个是我们类的指针
|
||||
|
||||
```c
|
||||
object = va_arg(va, zval **);
|
||||
ce = va_arg(va, zend_class_entry *);
|
||||
*object = this_ptr;
|
||||
```
|
||||
|
||||
看后面这一段,好像是校验类的,所以我觉得这个`zend_parse_method_parameters`和`zend_parse_parameters`的区别就在这里,method 能够对类进行校验
|
||||
|
||||
```c
|
||||
if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
|
||||
zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
|
||||
ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
|
||||
}
|
||||
|
||||
ZEND_API zend_bool ZEND_FASTCALL instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
|
||||
{
|
||||
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
|
||||
return instanceof_interface(instance_ce, ce);
|
||||
} else {
|
||||
return instanceof_class(instance_ce, ce);
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline zend_bool instanceof_class(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
|
||||
{
|
||||
while (instance_ce) {
|
||||
if (instance_ce == ce) {//会循环校验父类是否相等
|
||||
return 1;
|
||||
}
|
||||
instance_ce = instance_ce->parent;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...) /* {{{ */
|
||||
{
|
||||
va_list va;
|
||||
int retval;
|
||||
int flags = 0;
|
||||
|
||||
va_start(va, type_spec);
|
||||
retval = zend_parse_va_args(num_args, type_spec, &va, flags);
|
||||
va_end(va);
|
||||
|
||||
return retval;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const char *type_spec, ...) /* {{{ */
|
||||
{
|
||||
va_list va;
|
||||
int retval;
|
||||
int flags = 0;
|
||||
const char *p = type_spec;
|
||||
zval **object;
|
||||
zend_class_entry *ce;
|
||||
|
||||
/* Just checking this_ptr is not enough, because fcall_common_helper does not set
|
||||
* Z_OBJ(EG(This)) to NULL when calling an internal function with common.scope == NULL.
|
||||
* In that case EG(This) would still be the $this from the calling code and we'd take the
|
||||
* wrong branch here. */
|
||||
zend_bool is_method = EG(current_execute_data)->func->common.scope != NULL;
|
||||
|
||||
if (!is_method || !this_ptr || Z_TYPE_P(this_ptr) != IS_OBJECT) {
|
||||
va_start(va, type_spec);
|
||||
retval = zend_parse_va_args(num_args, type_spec, &va, flags);
|
||||
va_end(va);
|
||||
} else {
|
||||
p++;
|
||||
|
||||
va_start(va, type_spec);
|
||||
|
||||
object = va_arg(va, zval **);
|
||||
ce = va_arg(va, zend_class_entry *);
|
||||
*object = this_ptr;
|
||||
|
||||
if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
|
||||
zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
|
||||
ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
|
||||
}
|
||||
|
||||
retval = zend_parse_va_args(num_args, p, &va, flags);
|
||||
va_end(va);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
/* }}} */
|
||||
```
|
||||
|
||||
#### 使用
|
||||
|
||||
这里的**type_spec**我还加了一个 O,因为在源码中,`p++;`这里跳过了一个字符,那么我们后面`retval = zend_parse_va_args(num_args, p, &va, flags);`的时候传入的就是 **l 了, O** 这里应该是可以乱填一个字符的
|
||||
|
||||
**&this** 又传回来了- -
|
||||
|
||||
```c
|
||||
PHP_METHOD(study_ext_class,sum)
|
||||
{
|
||||
zend_long parma_num=0;
|
||||
zval* this=getThis();
|
||||
zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
|
||||
// zval
|
||||
if(zend_parse_method_parameters(ZEND_NUM_ARGS(),this,"Ol",&this,study_ce,&parma_num)==FAILURE){
|
||||
RETURN_LONG(-1)
|
||||
}
|
||||
if(Z_TYPE_P(static_num)==IS_LONG){
|
||||
RETURN_LONG(static_num->value.lval+parma_num)
|
||||
}
|
||||
RETURN_LONG(-1)
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 23 KiB |
47
docs/note/learn/SMTP+SSL协议研究-PHP实现.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: SMTP+SSL协议研究-PHP实现
|
||||
---
|
||||
|
||||
> 突然的就想尝试一下实现邮件发送协议,尤其是 SMTP+SSL 之类的方式,SMTP 协议全是明文的,写起来倒是不困难,但是到现在还完全不了解+SSL 的工作方式
|
||||
|
||||
## 开头
|
||||
|
||||
github:https://github.com/huanl-php/protocol
|
||||
|
||||
打算以后将实现的协议都放在这里,所以要做好规划
|
||||
|
||||
### Socket
|
||||
|
||||
[php socket](http://www.php.net/sockets "php socket")
|
||||
|
||||
先了解好 php 的 socket 函数,和 c 的 socket 非常像.为什么这里我们使用 socket 来实现,而不是用 swoole,因为在大多数的情况下,swoole 扩展并不一定安装了,这是非常不方便的
|
||||
|
||||
### client 类
|
||||
|
||||
> 这个是用来连接服务器的,由这一个类扩展出其他的协议,需要在这个里面写好连接和发送,接收的一些功能
|
||||
|
||||
这里不贴代码了,可以去 github 看:https://github.com/huanl-php/protocol/blob/master/src/Client.php
|
||||
|
||||
### smtp
|
||||
|
||||
> 这里我拿我的阿里云的邮箱测试,参考这篇文章 [邮件实现详解(二)------手工体验 smtp 和 pop3 协议](https://www.cnblogs.com/ysocean/p/7653252.html#_label0),顺便学习了一波 telnet,这篇文章是真的详细,我就不写过程了
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 实现
|
||||
|
||||
[SMTP](https://github.com/huanl-php/protocol/blob/master/src/SMTP.php)代码在这里了- -...然后看看 SSL...的工作方式
|
||||
|
||||
## SSL
|
||||
|
||||
> SSL 相当于是中间的一套层,客户端发送消息经过 SSL 层加密发送给服务器,然后经过服务器的 SSL 层又解密给服务器
|
||||
|
||||
在 php 中,ssl 套层实现非常的简单...用`stream_socket_client`连接 server,然后使用`stream_socket_enable_crypto`设置 ssl 链接,之后`fwrite`发送数据(大概这就是 linux 的哲学**万物皆文件**的体现吧),如果是这样,那么只需要在 Client 类中,重写一次 connect 和 send 那些方法就够了
|
||||
|
||||
### 源码
|
||||
|
||||
[SSLClient](https://github.com/huanl-php/protocol/blob/master/src/SSLClient.php)
|
136
docs/note/learn/SSL-TLS-client-hello.md
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
title: SSL/TLS client hello 解析
|
||||
---
|
||||
|
||||
> 摘抄:SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(是“Transport Layer Security”的缩写),中文叫做“传输层安全协议
|
||||
|
||||
> 上次写了个 ssl 的 smtp 协议,但是 ssl 实现那里,php 只需要随便调用几个函数就好了,觉得不过瘾,所以这次来看一下 ssl 的实现
|
||||
|
||||
## 准备
|
||||
|
||||
我们需要一个抓包工具**Wireshark**
|
||||
|
||||

|
||||
|
||||
这是我捕获到的,为了方便,我是抓的我的博客,右键刷新源码,然后就停止抓包
|
||||
|
||||
上面是过滤内容,ip 地址等于我的博客的地址,并且是 ssl 协议(tls)
|
||||
|
||||
在此之前我看了不少的相关知识,但是都只是说应用和流程,好处什么的,还有说一堆算法的,老夫看不懂,老夫才不管这些什么,老夫写代码就是一把梭
|
||||
|
||||
阮老师这篇文章说得挺容易理解[图解 SSL/TLS 协议](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)
|
||||
|
||||
但是没有找到实现,后来谷歌搜了一下,才找到了一篇[client hello](https://blog.csdn.net/leinchu/article/details/80196025)解析的(再一次吐槽百度),通过这一篇文章我才有一点头绪
|
||||
|
||||
## SSL 握手
|
||||
|
||||
> 经过了 TCP 三次握手之后,就开始 SSL 的握手
|
||||
|
||||
### 结构
|
||||
|
||||
> 先了解一下大概结构
|
||||
|
||||
我们用 Wireshark 抓到了数据,写得非常的详细,看第一个包就是**client hello**,点它,然后点**secure sockets layer**这就是我们 ssl 的数据了,然后会帮我们选中我们的数据,非常的直接,前面那些应该是 tcp 协议相关的数据
|
||||
|
||||

|
||||
|
||||
看这个,感觉大概结构应该是这样,首先是包类型版本号和长度,然后是内容
|
||||
|
||||
```cpp
|
||||
struct ssl_handshake{
|
||||
char type;
|
||||
short version;
|
||||
short length;
|
||||
char *content;
|
||||
}
|
||||
```
|
||||
|
||||
握手包,点开 handshake Protocol 可以看到,client hello 和 server hello,前一部分都是这样
|
||||
|
||||
```cpp
|
||||
//随机数
|
||||
struct ssl_random{
|
||||
int timestamp;
|
||||
char random[28];
|
||||
}
|
||||
struct ssl_hello{
|
||||
char type;
|
||||
char length[3];
|
||||
short version;
|
||||
struct ssl_random random;
|
||||
}
|
||||
```
|
||||
|
||||
这个结构我写到了随机数截止,因为后面的大多是不同的且变化
|
||||
|
||||
#### session
|
||||
|
||||
**session**,这个是用来复用的,我记得这个只是一个记录,并不一定需要,于是我通过其他软件访问网页,抓到了一个没有 session 的包来比对,所以我们就可以直接在后面填充一个 0,然后继续后面的
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#### cipher suites
|
||||
|
||||
**cipher suites**应该是客户端支持的秘钥类型,里面应该都是一些常量值
|
||||
|
||||
意义,例如:TLS***ECDHE*****RSA**_WITH_**AES_128_GCM**\_ **SHA256** (0xc02f)
|
||||
**ECDHE**秘钥交换算法
|
||||
**RSA**身份验证算法
|
||||
**AES_128_GCM**对称加密算法
|
||||
**SHA256**摘要算法
|
||||
这样按照顺序拆分开来看....
|
||||
|
||||
值的话,我只从 RFC 里面找到了零零散散的,不过我想干脆就从我们抓到的包里面提取几个吧
|
||||
|
||||
```
|
||||
Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
|
||||
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
|
||||
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
|
||||
```
|
||||
|
||||
#### Extension
|
||||
|
||||
> 跳过了 Compression Methods,等下按照我们抓到的值填就好了
|
||||
|
||||
Extension 听名字就知道是扩展项,感觉和原来的 radius 的一样
|
||||
|
||||
```cpp
|
||||
struct ssl_extension{
|
||||
short type;
|
||||
short length;
|
||||
char *content;
|
||||
}
|
||||
```
|
||||
|
||||
我想先吧他们复制一下试试能不能行(偷懒,滑稽)
|
||||
|
||||
### client hello
|
||||
|
||||
> 客户端先给服务端打声招呼,告诉客户端支持的加密算法 balabala,先尝试写代码,发送一个 hello 的包给服务器看看回答
|
||||
|
||||
代码如下:
|
||||
|
||||
```php
|
||||
public function testSSLHello() {
|
||||
$str = SSL::pac_ssl_handshake(22, SSL::TLSv3,
|
||||
SSL::pack_ssl_hello(1, SSL::TLSv3, SSL::pack_ssl_random(),
|
||||
hex2bin('00') . SSL::pack_ciphersuites(['009c', 'c02f', '003c']) .
|
||||
hex2bin('0100005800000014001200000f626c6f672e69636f6465662e636f6d000500050100000000000a00080006001d00170018000b00020100000d001400120401050102010403050302030202060106030023000000170000ff01000100')
|
||||
)
|
||||
);//那一大串十六进制是我复制的扩展区
|
||||
static::$client->send($str);
|
||||
static::$client->recv($buf, 2048);
|
||||
}
|
||||
```
|
||||
|
||||
发送之后成功接收到了服务器的应答,扩展区里面还包含了服务器的一些信息,如果换服务器可能还要修改一下`Extension: server_name`
|
||||
|
||||
~~今天坑先挖到这里,明天看 server hello~~
|
||||
|
||||
## 参考
|
||||
|
||||
[https://blog.csdn.net/mrpre/article/details/77867439](https://blog.csdn.net/mrpre/article/details/77867439)
|
||||
[图解 SSL/TLS 协议](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)
|
||||
[client hello](https://blog.csdn.net/leinchu/article/details/80196025)
|
4
docs/note/learn/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label":"学习笔记",
|
||||
"position": 5
|
||||
}
|
114
docs/note/learn/android-邀请链接.md
Normal file
@ -0,0 +1,114 @@
|
||||
---
|
||||
title: android 邀请链接,安装apk注册自动填写邀请人
|
||||
---
|
||||
|
||||
> 今天又遇到一个 app 推广的问题,要求通过下载链接下载安装之后,注册的时候自动填写这个邀请链接的邀请人,但是又怎么吧这个邀请人的信息填入这个 apk 包呢?开始想着在文件的最后直接写入用户的 uid,结果破坏的 apk 的结构,后来发现 apk 其实就是一个 zip 包,通过网上的资料找到了思路,通过邀请链接下载的时候就修改这个 zip 包的注释将邀请者的 uid 填入
|
||||
|
||||
## ZIP 文件结构
|
||||
|
||||
随手一搜就可以找到 zip 的文件结构
|
||||
|
||||
**[官方文档](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.0.txt) [参考文章](https://blog.csdn.net/a200710716/article/details/51644421)** 这个文章后面还拿了一个简单的文件做讲解,挺不错的
|
||||
|
||||
其他的都不管他,我只想要注释那一段的结构,
|
||||
|
||||
### End of central directory record(EOCD) 目录结束标识
|
||||
|
||||
目录结束标识存在于整个归档包的结尾,用于标记压缩的目录数据的结束。每个压缩文件必须有且只有一个 EOCD 记录。
|
||||
|
||||
| Offset | Bytes | Description | 翻译 |
|
||||
| ------ | ----- | ----------------------------------------------------------------------------- | ----------------------------- |
|
||||
| 0 | 4 | End of central directory signature = 0x06054b50 | 核心目录结束标记(0x06054b50 |
|
||||
| 4 | 2 | Number of this disk | 当前磁盘编号 |
|
||||
| 6 | 2 | Number of the disk with the start of the central directory | 核心目录开始位置的磁盘编号 |
|
||||
| 8 | 2 | total number of entries in the central directory on this disk | 该磁盘上所记录的核心目录数量 |
|
||||
| 10 | 2 | total number of entries in the central directory | 核心目录结构总数 |
|
||||
| 12 | 2 | size of central directory (bytes) | 核心目录的大小 |
|
||||
| 16 | 2 | offset of start of central directory with respect to the starting disk number | 核心相对于 archive 开始的位移 |
|
||||
| 20 | 2 | .ZIP file comment length(n) | 注释长度 |
|
||||
| 22 | n | .ZIP Comment | 注释内容 |
|
||||
|
||||
### 先用易语言测试一下
|
||||
|
||||
先打包一个文件
|
||||
|
||||

|
||||
|
||||
随手一读
|
||||
|
||||
```
|
||||
.版本 2
|
||||
.支持库 spec
|
||||
.局部变量 data, 字节集
|
||||
data = 读入文件 (“test.zip”)
|
||||
调试输出 (取字节集中间 (data, 寻找字节集 (data, { 80, 75, 5, 6 }, ), 50))
|
||||
调试输出 (到字节集 (“test123”))
|
||||
```
|
||||
|
||||
读取出来的数据中确实有 test123
|
||||
然后看看怎么写注释进去,这回读取 apk 的
|
||||
|
||||
```
|
||||
//80,75,5,6,0,0,0,0,18,0,18,0,18,5,0,0,55,14,17,0,0,0,60,105,110,118,62,50,60,47,105,110,118,62
|
||||
//读出来这玩意,先理清楚结构
|
||||
//80,75,5,6 EOCD
|
||||
//0,0 磁盘编号
|
||||
//0,0 开始位置的磁盘编号
|
||||
//18,0 磁盘上所记录的核心目录数量
|
||||
//18,0 核心目录结构总数
|
||||
//18,5,0,0 核心目录的大小
|
||||
//55,14,17,0 核心目录开始位置相对于archive开始的位移
|
||||
//0,0 注释长度
|
||||
//0,60,105,110,118,62,50,60,47,105,110,118,62未知内容,不管了,我们只需要修改注释
|
||||
|
||||
.版本 2
|
||||
|
||||
.局部变量 data, 字节集
|
||||
.局部变量 tmpByte, 字节集
|
||||
.局部变量 pos, 整数型
|
||||
|
||||
data = 读入文件 (“test.apk”)
|
||||
pos = 寻找字节集 (data, { 80, 75, 5, 6 }, )
|
||||
data [pos + 20] = 8
|
||||
tmpByte = 到字节集 (“12345678”) + { 0 } + 取字节集右边 (data, 取字节集长度 (data) - pos - 21)
|
||||
data = 取字节集左边 (data, 寻找字节集 (data, { 80, 75, 5, 6 }, ) + 21)
|
||||
data = data + tmpByte
|
||||
写到文件 (“ddd.apk”, data)
|
||||
|
||||
|
||||
成功写入压缩包,看看能不能在手机上安装
|
||||
成功可以安装使用
|
||||
```
|
||||
|
||||
### PHP 下载代码
|
||||
|
||||
php 的话就和前面一下,修改注释内容,直接贴 php 的代码了
|
||||
php 的文件操作就是文本操作....十六进制要用双引号\0xff 表示
|
||||
|
||||
```php
|
||||
public function download_apk($inv = 1) {
|
||||
if (!userModel::existUser($inv)) {//判断用户是否存在
|
||||
$inv = 0;
|
||||
}
|
||||
$invStr = '<inv>' . $inv . '</inv>';//设置注释
|
||||
$data = file_get_contents('app/kana.apk');//读取apk源数据
|
||||
$pos = strpos($data, "\x50\x4b\x05\x06");
|
||||
//搜索标志位置(一般都是文件尾部,不考虑了)
|
||||
$data = substr($data, 0, $pos + 20);
|
||||
//取出标志+20左边,等下直接从注释长度那块合成
|
||||
$dec = dechex(strlen($invStr));
|
||||
//将长度转为十六进制,后面再将十六进制转为byte,只考虑一位,反正ff,255个长度也够了
|
||||
$data .= hex2bin(strlen($dec) <= 1 ? ('0' . $dec) : $dec) . "\x00$invStr";//合成注释
|
||||
header("Content-Type: application/octet-stream");
|
||||
header("Accept-Ranges: bytes");
|
||||
header("Accept-Length: " . strlen($data));
|
||||
header("Content-Disposition: attachment; filename=kana.apk");
|
||||
echo $data;//输出文件数据
|
||||
}
|
||||
```
|
||||
|
||||
### android 读取
|
||||
|
||||
暂时空着,android 又不是我写 ┓( ´∀` )┏,贴一下思路吧
|
||||
读取本地安装的 apk 包数据,然后在里面搜索这个注释(怎么感觉和没讲一样)
|
||||
总之很简单的
|
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 77 KiB |
72
docs/note/learn/linux-aio-异步io.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: linux aio 异步io
|
||||
---
|
||||
|
||||
> linux 下的 aio 有 glibc 的和内核所提供的,glibc 是使用的多线程的模式模拟的,另外一种是真正的内核异步通知了,已经使用在了 nginx 上,前面看了一下 swoole 的实现,是类似与 glibc 那种多线程的模式。不过两种方法都有一定的毛病,多线程模拟自然是有所效率损失,然而内核不能利用系统的缓存,只能以 O_DIRECT 方式做直接 IO,所以看知乎上有一个**linux 下的异步 IO(AIO)是否已成熟?**的问题,不过那是 2014 年的事情了,不知道现在怎么样。
|
||||
|
||||
在此之前需要安装好 **libaio**
|
||||
|
||||
```
|
||||
sudo apt install libaio-dev
|
||||
```
|
||||
|
||||
## 函数
|
||||
|
||||
头文件:`#include <libaio.h>`
|
||||
|
||||
## 例子
|
||||
|
||||
> 一个一步读取文件内容的例子
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <libaio.h>
|
||||
#include <malloc.h>
|
||||
#include <mhash.h>
|
||||
|
||||
#define MAX_EVENT 10
|
||||
#define BUF_LEN 1024
|
||||
|
||||
void callback(io_context_t ctx, struct iocb *iocb, long res, long res2) {
|
||||
printf("test call\n");
|
||||
printf("%s\n", iocb->u.c.buf);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int fd = open("/home/huanl/client.ovpn", O_RDONLY, 0);
|
||||
io_context_t io_context;
|
||||
struct iocb io, *p = &io;
|
||||
struct io_event event[MAX_EVENT];
|
||||
char *buf = malloc(BUF_LEN);
|
||||
memset(buf, 0, BUF_LEN);
|
||||
memset(&io_context, 0, sizeof(io_context));
|
||||
|
||||
if (io_setup(10, &io_context)) {
|
||||
printf("io_setup error");
|
||||
return 0;
|
||||
}
|
||||
if (fd < 0) {
|
||||
printf("open file error");
|
||||
return 0;
|
||||
}
|
||||
io_prep_pread(&io, fd, buf, BUF_LEN, 0);
|
||||
io_set_callback(&io, callback);
|
||||
if (io_submit(io_context, 1, &p) < 0) {
|
||||
printf("io_submit error");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int num = io_getevents(io_context, 1, MAX_EVENT, event, NULL);
|
||||
for (int i = 0; i < num; i++) {
|
||||
io_callback_t io_callback = event[i].data;
|
||||
io_callback(io_context, event[i].obj, event[i].res, event[i].res2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
[https://jin-yang.github.io/post/linux-program-aio.html](https://jin-yang.github.io/post/linux-program-aio.html)
|
234
docs/note/learn/swoole学习笔记/01-swoole环境配置.md
Normal file
@ -0,0 +1,234 @@
|
||||
---
|
||||
title: swoole学习笔记(一)-swoole环境配置(树莓派安装)
|
||||
---
|
||||
|
||||
> 打算开始学习 swoole 了(原来好像弄过,不过那次只是接触了一下,并未太过深入,这次重新来过 (° ー °〃)
|
||||
> swoole 虽然能在 windows 上搭建,不过我觉得意义不大....需要安装 CygWin 这和在 linux 上有什么区别呢 ┑( ̄ Д  ̄)┍,刚好现在手上有一台空闲的树莓派 zero,试试在上面搭建
|
||||
|
||||
## 编译 php
|
||||
|
||||
> 之所以要编译安装是因为在 swoole 编译的时候需要用到 phpize,apt-get 安装的时候没发现有
|
||||
|
||||
现在这个上面什么东西都没有,先安装 php,我选最新的 php7.2.6,zero 配置是真的好低....解压和编译 cpu 都 100%了很慢....趁这个时间去干点别的吧
|
||||
|
||||
下载,解压源码,安装依赖
|
||||
|
||||
强烈建议使用国内镜像....不然可能一些依赖 lib 按照失败,导致编译错误
|
||||
|
||||
```
|
||||
sudo -i
|
||||
wget http://hk1.php.net/get/php-7.2.6.tar.gz/from/this/mirror
|
||||
mv mirro php.tar.gz
|
||||
tar -zxvf php.tar.gz
|
||||
apt-get update
|
||||
apt-get install libxml2* libbz2-dev libjpeg-dev libmcrypt-dev libssl-dev openssl libxslt1-dev libxslt1.1 libcurl4-gnutls-dev libpq-dev build-essential git make
|
||||
```
|
||||
|
||||
编译配置,复制的网上的 lnmp 编译- -...去掉了和 Nginx 有关的编译项,我只需要编译出 php 就行,不需要 Nginx 那些环境,当然如果你之前已经有了这些,这一部分就可以跳过了
|
||||
|
||||
```
|
||||
cd php-7.2.6
|
||||
./configure \
|
||||
--prefix=/usr/local/php \
|
||||
--exec-prefix=/usr/local/php \
|
||||
--bindir=/usr/local/php/bin \
|
||||
--sbindir=/usr/local/php/sbin \
|
||||
--includedir=/usr/local/php/include \
|
||||
--libdir=/usr/local/php/lib/php \
|
||||
--mandir=/usr/local/php/php/man \
|
||||
--with-config-file-path=/usr/local/php/etc \
|
||||
--with-mysql-sock=/var/lib/mysql/mysql.sock \
|
||||
--with-mcrypt=/usr/include \
|
||||
--with-mhash \
|
||||
--with-openssl \
|
||||
--with-mysql=shared,mysqlnd \
|
||||
--with-mysqli=shared,mysqlnd \
|
||||
--with-pdo-mysql=shared,mysqlnd \
|
||||
--with-gd \
|
||||
--with-iconv \
|
||||
--with-zlib \
|
||||
--enable-zip \
|
||||
--enable-inline-optimization \
|
||||
--disable-debug \
|
||||
--disable-rpath \
|
||||
--enable-shared \
|
||||
--enable-xml \
|
||||
--enable-bcmath \
|
||||
--enable-shmop \
|
||||
--enable-sysvsem \
|
||||
--enable-mbregex \
|
||||
--enable-mbstring \
|
||||
--enable-ftp \
|
||||
--enable-gd-native-ttf \
|
||||
--enable-pcntl \
|
||||
--enable-sockets \
|
||||
--with-xmlrpc \
|
||||
--enable-soap \
|
||||
--without-pear \
|
||||
--with-gettext \
|
||||
--enable-session \
|
||||
--with-curl \
|
||||
--with-freetype-dir \
|
||||
--enable-opcache \
|
||||
--enable-redis \
|
||||
--enable-fpm \
|
||||
--enable-fastcgi \
|
||||
--disable-fileinfo
|
||||
```
|
||||
|
||||

|
||||
|
||||
CPU 100% 有点怕,树莓派 zero 性能确实是弱...编译好慢....解决了编译配置的问题后就开始编译,我是真的睡了一觉(第二天)才起来 make install
|
||||
|
||||
```
|
||||
make && make install
|
||||
```
|
||||
|
||||
设置一下 php.ini 文件
|
||||
|
||||
```
|
||||
cp php.ini-production /usr/local/php/etc/php.ini
|
||||
//我输入php -v之后发现没反应,但是php确实是成功了,在/usr/local/php/bin里面./php -v也有反应,想到可能是没有链接到/usr/bin 目录里,用ln命令链接一下
|
||||
ln -s /usr/local/php/bin/php /usr/bin/php
|
||||
//链接phpize
|
||||
ln -s /usr/local/php/bin/phpize /usr/bin/phpize
|
||||
```
|
||||
|
||||
成功之后,老套路
|
||||
|
||||
```
|
||||
php -v
|
||||
```
|
||||
|
||||

|
||||
|
||||
成功,终于可以下一步了,进入 swoole 编译配置
|
||||
|
||||
## swoole 编译
|
||||
|
||||
从 git 上下载源码[https://github.com/swoole/swoole-src/releases](https://github.com/swoole/swoole-src/releases),开始编译
|
||||
|
||||
```
|
||||
wget https://github.com/swoole/swoole-src/archive/v4.0.0.zip
|
||||
unzip v4.0.0.zip
|
||||
mv swoole-src-4.0.0/ swoole
|
||||
cd swoole
|
||||
phpize
|
||||
```
|
||||
|
||||
这里我提示了一个错误...
|
||||
Cannot find autoconf. Please check your autoconf installation and the
|
||||
$PHP_AUTOCONF environment variable. Then, rerun this script.
|
||||
解决办法:
|
||||
|
||||
```
|
||||
apt-get install m4 autoconf
|
||||
```
|
||||
|
||||
phpize 成功之后继续运行编译配置和开始编译(但愿这次不用那么久了...)
|
||||
|
||||
开启一些需要的:[编译配置项](https://wiki.swoole.com/wiki/page/437.html)
|
||||
|
||||
```
|
||||
./configure --with-php-config=/usr/local/php/bin/php-config --enable-sockets --enable-swoole-debug --enable-openssl --enable-mysqlnd --enable-coroutine
|
||||
make && make install
|
||||
```
|
||||
|
||||

|
||||
|
||||
然后需要在 php.ini 中配置下
|
||||
|
||||
```
|
||||
vi /usr/local/php/etc/php.ini
|
||||
//添加
|
||||
extension=swoole.so
|
||||
```
|
||||
|
||||
然后`php -m`
|
||||
|
||||

|
||||
|
||||
有这一项就代表成啦~
|
||||
|
||||
## 测试
|
||||
|
||||
> 安装编译都完成之后,当然来试试是不是真的能用了
|
||||
|
||||
复制官方的例子,嘿嘿嘿~
|
||||
|
||||
```php
|
||||
<?php
|
||||
//创建websocket服务器对象,监听0.0.0.0:9502端口
|
||||
$ws = new swoole_websocket_server("0.0.0.0", 9502);
|
||||
//监听WebSocket连接打开事件
|
||||
$ws->on('open', function ($ws, $request) {
|
||||
var_dump($request->fd, $request->get, $request->server);
|
||||
$ws->push($request->fd, "hello, welcome\n");
|
||||
});
|
||||
//监听WebSocket消息事件
|
||||
$ws->on('message', function ($ws, $frame) {
|
||||
echo "Message: {$frame->data}\n";
|
||||
$ws->push($frame->fd, "server: {$frame->data}");
|
||||
});
|
||||
//监听WebSocket连接关闭事件
|
||||
$ws->on('close', function ($ws, $fd) {
|
||||
echo "client-{$fd} is closed\n";
|
||||
});
|
||||
$ws->start();
|
||||
```
|
||||
|
||||
`php swoole.php`
|
||||
web:
|
||||
|
||||
```html
|
||||
<script>
|
||||
var ws = new WebSocket("ws://localhost:9502");
|
||||
ws.onopen = function () {
|
||||
ws.send("send data");
|
||||
};
|
||||
|
||||
ws.onmessage = function (evt) {
|
||||
var received_msg = evt.data;
|
||||
console.log(received_msg);
|
||||
};
|
||||
|
||||
ws.onclose = function () {
|
||||
console.log("连接关闭");
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
成了~
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 问题解决
|
||||
|
||||
### redis 扩展安装
|
||||
|
||||
> 弄完后...并没有用,然后重新编译一次成了....= =,不过还是记着
|
||||
|
||||
在 swoole 编译完成后,又遇到了一个问题....
|
||||
|
||||
```
|
||||
php: symbol lookup error: /usr/local/php/lib/php/extensions/no-debug-non-zts-20170718/swoole.so: undefined symbol: swoole_redis_coro_init
|
||||
```
|
||||
|
||||
查资料后发现可能是需要给 php 安装 redis 扩展....[redis 源码下载](https://pecl.php.net/package/redis)
|
||||
|
||||
```
|
||||
wget https://pecl.php.net/get/redis-4.0.2.tgz
|
||||
tar -zxvf redis-4.0.2.tgz
|
||||
cd redis-4.0.2
|
||||
phpize
|
||||
./configure --with-php-config=/usr/local/php/bin/php-config
|
||||
make && make install
|
||||
```
|
||||
|
||||
然后在 php.ini 中加上`extension = redis.so`就好了,注意这个配置一定要放在 swoole 的配置的前面,因为这些扩展都是按照顺序加载的
|
||||
|
||||
---
|
||||
|
||||
**历时一天,终于搞定了 编译真的是漫长的过程=\_=**
|
134
docs/note/learn/swoole学习笔记/02-开发环境配置.md
Normal file
@ -0,0 +1,134 @@
|
||||
---
|
||||
title: swoole学习笔记(二)-开发环境配置
|
||||
---
|
||||
|
||||
> swoole 可以跑了,然后开始弄开发环境
|
||||
|
||||
> 后面的 xdebug,在协程中 tm 不能用!...有挺多问题的,不推荐配置了,写 log 吧
|
||||
|
||||
## 代码自动上传
|
||||
|
||||
我的开发环境一般是 windows,phpstorm,然而我的树莓派和 swoole 的环境又不在一起,这时候就可以用 phpstorm 的一个功能,可以自动同步代码
|
||||
|
||||
File->setting->Deployment
|
||||
|
||||
添加一个,选择 sftp,然后输入 pi 的信息
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
添加好服务器后,再设置 options
|
||||
|
||||

|
||||
|
||||
自动上传就配置好了,当你保存的时候就会自动上传到服务器
|
||||
|
||||

|
||||
|
||||
## 代码自动提示
|
||||
|
||||
> 对于我这种百度型程序员,自动提示是必不可少的
|
||||
|
||||
### swoole-ide-helper
|
||||
|
||||
https://github.com/eaglewu/swoole-ide-helper
|
||||
|
||||
这是一个**Swoole 在 IDE 下自动识别类、函数、宏,自动补全函数名**
|
||||
|
||||
#### 安装方法
|
||||
|
||||
##### phpstrom
|
||||
|
||||
将项目 clone 或者直接下载下来,解压
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### composer
|
||||
|
||||
要是你的项目中使用了 composer,你可以直接
|
||||
|
||||
```bash
|
||||
composer require --dev "eaglewu/swoole-ide-helper:dev-master"
|
||||
```
|
||||
|
||||
## 远程调试配置
|
||||
|
||||
> 虽然可以通过 echo 之类的来调试,但是断点调试也是必不可少的
|
||||
|
||||
### XDebug 安装
|
||||
|
||||
[https://github.com/xdebug/xdebug](https://github.com/xdebug/xdebug)
|
||||
|
||||
```bash
|
||||
wget https://github.com/xdebug/xdebug/archive/2.6.0.tar.gz
|
||||
tar -zxvf 2.6.0.tar.gz
|
||||
cd xdebug-2.6.0/
|
||||
./configure --with-php-config=/usr/local/php/bin/php-config
|
||||
make && make install
|
||||
```
|
||||
|
||||
然后在 php.ini 中配置
|
||||
|
||||
```toml
|
||||
zend_extension=xdebug.so
|
||||
[xdebug]
|
||||
xdebug.remote_enable=true
|
||||
xdebug.remote_host=127.0.0.1
|
||||
xdebug.remote_port=9000
|
||||
xdebug.remote_handler=dbgp
|
||||
```
|
||||
|
||||
保存之后`php -m`,出现 xdebug 就算安装成功了
|
||||
|
||||

|
||||
|
||||
### phpstorm 配置
|
||||
|
||||
setting 中 php 配置,设置一下远程 cli
|
||||
|
||||

|
||||
|
||||
在之前已经配置了远程自动同步的话,这里是会有服务器可以选择的
|
||||
|
||||

|
||||
|
||||
点击 OK 之后,phpstorm 会自动获取远程的 php 信息,如下
|
||||
|
||||

|
||||
|
||||
之后选择我们刚刚添加的(我重命名了 pi_zero php7.2)
|
||||
|
||||

|
||||
|
||||
然后下方的 path mappings,也需要设置(我这里默认设置好了),对本地与远程的目录进行映射
|
||||
|
||||

|
||||
|
||||
xdebug 的端口 9000,一开始就是这样的,如果你改了的话,这里注意也改一下
|
||||
|
||||

|
||||
|
||||
这些配置好之后就可以开始配置调试选项了
|
||||
|
||||

|
||||
|
||||
配置启动文件
|
||||
|
||||

|
||||
|
||||
之后就可以开始调试了,在我们的源码下下断点,然后点击调试按钮,成功~!
|
||||
|
||||

|
||||
|
||||
当收到信息/连接的时候:
|
||||
|
||||

|
||||
|
||||
非常舒服,嘿嘿嘿
|
||||
|
||||

|
445
docs/note/learn/swoole学习笔记/03-UDP-radius协议实现.md
Normal file
@ -0,0 +1,445 @@
|
||||
---
|
||||
title: swoole学习笔记(三)-UDP radius协议实现
|
||||
---
|
||||
|
||||
> 又开新坑-swoole 作为 radius 服务器,huanlphp 写业务 [php-radius](https://github.com/CodFrm/php-radius "php-radius") 主要还是为了学习 swoole 和实验我的框架,所以这里记录一下 radius 协议的结构和使用(原来用 python 实现过一次,容易崩溃还写得垃圾),文章中只写了 auth,没有写 account 的记录,openvpn 需要 auth 和 account 才能实现连接成功,可以看我完整的实现[python 实现](https://github.com/CodFrm/stuShare/blob/master/radius/main.py)
|
||||
|
||||
## 协议&工具
|
||||
|
||||
- [rfc2865 radius 身份认证](https://tools.ietf.org/html/rfc2865)
|
||||
- [rfc2866 radius 计费](https://tools.ietf.org/html/rfc2866)
|
||||
|
||||
测试工具我用的 NTRadPing:[http://www.winsite.com/internet/server-tools/ntradping/](http://www.winsite.com/internet/server-tools/ntradping/)
|
||||
|
||||

|
||||
|
||||
## 结构
|
||||
|
||||
### pack fromat
|
||||
|
||||
> 关于计费和验证的格式都是一样的
|
||||
|
||||
验证:[https://tools.ietf.org/html/rfc2865#page-13](https://tools.ietf.org/html/rfc2865#page-13)
|
||||
|
||||
计费:[https://tools.ietf.org/html/rfc2866#page-5](https://tools.ietf.org/html/rfc2866#page-5)
|
||||
|
||||
```
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Code | Identifier | Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| Authenticator |
|
||||
| |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Attributes ...
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-
|
||||
```
|
||||
|
||||
看得有些隐晦,我将它转换成 c 的 struct 结构吧
|
||||
char 1 个字节,8bit short 两个字节,16bit
|
||||
|
||||
```c
|
||||
struct radius_format{
|
||||
char code;//包类型,请求类型根据这个判断
|
||||
char identifier;//鉴定码,服务器和客户端这个值需要一样
|
||||
short lenght; //本次数据包的总长度(20-4096)
|
||||
char authenticator[16];//数据hash,用来校验数据是否正确,一个数据的md5码
|
||||
};
|
||||
```
|
||||
|
||||
在响应的时候 authenticator 的计算方式为:MD5(Code+ID+Length+RequestAuth+Attributes+**Secret**) 在计算中的**RequestAuth**为请求的时候的 authenticator,**Secret**记笔记,是双方的一个 key
|
||||
|
||||
后面的 Attributes 长度不一定(length-20),里面包含着 **用户/NAS** 的一些信息,比如账号,密码,ip,之类的,格式看后面,这里也可以用来扩展自己的协议,带上自己的一些奇奇怪怪的值- -
|
||||
|
||||
#### code 意义
|
||||
|
||||
主要就是前面 5 个了
|
||||
|
||||
```
|
||||
RADIUS Codes (decimal) are assigned as follows:
|
||||
|
||||
1 Access-Request //验证请求,一般由客户端使用
|
||||
2 Access-Accept //验证通过,一般由服务端处理返回
|
||||
3 Access-Reject //验证拒绝,一般由服务端处理返回
|
||||
4 Accounting-Request //计费请求,客户端发起
|
||||
5 Accounting-Response //计费返回,服务器发起
|
||||
11 Access-Challeng //服务端主动请求再次验证,客户端要求必须返回
|
||||
12 Status-Server (experimental)//服务器状态???没看到说明
|
||||
13 Status-Client (experimental)//同上
|
||||
255 Reserved//保留
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
[https://tools.ietf.org/html/rfc2865#page-22](https://tools.ietf.org/html/rfc2865#page-22)
|
||||
|
||||
```
|
||||
0 1 2
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
||||
| Type | Length | Value ...
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
||||
```
|
||||
|
||||
```c
|
||||
struct radius_attr{
|
||||
char type;//类型,有不少...
|
||||
char length;//type+length+value的长度
|
||||
}
|
||||
```
|
||||
|
||||
value 是不固定的(长度:1-253),根据 length 来判定(就是 length-2 咯)
|
||||
|
||||
#### type
|
||||
|
||||
> 偷懒了,不写意义了
|
||||
|
||||
验证属性:[https://tools.ietf.org/html/rfc2865#page-24](https://tools.ietf.org/html/rfc2865#page-24)
|
||||
|
||||
计费属性:[https://tools.ietf.org/html/rfc2866#page-11](https://tools.ietf.org/html/rfc2866#page-11)
|
||||
|
||||
```
|
||||
This specification concerns the following values:
|
||||
|
||||
1 User-Name //登录用户名
|
||||
2 User-Password //登录密码
|
||||
3 CHAP-Password
|
||||
4 NAS-IP-Address //NAS的IP地址
|
||||
5 NAS-Port //NAS的端口
|
||||
6 Service-Type
|
||||
7 Framed-Protocol
|
||||
8 Framed-IP-Address
|
||||
9 Framed-IP-Netmask
|
||||
10 Framed-Routing
|
||||
11 Filter-Id
|
||||
12 Framed-MTU
|
||||
13 Framed-Compression
|
||||
14 Login-IP-Host
|
||||
15 Login-Service
|
||||
16 Login-TCP-Port
|
||||
17 (unassigned)
|
||||
18 Reply-Message
|
||||
19 Callback-Number
|
||||
20 Callback-Id
|
||||
21 (unassigned)
|
||||
22 Framed-Route
|
||||
23 Framed-IPX-Network
|
||||
24 State
|
||||
25 Class
|
||||
26 Vendor-Specific
|
||||
27 Session-Timeout
|
||||
28 Idle-Timeout
|
||||
29 Termination-Action
|
||||
30 Called-Station-Id
|
||||
31 Calling-Station-Id
|
||||
32 NAS-Identifier
|
||||
33 Proxy-State
|
||||
34 Login-LAT-Service
|
||||
35 Login-LAT-Node
|
||||
36 Login-LAT-Group
|
||||
37 Framed-AppleTalk-Link
|
||||
38 Framed-AppleTalk-Network
|
||||
39 Framed-AppleTalk-Zone
|
||||
40-59 (reserved for accounting)//计费的,包含流量信息和连接时间什么的
|
||||
60 CHAP-Challenge
|
||||
61 NAS-Port-Type
|
||||
62 Port-Limit
|
||||
63 Login-LAT-Port
|
||||
```
|
||||
|
||||
## 实现
|
||||
|
||||
> 利用 NTR 工具发送测试数据,使用 php-swoole 来接收和处理
|
||||
|
||||
### 解包
|
||||
|
||||
php 中使用 unpack 来解包,还是很方便的
|
||||
|
||||
```php
|
||||
public function onPacket(swoole_server $serv, string $data, array $clientInfo) {
|
||||
//解包
|
||||
$radius = unpack('ccode/cidentifier/nlength/a16authenticator', $data);
|
||||
print_r($radius);
|
||||
echo bin2hex($radius['authenticator']) . "\n";
|
||||
echo bin2hex($data);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||

|
||||
|
||||
后面还需要处理 attr 属性,这是我解码的代码
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class radius {
|
||||
/**
|
||||
* @var swoole_server
|
||||
*/
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* 密钥
|
||||
* @var string
|
||||
*/
|
||||
public $secret_key;
|
||||
|
||||
public static $ATTR_TYPE = [1 => 'User-Name',
|
||||
2 => 'User-Password', 3 => 'CHAP-Password', 4 => 'NAS-IP-Address', 5 => 'NAS-Port', 6 => 'Service-Type',
|
||||
7 => 'Framed-Protocol', 8 => 'Framed-IP-Address', 9 => 'Framed-IP-Netmask', 10 => 'Framed-Routing',
|
||||
11 => 'Filter-Id', 12 => 'Framed-MTU', 13 => 'Framed-Compression', 14 => 'Login-IP-Host', 15 => 'Login-Service',
|
||||
16 => 'Login-TCP-Port', 17 => '(unassigned)', 18 => 'Reply-Message', 19 => 'Callback-Number',
|
||||
20 => 'Callback-Id', 21 => '(unassigned)', 22 => 'Framed-Route', 23 => 'Framed-IPX-Network', 24 => 'State',
|
||||
25 => 'Class', 26 => 'Vendor-Specific', 27 => 'Session-Timeout', 28 => 'Idle-Timeout', 29 => 'Termination-Action',
|
||||
30 => 'Called-Station-Id', 31 => 'Calling-Station-Id', 32 => 'NAS-Identifier', 33 => 'Proxy-State',
|
||||
34 => 'Login-LAT-Service', 35 => 'Login-LAT-Node', 36 => 'Login-LAT-Group', 37 => 'Framed-AppleTalk-Link',
|
||||
38 => 'Framed-AppleTalk-Network', 39 => 'Framed-AppleTalk-Zone',
|
||||
60 => 'CHAP-Challenge', 61 => 'NAS-Port-Type', 62 => 'Port-Limit', 63 => 'Login-LAT-Port'];
|
||||
|
||||
/**
|
||||
* 收到udp数据包
|
||||
* @param swoole_server $serv
|
||||
* @param string $data
|
||||
* @param array $clientInfo
|
||||
*/
|
||||
public function onPacket(swoole_server $serv, string $data, array $clientInfo) {
|
||||
$attr = [];
|
||||
$struct = $this->unpack($data, $attr);
|
||||
print_r($struct);
|
||||
print_r($attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码radius数据包
|
||||
* @param string $bin
|
||||
* @param array $attr
|
||||
* @return array|bool
|
||||
*/
|
||||
public function unpack(string $bin, array &$attr): array {
|
||||
//一个正常的radius封包长度是绝对大于等于20的
|
||||
if (strlen($bin) < 20) {
|
||||
return [];
|
||||
}
|
||||
//解包
|
||||
$radius = unpack('ccode/cidentifier/nlength/a16authenticator', $bin);
|
||||
//获取后面的属性长度,并且对数据包进行验证
|
||||
if (strlen($bin) != $radius['length']) {
|
||||
return [];
|
||||
}
|
||||
$attr_len = $radius['length'] - 20;
|
||||
//处理得到后面的Attributes,并且解包
|
||||
$attr = $this->unpack_attr(substr($bin, 20, $attr_len));
|
||||
if ($attr == []) {
|
||||
return [];
|
||||
}
|
||||
return $radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Attributes
|
||||
* @param string $bin
|
||||
* @return array
|
||||
*/
|
||||
public function unpack_attr(string $bin): array {
|
||||
$attr = [];
|
||||
$offset = 0;
|
||||
$len = strlen($bin);
|
||||
while ($offset < $len) {
|
||||
$attr_type = ord($bin[$offset]);//属性类型
|
||||
$attr_len = ord($bin[$offset + 1]);//属性长度
|
||||
$attr[static::$ATTR_TYPE[$attr_type]] = substr($bin, $offset + 2, $attr_len - 2);//属性值
|
||||
//跳到下一个
|
||||
$offset += $attr_len;
|
||||
}
|
||||
//判断offset和$len是否相等,不相等认为无效,抛弃这个封包
|
||||
if ($offset != $len) {
|
||||
return [];
|
||||
}
|
||||
return $attr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 运行服务器
|
||||
* @param int $authPort
|
||||
* @param int $accountPort
|
||||
*/
|
||||
public function run(string $secret_key, int $authPort = 1812, int $accountPort = 1813) {
|
||||
$server = new swoole_server("0.0.0.0", $authPort, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
|
||||
$server->on('Packet', array($this, 'onPacket'));
|
||||
$server->start();
|
||||
$this->server = $server;
|
||||
$this->secret_key = $secret_key;
|
||||
}
|
||||
}
|
||||
|
||||
$server = new radius();
|
||||
$server->run('test123');
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 密码验证
|
||||
|
||||
> 密码有两种的加密方式 `User-Password(PAP加密)`和`CHAP-Password(CHAP加密)`
|
||||
|
||||
### User-Password
|
||||
|
||||
[https://tools.ietf.org/html/rfc2865#page-27](https://tools.ietf.org/html/rfc2865#page-27)
|
||||
|
||||
> 在传输时,密码是隐藏的。首先在密码的末尾用空值填充 16 个字节的倍数。单向 MD5 哈希是通过由共享密钥组成的八位字节流来计算的,后面是请求身份验证器。该值与密码的前 16 个八位位组段进行异或并放置在用户密码属性的字符串字段的前 16 个八位位组中。
|
||||
>
|
||||
> 如果密码长度超过 16 个字符,则第二个单向 MD5 散列值将通过由共享密钥组成的八位字节流计算,然后是第一个异或结果。该散列与密码的第二个 16 位八位字节进行异或,并置于用户密码属性的字符串字段的第二个 16 位字节中。
|
||||
>
|
||||
> 调用**共享密钥 S**和伪随机 128 位请求认证器**RA** 。 将密码分成 16 个八位字节块 p1,p2 等。 最后一个填充为零,最终为 16 个八位字节的边界。 调用密文块 c(1),c(2)等,我们需要中间值 b1,b2 等。
|
||||
|
||||
```
|
||||
b1 = MD5(S + RA) c(1) = p1 xor b1
|
||||
b2 = MD5(S + c(1)) c(2) = p2 xor b2
|
||||
. .
|
||||
. .
|
||||
. .
|
||||
bi = MD5(S + c(i-1)) c(i) = pi xor bi
|
||||
|
||||
The String will contain c(1)+c(2)+...+c(i) where + denotes
|
||||
concatenation.
|
||||
```
|
||||
|
||||
上面其实是 google 翻译来的- -...把我理解的说一下
|
||||
|
||||
当密码位数小于 16 位的时候,先计算出**md5(密钥(secret_key)+Authenticator)**的 md5,然后再与我们接收到的**User-Password**进行位运算,遇到`\x0`结尾字符串结束
|
||||
|
||||
如果超过了 16 位而且还没有遇到`\x0`那么继续和我们的**User-Password**进行位运算,但是这回的用于位运算的 md5 为**md5(密钥(secret_key)+前 16 个字符)**,这里有个坑....文档说的是加密的过程,我们需要实现的是解密,所以前 16 个字符串不是我们解密后的 16 个字符,而是加密后的 16 个字符,=\_=害我拿原文(解密后的)一直在算,不对
|
||||
|
||||
实现代码:
|
||||
|
||||
```php
|
||||
public function decode_user_passwd($bin, $Authenticator) {
|
||||
$passwd = '';
|
||||
$S = $this->secret_key;
|
||||
$len = strlen($bin);
|
||||
//b1 = MD5(S + RA)
|
||||
$hash_b = md5($S . $Authenticator, true);
|
||||
for ($offset = 0; $offset < $len; $offset += 16) {
|
||||
//每次拿16字符进行解码
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
$pi = ord($bin[$offset + $i]);
|
||||
$bi = ord($hash_b[$i]);
|
||||
//c(i) = pi xor bi
|
||||
$chr = chr($pi ^ $bi);
|
||||
if ($chr == "\x0") {
|
||||
//文本标志\x0结尾
|
||||
return $passwd;
|
||||
}
|
||||
$passwd .= $chr;
|
||||
}
|
||||
//判断一下是不是已经结束了,然后返回
|
||||
if ($len == $offset + 16) {
|
||||
return $passwd;
|
||||
}
|
||||
//bi = MD5(S + c(i-1))
|
||||
$hash_b = md5($S . substr($bin, $offset, 16), true);
|
||||
}
|
||||
//都循环完了,还没看见结束,返回空
|
||||
return '';
|
||||
}
|
||||
```
|
||||
|
||||
### CHAP-Password
|
||||
|
||||
> 从上面的代码来看`User-Password`是不安全的,只要知道了**secret_key**,密码明文完全可以解密出来,CHAP 密码原文是无法解密出来的
|
||||
>
|
||||
> RADIUS 服务器根据用户名查找密码,使用 MD5 在 CHAP ID 八位位组,密码和 CHAP 质询(如果存在 CHAP-Challenge 属性,否则从请求身份验证器)加密质询,以及 将该结果与 CHAP 密码进行比较。 如果它们匹配,则服务器发回一个访问接受,否则它将发回一个访问拒绝。
|
||||
>
|
||||
> 此属性指示由 PPP 挑战握手认证协议(CHAP)用户响应挑战而提供的响应值。 它仅用于 Access-Request 数据包。
|
||||
>
|
||||
> 如果存在于数据包中,则在 CHAP-Challenge 属性(60)中找到 CHAP 质询值,否则在请求验证器字段中找到。
|
||||
|
||||
[https://tools.ietf.org/html/rfc2865#page-8](https://tools.ietf.org/html/rfc2865#page-8)
|
||||
|
||||
[https://tools.ietf.org/html/rfc2865#page-28](https://tools.ietf.org/html/rfc2865#page-28)
|
||||
|
||||
CHAP-Password 的结构是这样的,中间还有一个 CHAP Ident,后面 string 是一个 16 个字节的字符串,一听就觉得是 md5
|
||||
|
||||
```
|
||||
A summary of the CHAP-Password Attribute format is shown below. The
|
||||
fields are transmitted from left to right.
|
||||
|
||||
0 1 2
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
||||
| Type | Length | CHAP Ident | String ...
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
||||
```
|
||||
|
||||
感觉 CHAP 比 PAP 好写多了,直接验证**md5(chapid+passwd+CHAP-Challenge(没有则用 Authenticator))**和 String 是否相等就好了
|
||||
|
||||
```php
|
||||
//调用
|
||||
echo "isPass:" . (
|
||||
$this->verify_chap_passwd($attr['CHAP-Password'],
|
||||
'qwe123',
|
||||
$attr['CHAP-Challenge'] ?? $struct['authenticator']
|
||||
) ? 'true' : 'false');
|
||||
|
||||
public function verify_chap_passwd(string $bin, string $pwd, string $chap): bool {
|
||||
if (strlen($bin) != 17) return false;
|
||||
$chapid = $bin[0];
|
||||
$string = substr($bin, 1);
|
||||
return md5($chapid . $pwd . $chap, true) == $string;
|
||||
}
|
||||
```
|
||||
|
||||
### 封包
|
||||
|
||||
> 处理完后,总还得人家一个回信吧
|
||||
|
||||
[https://tools.ietf.org/html/rfc2865#page-19](https://tools.ietf.org/html/rfc2865#page-19)
|
||||
|
||||
都是一样的,只是吧解包变成封包一个逆向操作,值得注意的是**Authenticator**,在接收的时候这个是随机的,发送的时候,我们需要带进去计算**MD5(Code+ID+Length+RequestAuth+Attributes+Secret)**
|
||||
|
||||
```php
|
||||
/**
|
||||
* 封包
|
||||
* @param int $code
|
||||
* @param int $identifier
|
||||
* @param string $reqAuthenticator
|
||||
* @param array $attr
|
||||
* @return string
|
||||
*/
|
||||
public function pack(int $code, int $identifier, string $reqAuthenticator, array $attr = []): string {
|
||||
$attr_bin = '';
|
||||
foreach ($attr as $key => $value) {
|
||||
$attr_bin .= $this->pack_attr($key, $value);
|
||||
}
|
||||
$len = 20 + strlen($attr_bin);
|
||||
//MD5(Code+ID+Length+RequestAuth+Attributes+Secret)
|
||||
$send = pack('ccna16',
|
||||
$code, $identifier, $len,
|
||||
md5(chr($code) . chr($identifier) . pack('n', $len) .
|
||||
$reqAuthenticator . $attr_bin . $this->secret_key, true)
|
||||
) . $attr_bin;
|
||||
//这里实际使用的时候有错误,因为NTR工具没有校验Response Authenticator...现在已经修改了
|
||||
return $send;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封包属性
|
||||
* @param $code
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
public function pack_attr($code, string $data): string {
|
||||
return pack('cc', $code, 2 + strlen($data)) . $data;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
> 完整代码中的封包有错误,请看上面的注释,在代码中修改了
|
||||
|
||||
完成,完整代码: [start.php](./img/03-UDP-radius协议实现.assets/start.zip)
|
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 40 KiB |