mac m1 安装 swoole 报错问题记录
发表于|更新于|码不能停
|总字数:145|阅读时长:1分钟|浏览量:
今天切本地环境的时候发现 swoole 还没装,装的时候又报了错:pcre2.h file not found,鉴于我这个记性越来越差,还是写条笔记记录一下解决办法。
brew install pcre2,如果本机已经安装,就直接进行第二步ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/Cellar/php@8.1/8.1.31/include/php/ext/pcre/pcre2.h,正常安装 pcre2 之后,需要软连接对应目录,注意这里要把目录切换成你实际的目录pecl install swoole,再次运行安装php -m | grep swoole,运行验证
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

2024-03-01
Laravel Octane 和 Swoole 协程的使用分析二
又仔细研究了下 Octane 源码和 Swoole 的文档,关于前几天 Laravel Octane 和 Swoole 协程的使用分析中的猜想,得到进一步验证: Swoole 的 HTTP Server 启动后会创建一个 master 进程和一个 manager 进程;master 进程又会创建多个 reactor 线程,负责将请求转发到 work,并从 work 接收结果发送给客户端,相当于 nginx;manager 会创建多个 work 和 task 子进程,work 进程相当于 php-fpm,task 专门处理一些耗时任务,最后将结果交给 work; 而 LaravelOctane 的 concurrently 方法,其实是以 task 为基础,也就解释了为什么脱离 HTTP server 会无法使用。 Swoole

2023-05-11
php 国密 sm2 sm3 sm4 完整测试类
应用范围及描述 算法类型 国密算法 应用范围及描述 对称加密 SM1 128位数据加密,算法不公开,仅以IP核的形式存在于芯片中。智能IC卡、智能密码钥匙、加密卡、加密机。 非对称加密 SM2 被用来替换RSA算法。常用于身份认证,数据签名,密码交换,256位椭圆曲线。 完整性运算 SM3 256位数据摘要计算,相当于SHA256,数字签名及验证、消息认证码生成及验证、随机数生成 对称加密 SM4 128位数据加密,相当于AES(128) 相关代码php sm2 sm3 sm4 完整测试类,可拖入 laravel unit test 模块运行。基于扩展包 [ lpilp/guomi ] , sm2 与兴业银行有部分区别,sm4 已互通,未做招行验证。 sm2 密钥长度一般为 128 或 130 位,部分使用压缩密钥长度为 66,也就是将密钥分成 x、y,y是偶数就是02,y是奇数就是03,通过 x 可以算出 y。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290<?phpnamespace Tests\Unit;use FG\ASN1\ASNObject;use FG\ASN1\Exception\ParserException;use Mdanter\Ecc\Crypto\Signature\Signature;use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;use PHPUnit\Framework\TestCase;use Rtgm\sm\RtSm2;use Rtgm\sm\RtSm3;use Rtgm\sm\RtSm4;/** * 国密加密测试 * sm4 已与兴业银行调通 */class GmTest extends TestCase{ /** * 获取 sm2 * @return RtSm2 */ private function getSm2(): RtSm2 { return new RtSm2('base64'); } /** * 获取 sm3 * @return RtSm3 */ private function getSm3(): RtSm3 { return new RtSm3(); } /** * 获取 sm4 * @return RtSm4 */ private function getSm4(): RtSm4 { $privateKey = $this->getSm4PrivateKey(); return new RtSm4($privateKey); } /** * 获取 16进制 sm2 密钥 * 生成于工具站 https://www.lzltool.com/SM2 base64格式 * @return string */ private function getSm2PrivateKey(): string { return bin2hex(base64_decode('L8TbMByc+rQmKECWMBjnDQHrXrExqZKdl5S6sBbP07M=')); } /** * 获取 16进制 sm2 公钥 * 生成于工具站 https://www.lzltool.com/SM2 base64格式 * @return string */ private function getSm2PublicKey(): string { return bin2hex(base64_decode('BCxc4cDX1OQEpCD8O7wzPhTOljYg0uzfsMAEanCvYgBIj966+i5pgjwyIOtFSNWLWjoDzLmMJP9nf2cVmiH+aYI=')); } /** * sm2 数据格式化 * @param $dec * @return string */ private function sm2FormatHex($dec): string { $hex = gmp_strval(gmp_init($dec, 10), 16); $len = strlen($hex); if ($len == 64) { return $hex; } return $len < 64 ? str_pad($hex, 64, "0", STR_PAD_LEFT) : substr($hex, $len - 64, 64); } /** * 获取 16位 密钥 * @return bool|string */ private function getSm4PrivateKey(): bool|string { return base64_decode('NmQzZDQ2YTcxMmRjNGE0NQ=='); } /** * 获取待加密字符串 * @return string */ private function getDataStr(): string { return '{"bankCardNo":"6212028190240439021","certNo":"41052619700925136X","userName":"南瓜"}'; } /** * 拼接 sm2 待加密字符串 * @return bool|string */ private function getSm2SignStr(): bool|string { $params = json_decode($this->getDataStr(), true); $signStr = ''; if ($params != null) { ksort($params); foreach ($params as $k => $v) { $signStr .= "{$k}={$v}&"; } } return substr($signStr, 0, strlen($signStr) - 1); } public function test_sm2_sign() { $sm2 = $this->getSm2(); $signStr = $this->getSm2SignStr(); // 加密 $sign = $sm2->doSign($signStr, $this->getSm2PrivateKey()); $encryptStr = base64_decode($sign); try { $a = ASNObject::fromBinary($encryptStr)->getChildren(); } catch (ParserException $e) { $this->fail('加密失败: ' . $e->getMessage()); } $aa = $this->sm2FormatHex($a[0]->getContent()); $bb = $this->sm2FormatHex($a[1]->getContent()); $encryptStr = base64_encode(hex2bin($aa . $bb)); $this->assertNotEmpty($encryptStr); return $encryptStr; } public function test_sm2_verify_sign() { $sm2 = $this->getSm2(); $encryptSignStr = bin2hex(base64_decode($this->test_sm2_sign())); echo 'sm2 sign str: ', $encryptSignStr, PHP_EOL; $r = substr($encryptSignStr, 0, 64); $s = substr($encryptSignStr, 64, 64); $r = gmp_init($r, 16); $s = gmp_init($s, 16); $signature = new Signature($r, $s); $serializer = new DerSignatureSerializer(); $sign = base64_encode($serializer->serialize($signature)); $boolean = $sm2->verifySign($this->getSm2SignStr(), $sign, $this->getSm2PublicKey()) ?? false; echo $boolean ? 'sm2 验签通过' : 'sm2 验签失败', PHP_EOL; $this->assertTrue($boolean); } public function test_sm2_encrypt() { $sm2 = $this->getSm2(); // 压缩公钥 $key = $this->decompressPublicKey('0315edd9126410e9b94b83ee2bcdfeebe9166e84d7aad1b9d16fa923995d28e81f'); $encrypt = $sm2->doEncrypt($this->getDataStr(), $key); $this->assertNotEmpty($encrypt); return $encrypt; } public function test_sm2_decrypt() { $sm2 = $this->getSm2(); $encrypt = $this->test_sm2_encrypt(); $privateKey = 'bf5e3e47e5392a8cdba8e3f854db2d3f5e2c536235303a02898b58d085a8246a'; echo 'sm2 encrypt str: ', $encrypt, PHP_EOL; $decryptStr = $sm2->doDecrypt($encrypt, $privateKey); echo 'sm2 decrypt str: ', $decryptStr, PHP_EOL; $this->assertNotEmpty($decryptStr); $this->assertTrue($decryptStr === $this->getDataStr()); } /** * 获取未压缩公钥 * @param $compressedKey * @return string|null */ function decompressPublicKey($compressedKey): ?string { // 获取压缩标志和X坐标 $flag = substr($compressedKey, 0, 2); $x = substr($compressedKey, 2); // 将16进制字符串转换为大整数 $p = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', 16); $a = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', 16); $b = gmp_init('28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', 16); $gx = gmp_init('32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1711D7AFB1B8B4E16', 16); $gy = gmp_init('BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0', 16); $n = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', 16); // 计算Y坐标 $x = gmp_init($x, 16); $alpha = gmp_powm($x, 3, $p); $beta = gmp_add(gmp_mod(gmp_mul($a, $x), $p), $b); $y2 = gmp_mod(gmp_add($alpha, $beta), $p); $y = gmp_powm($y2, gmp_div_q(gmp_add($p, 1), 4), $p); if ($flag == "02") { // 如果压缩标志为 02,则Y坐标为偶数 if (gmp_strval(gmp_mod($y, 2)) != "0") { $y = gmp_sub($p, $y); } return "04" . gmp_strval($x, 16) . str_pad(gmp_strval($y, 16), 64, "0", STR_PAD_LEFT); } if ($flag == "03") { // 如果压缩标志为 03,则Y坐标为奇数 if (gmp_strval(gmp_mod($y, 2)) != "1") { $y = gmp_sub($p, $y); } return "04" . gmp_strval($x, 16) . str_pad(gmp_strval($y, 16), 64, "0", STR_PAD_LEFT); } return null; } public function test_sm3() { $sm3 = $this->getSm3(); $signStr = $sm3->digest($this->getDataStr()); echo 'sm3 sign str: ', $signStr, PHP_EOL; $this->assertNotEmpty($signStr); } /** * 测试 byteArr to string * @return string */ public function test_sm4_iv() { $byteArr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; $iv = call_user_func_array('pack', array_merge(['C*'], $byteArr)); $this->assertTrue(base64_encode($iv) === 'AAAAAAAAAAAAAAAAAAAAAA=='); return base64_encode($iv); } public function test_sm4_encrypt() { try { $iv = $this->test_sm4_iv(); $sm4 = $this->getSm4(); $encryptJsonStr = $sm4->encrypt($this->getDataStr(), 'sm4', base64_decode($iv)); } catch (\Exception $e) { $this->fail('加密异常:' . $e->getMessage()); } $encryptJsonStr = base64_encode(hex2bin($encryptJsonStr)); $this->assertNotEmpty($encryptJsonStr); return $encryptJsonStr; } public function test_sm4_decrypt() { try { $sm4 = $this->getSm4(); $encryptJsonStr = $this->test_sm4_encrypt(); echo "sm4 encrypt str: " . $encryptJsonStr, PHP_EOL; $decryptJsonStr = $sm4->decrypt(bin2hex(base64_decode($encryptJsonStr)), 'sm4', base64_decode($this->test_sm4_iv())); } catch (\Exception $e) { $this->fail('解密异常:' . $e->getMessage()); } echo "sm4 decrypt str: " . $decryptJsonStr, PHP_EOL; echo $decryptJsonStr === $this->getDataStr() ? 'sm4 数据一致' : 'sm4 数据不一致', PHP_EOL; $this->assertNotEmpty($decryptJsonStr); }}

2024-02-28
Laravel Octane 和 Swoole 协程的使用分析
之前在工作中使用 Laravel Octane 的 concurrently 处理并发时,发现在队列和定时任务中不会触发并发效果。经过分析,作了如下猜测:队列定时任务都属于一个独立的进程,与 Octane 服务无关,而 Octane conturrently 恰恰需要在 Octane 环境下才能运行。 后来通过代码进行环境检测和查看 php 的进程,证明猜想成立。 1234info('check env', [ 'served by octane' => isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1), 'on swoole server' => (extension_loaded('swoole') || extension_loaded('openswoole')) && app()->bound(Server::class)]); 为了能够在任意代码中实现并发,我们研究参考了 Hyperf 框架关于协程的代码,然后抽取了如下两个类: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071<?phpnamespace App\Services;use App\Exceptions\ParallelExecutionException;use Laravel\Octane\Facades\Octane;use Throwable;use Swoole\Coroutine as Co;class Parallel{ protected array $callbacks = []; protected array $results = []; /** * @var Throwable[] */ protected array $throwables = []; public function add(callable $callable, $key = null): void { if (is_null($key)) { $this->callbacks[] = $callable; } else { $this->callbacks[$key] = $callable; } } public function wait(bool $throw = true): array { if (isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1)) { return Octane::concurrently($this->callbacks, 300000); } app('log')->useLoggingLoopDetection(false); Co\run(function () { foreach ($this->callbacks as $key => $callback) { Co::create(function () use ($callback, $key) { try { $this->results[$key] = $callback(); } catch (Throwable $throwable) { $this->throwables[$key] = $throwable; unset($this->results[$key]); } }); } }); if ($throw && ($throwableCount = count($this->throwables)) > 0) { $message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowAbles($this->throwables); $executionException = new ParallelExecutionException($message); $executionException->setResults($this->results); $executionException->setThrowAbles($this->throwables); unset($this->results, $this->throwables); throw $executionException; } app('log')->useLoggingLoopDetection(true); return $this->results; } private function formatThrowAbles(array $throwables): string { $output = ''; foreach ($throwables as $key => $value) { $output .= sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString()); } return $output; }} 1234567891011121314151617181920212223242526272829303132<?phpnamespace App\Exceptions;use RuntimeException;class ParallelExecutionException extends RuntimeException{ protected array $results = []; protected array $throwables = []; public function getResults(): array { return $this->results; } public function setResults(array $results): void { $this->results = $results; } public function getThrowAbles(): array { return $this->throwables; } public function setThrowAbles(array $throwables): array { return $this->throwables = $throwables; }} 其中,第一个类的作用是检测系统是否运行在 Octane 环境下,是则调用Octane concurrently,否则就执行 Swoole 协程代码,使用起来也比较简单: 1234567$parallel = new Parallel();$parallel->add(fn() => $this->analysisStructure(), 'structure');$parallel->add(fn() => $this->analysisHabit(), 'habit');[ 'structure' => $structure, 'habit' => $habit,] = $parallel->wait(); 之所以没有完全使用 Swoole 协程,是因为相比之下,Octane 代码更加优雅,我们在期待着某天更新后,Octane concurrently 也能直接在队列中运行使用。 第二个类的作用比较简单,就是对协程中异常的一个定义。 另外在分析过程中,我们也发现了一个比较有意思的事情: 如图所示,当我在路由中运行检测代码时,Octane 和 Swoole server 都为 true;在控制器中运行检测代码时,又只有 Octane 为true;为什么会有这样的区分?我个人猜测是 Octane 在将框架代码读进内存时,特意跳过了控制器中的代码,以避免数据更新不及时等问题。 有知道具体原因的小伙伴,欢迎留言探讨。

2023-11-20
为你的 Laravel 应用添加一个基于 Swoole 的 WebSocket 服务
做了一个基于 Swoole 的 WebSocket 扩展包,可以用来做实时状态推送,或者自定义消息处理实现 im,有需要的可以看看: [giorgio-socket] 使用方法安装安装扩展包 1composer require wu/giorgio-socket 发布配置文件 1php artisan vendor:publish --provider="GiorgioSocket\Providers\SocketServiceProvider" 运行 Socket 服务 1php artisan socket:start 注意事项 可以通过实现 GiorgioSocket\Services\Handlers\Interfaces 下的接口类来自定义自己的业务逻辑。 如果要从服务端发送消息,这里有两种方式: 第一种,借助 Laravel HTTP 客户端 123456Route::get('/socket', function () { \Illuminate\Support\Facades\Http::asForm()->post('http://127.0.0.1:9501', [ 'to' => 2, 'message' => 'server message', ]);}); 第二种:借助 Laravel Listener,需要将 .env 文件中的 QUEUE_CONNECTION 配置修改为 redis 或其他异步队列。配置更改后,运行以下命令:php-artisan queue:work --queue=socket-listener监听队列,然后按以下代码调用 event: 123Route::any('socket', function (Request $request){ \GiorgioSocket\Events\SocketEvent::dispatch($request->get('to'), $request->get('message'));}); 如果你正在使用 laravel/breeze 扩展包,并且使用了 Blade 模板,可以将以下代码粘贴到 dashboard.blade.php 中进行快速测试: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111@auth <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg"> <div class="grid grid-cols-1 md:grid-cols-2"> <div class="p-6" id="server-message"> messages:<br/> </div> <div class="p-6"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="from">from</label> <input class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" value="{{ auth()->user()->getKey() }}" id="from"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="to">to</label> <input class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" value="" id="to"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="message">message</label> <textarea class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" id="message"></textarea> <input class="inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 mt-3" type="button" id="submit" value="submit"> </div> </div> </div> </div> </div> <script type="text/javascript"> let heartBeatTimer = 0; let socket = connectWebSocket(); function startHeartbeat(interval) { interval = interval || 30; heartBeatTimer = setInterval(function () { sendMessage(null, "heart_beat"); }, interval * 1000); } function stopHeartbeat() { clearInterval(heartBeatTimer); } function connectWebSocket() { const wsServer = 'ws://127.0.0.1:9501'; const socket = new WebSocket(wsServer); let userId = document.getElementById('from').value; socket.onopen = function (evt) { let data = { user_id: userId, type: 'connect' }; console.log('open', data) socket.send(JSON.stringify(data)); }; socket.onmessage = function (evt) { console.log('get message from server: ' + evt.data); if (evt.data !== 'heart_beat') { let data = JSON.parse(evt.data); let message = document.getElementById("server-message") message.innerHTML += data.user_name + ': ' + data.data + '<br/>' } }; socket.onerror = function (evt) { console.log(evt); }; socket.onclose = function () { let data = { user_id: userId, type: 'close' }; socket.send(JSON.stringify(data)); }; return socket; } function sendMessage(to, message) { if (socket != null && socket.readyState === WebSocket.OPEN) { if (message !== 'heart_beat') { let messageBox = document.getElementById("server-message") messageBox.innerHTML += 'me: ' + message + '<br/>' } let from = document.getElementById("from") socket.send(JSON.stringify({ user_id: from.value, user_name: '{{ auth()->user()->name }}', to: to, type: 'message', data: message, })); console.log("webSocket send message:" + JSON.stringify({ user_id: from.value, user_name: '{{ auth()->user()->name }}', to: to, type: 'message', data: message, })); } else { console.log("webSocket closed"); } } let button = document.getElementById("submit"); button.addEventListener('click', function () { let message = document.getElementById("message"); let to = document.getElementById("to"); sendMessage(to.value, message.value) }); </script>@endauth 如有任何疑问,欢迎提交 [issue]

2024-09-19
Mac 用 Brew 安装旧版本 PHP
最近又用到 php7.4,奈何本地早就已经升到了 8,想搞回来,发现一个简单好用爽歪歪的三方库,需要的可以试一下,5.6 - 8.4 都有。[ homebrew-php ] 添加地址库1brew tap shivammathur/php 安装指定版本1brew install shivammathur/php/php@7.4 切换版本12345brew services stop php@7.4brew unlink php@xxbrew link php@7.4brew services start php@7.4 搞定收工。

2018-02-08
openssl_pkey_get_private 内存泄漏
从前,有一块使用 swoole_process 做多线程处理的程序,这个程序有一个奇怪的问题:每运行一个星期左右都会因为内存爆表而停掉。 因为爆表的时间关系,开发的小伙伴们都认为是这段使用 swoole_process 的程序有问题,几经更新改版,代码已经趋于完美,平稳的运行了几天后,小伙伴们终于都松了一口气,终于解决了这个问题。 然而,伴随着百年难遇的血月,这块程序还是在一个意想不到的时间又爆了一次,小伙伴们终于意识到,问题的根本并不在这里,想要找出并改掉这个问题,是何等艰难!但是他们知道,真相只有一个! 又经过几天的排查,凶手终于浮出水面! 呐,就是这孙子:openssl_pkey_get_private 抽出的测试代码: 123456789while(true){ $key = file_get_contents('mock_A.pem'); echo $m=memory_get_usage(),PHP_EOL; $getkey = openssl_pkey_get_private($key, ''); openssl_pkey_free($getkey); echo $mm = memory_get_usage(),PHP_EOL; echo "Before unset: ",$mm-$m ,PHP_EOL; sleep(3);} 结果: 审判结果:该阉的就赶紧阉了吧 行刑后长相: 12345678910$key = file_get_contents('mock_A.pem');$getkey = openssl_pkey_get_private($key, '');while(true){ echo $m=memory_get_usage(),PHP_EOL; openssl_pkey_free($getkey); echo $mm = memory_get_usage(),PHP_EOL; echo "Before unset: ",$mm-$m ,PHP_EOL; sleep(3);} 前线专家点评:纯属冤案,哪儿特么这么容易泄漏,都是因为自己写的不规范! 注:生成公私钥是需要消耗内存的,每次脚本执行结束后会释放掉,但是常驻内存的脚本没有释放的机会,所以每次循环调用都会造成内存增加,持续增加到一定量后系统也就崩了。所以解决办法是在循环开始之前就把密钥生成好。