Fork me on GitHub

随笔分类 - php

swoole创建websocket服务器

[TOC] swoole算是nodejs在php中的一种实现,异步响应请求,性能超强 # 1 安装准备 ## 1.1 安装swoole前必须保证系统已经安装了下列软件 php-5.3.10 或更高版本 gcc-4.4 或更高版本 make autoconf pcre (centos系统可以执行命令:yum install pcre-devel) ## 1.2 下载并解压 下载地址 https://github.com/swoole/swoole-src/releases 进入页面后选择download链接下的tar.gz的压缩包 下载源代码包后,解压 ``` tar xzvf xxx.tar.gz ``` 在终端进入源码目录,执行下面的命令进行编译和安装 ``` cd swoole phpize ./configure --enable-swoole-debug make sudo make install ``` 编译参数根据自己的需求选择,详情参看官方文档。 ## 1.3 编译安装成功后,修改php.ini 在php.ini中加入 `extension=swoole.so` 通过在命令行使用 php-m查看,是否安装了swoole 注意:如通重新编译的话需要 make clean # 2 构建Swoole基本实例 ## 2.1 tcp服务器实例 (来自w3cschool教程https://www.w3cschool.cn/swoole/bnte1qcd.html) **服务端代码:Server.php** ``` <?php // Server class Server { private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); } public function onStart( $serv ) { echo "Start\n"; } public function onConnect( $serv, $fd, $from_id ) { $serv->send( $fd, "Hello {$fd}!" ); } public function onReceive( swoole_server $serv, $fd, $from_id, $data ) { echo "Get Message From Client {$fd}:{$data}\n"; } public function onClose( $serv, $fd, $from_id ) { echo "Client {$fd} close connection\n"; } } // 启动服务器 $server = new Server(); ``` 从代码中可以看出,创建一个swoole_server基本分三步: 1. 通过构造函数创建swoole_server对象 2. 调用set函数设置swoole_server的相关配置选项 3. 调用on函数设置相关回调函数 关于set配置选项以及on回调函数的具体说明,请参考我整理的swoole文档( 配置选项) 这里只给出简单介绍。onStart回调在server运行前被调用,onConnect在有新客户端连接过来时被调用,onReceive函数在有数据发送到server时被调用,onClose在有客户端断开连接时被调用。 这里就可以大概看出如何使用swoole:在onConnect处监听新的连接;在onReceive处接收数据并处理,然后可以调用send函数将处理结果发送出去;在onClose处处理客户端下线的事件。 **客户端的代码:Client.php** ``` <?php class Client { private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP); } public function connect() { if( !$this->client->connect("127.0.0.1", 9501 , 1) ) { echo "Error: {$fp->errMsg}[{$fp->errCode}]\n"; } $message = $this->client->recv(); echo "Get Message From Server:{$message}\n"; fwrite(STDOUT, "请输入消息:"); $msg = trim(fgets(STDIN)); $this->client->send( $msg ); } } $client = new Client(); $client->connect(); ``` 这里,通过swoole_client创建一个基于TCP的客户端实例,并调用connect函数向指定的IP及端口发起连接请求。随后即可通过recv()和send()两个函数来接收和发送请求。需要注意的是,这里我使用了默认的同步阻塞客户端,因此recv和send操作都会产生网络阻塞。 **使用方法** 进入到文件目录,在窗口1先启动php Serve.php,然后再开一个窗口(窗口2)启动php Client.php 窗口1内容: ``` # root @ WENGINE in /data/learnSwoole [9:24:57] C:130 $ php Server.php Start Get Message From Client 1:ceshi1 Client 1 close connection ``` 窗口2内容: ``` # root @ WENGINE in /data/learnSwoole [9:23:07] $ php Client.php Get Message From Server:Hello 1! 请输入消息:ceshi1 ``` ## 2.2 web服务器 **服务端代码 http_server.php** ``` $http = new swoole_http_server("0.0.0.0", 9501); $http->on('request', function ($request, $response) { var_dump($request->get, $request->post); $response->header("Content-Type", "text/html; charset=utf-8"); $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>"); }); $http->start(); ``` Http服务器只需要关注请求响应即可,所以只需要监听一个onRequest事件。当有新的Http请求进入就会触发此事件。事件回调函数有2个参数,一个是$request对象,包含了请求的相关信息,如GET/POST请求的数据。 另外一个是response对象,对request的响应可以通过操作response对象来完成。$response->end()方法表示输出一段HTML内容,并结束此请求。 ● 0.0.0.0 表示监听所有IP地址,一台服务器可能同时有多个IP,如127.0.0.1本地回环IP、192.168.1.100局域网IP、210.127.20.2 外网IP,这里也可以单独指定监听一个IP ● 9501 监听的端口,如果被占用程序会抛出致命错误,中断执行。 ## 2.3 WebSocket服务器 **服务端程序代码 ws_server.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(); ``` WebSocket服务器是建立在Http服务器之上的长连接服务器,客户端首先会发送一个Http的请求与服务器进行握手。握手成功后会触发onOpen事件,表示连接已就绪,onOpen函数中可以得到$request对象,包含了Http握手的相关信息,如GET参数、Cookie、Http头信息等。 建立连接后客户端与服务器端就可以双向通信了。 ● 客户端向服务器端发送信息时,服务器端触发onMessage事件回调 ● 服务器端可以调用$server->push()向某个客户端(使用$fd标识符)发送消息 ● 服务器端可以设置onHandShake事件回调来手工处理WebSocket握手 运行程序 **客户端的代码** 可以使用Chrome浏览器进行测试,JS代码为: ``` var wsServer = 'ws://127.0.0.1:9502'; var websocket = new WebSocket(wsServer); websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); }; websocket.onclose = function (evt) { console.log("Disconnected"); }; websocket.onmessage = function (evt) { console.log('Retrieved data from server: ' + evt.data); }; websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; ``` - 不能直接使用swoole_client与websocket服务器通信,swoole_client是TCP客户端 - 必须实现WebSocket协议才能和WebSocket服务器通信,可以使用swoole/framework提供的PHP WebSocket客户端 - WebSocket服务器除了提供WebSocket功能之外,实际上也可以处理Http长连接。只需要增加onRequest事件监听即可实现Comet方案Http长轮询。 *关于onRequest回调* swoole_websocket_server 继承自 swoole_http_server - 设置了onRequest回调,websocket服务器也可以同时作为http服务器 - 未设置onRequest回调,websocket服务器收到http请求后会返回http 400错误页面 - 如果想通过接收http触发所有websocket的推送,需要注意作用域的问题,面向过程请使用“global”对swoole_websocket_server进行引用,面向对象可以把swoole_websocket_server设置成一个成员属性 可以创建更多的服务器 参照官方文档尝试 https://wiki.swoole.com/wiki/page/475.html # 3 使用laravel5.5实现的前后台通信实例 主要思路是使用php artisan 自建命令控制服务端,使用HTML5的websocket实现客户端功能 **服务端:app/Console/Commands/Websocket.php内容** ``` <?php namespace App\Console\Commands; use Illuminate\Console\Command; use swoole_http_request; use swoole_http_response; use swoole_websocket_server; class WebSocket extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'websocket {cmd=start : can use start|stop|status|restart} {--daemon : set to run in daemonize mode} '; /** * The console command description. * * @var string */ protected $description = 'swoole server control'; /** * server * * @var swoole_websocket_server */ private $server; /** * * TYPE_ADMIN * */ const TYPE_ADMIN = 0X00; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * 处理不同command信息 * * @return mixed */ public function handle() { $command = $this->argument('cmd'); $option = $this->option('daemon'); switch ($command) { case 'start': $this->initWs($option); break; case 'stop': $res = $this->sendAdminRequest($command); if ($res){ $this->info('stop the server successfully'); } else { $this->info('the server is not running'); } break; case 'status': $res = $this->sendAdminRequest($command); if ($res){ $this->info($res); } else { $this->info('the server is not running'); } break; case 'restart': $res = $this->sendAdminRequest($command); if ($res){ $this->info('restart the server successfully'); } else { $this->info('the server is not running'); } break; default: $this->info('请按照下面格式输入命令:php artisan websocket {start|stop|status|restart}'); break; } } //初始化服务端 public function initWs($daemonize = false) { if ($daemonize) { $this->info('Starting Websocke server in daemon mode...'); } else { $this->info('Starting Websocke server in interactive mode...'); } $server = new swoole_websocket_server('0.0.0.0', 9501); $server->set([ 'daemonize' => $daemonize, 'log_file' => '/var/log/websocket.log' ]); $server->on('close', function ($server, $fd) { $this->info('close websocket server'); }); $server->on('open', function (swoole_websocket_server $server, $request) { $this->info('websocket open'); }); $server->on('open', [$this, 'onOpen']); $server->on('close', [$this, 'onClose']); $server->on('message', [$this, 'onMessage']); $server->on('request', [$this, 'onRequest']); $this->server = $server; $this->server->start(); } public function onOpen(swoole_websocket_server $server, $request) { $this->info('websocket open'); } public function onClose($server, $fd) { $this->info('close websocket server'); } public function onMessage(swoole_websocket_server $server, $frame) { $this->info($frame->data); $data = json_decode($frame->data, true); //对data进行逻辑处理 $reply = '发送的信息是:' . $data['message']; $response = [ 'status' => true, 'data' => $reply ]; $server->push($frame->fd, json_encode($response)); } //websocket客户端同样支持http协议 public function onRequest(swoole_http_request $request, swoole_http_response $response) { if ($request->post['type'] == self::TYPE_ADMIN) { $ret = json_encode($this->commandHandle($request->post['content'])); return $response->end($ret); } } //操作命名 public function commandHandle($command) { if ($command == 'status') { $this->info('handle status'); return $this->server->stats(); } if ($command == 'restart') { $this->info('handle restart'); return $this->server->reload(); } if ($command == 'stop') { $this->info('handle stop'); return $this->server->stop() && $this->server->shutdown(); } return 'Unknown Command'; } //发送http请求 public function sendAdminRequest($content) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:9501"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'type' => self::TYPE_ADMIN, 'content' => $content ]); $response = curl_exec($ch); curl_close($ch); return $response; } } ``` **客户端内容** ``` <!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>websocket client</title> </head> <body> <div> <input id="message-content" type="text" name="message" /> <button onclick="sendMessage()">发送消息</button> </div> </body> <script> var wsServer = 'ws://115.159.81.46:9501'; var websocket = new WebSocket(wsServer); websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); }; websocket.onclose = function (evt) { console.log("Disconnected"); }; websocket.onmessage = function (evt) { console.log('从服务器接收到json信息: ' + evt.data); alert('服务器返回信息:' + JSON.parse(evt.data).data); }; websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; function sendMessage(){ var content = document.getElementById('message-content').value; var data = { message : content, } websocket.send(JSON.stringify(data)); }; </script> </html> ``` **启动websocket服务器** 进入系统根目录, php artisan websocket [--daemon] //是否使用daemon模式 php artisan websocket start|stop|status|restart //默认是start

cgi、fastcgi、php-cgi、php-fpm的关系

# 1. CGI CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。 CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。 服务器接收到请求后,如果是index.html这样的静态文件,可以直接去相应的目录找到这个文件,然后返回给客户端,但是当发送的请求是index.php这样请求,显然这个是需要解析的,此时就需要服务器将这个请求传递给cgi程序解析,解析完成后返回结果。但是要传递什么内容呢,这个就是cgi来规定的。 # 2. Fastcgi Fastcgi是用来提高CGI程序性能的,是CGI的升级版,一种语言无关的协议 服务器每次将请求传递给cig程序解析的时候都会解析配置文件,比如php.ini,想想就知道这回影响性能,fastcgi会先启动一个master解析配置文件,初始化环境,然后再启动多个worker,当请求过来的时候master会传递给woker,然后立即去接受下一个请求。当worker不够用的时候会增加,当空闲的worker多的时候会停掉一些,这样的机制可以提高性能,节省资源。 # 3. php-cgi - PHP-CGI是php自带的Fast-CGI管理器. - php.ini修改之后,必须kill掉php-cgi再启动php.ini 才生效。不可以平滑的重启 - 内存不能动态分配 - 启动php,指定启动的worker ,长期驻留在内存里 ,用户访问php文件, php-cgi 处理请求,返回结果 # 4. Php-fmp - 非官方fastCgi进程管理器,后来php5.4开始,被官方收录了 - 可以平滑重启php - 动态调度进程 - 启动php,动态指定启动的worker ,长期驻留在内存里 ,根据来访压力动态增减worker的进程数量,用户访问php文件, php-fpm 处理请求,返回结果 php-cgi和php-fpm的关系呢? php54是之前是一种关系,php54之后另一种关系。php54之前,php-fpm(第三方编译)是管理器,php-cgi是解释器。php54之后,php-fpm(官方自带),master 与 pool 模式。php-fpm 和 php-cgi 没有关系了。php-fpm又是解释器,又是管理器网上大部分说法:php-fpm 是管理php-cgi 的,是针对php54之前的 ## 参考资料 https://www.awaimai.com/371.html https://segmentfault.com/q/1010000000256516 http://cuishan.win/2017/02/05/cgi-fastcgi-php-cgi-php-fpm/ https://www.zhihu.com/question/55835080

php安全性问题

[TOC] # 常见攻击类型 ## 1.sql注入: 攻击者把SQL命令插入到Web表单的输入域或页面请求的字符串,欺骗服务器执行恶意的SQL命令。 防范方法: - 1.检查变量数据类型和格式 - 2.过滤特殊符号 - 3.绑定变量,使用预处理语句 ## 2.xss攻击 XSS其实就是Html的注入问题,攻击者的输入没有经过严格的控制进入了数据库,最终显示给来访的用户,导致可以在来访用户的浏览器里以浏览用户的身份执行Html代码, 数据流程如下:攻击者的Html输入—>web程序—>进入数据库—>web程序—>用户浏览器。 防范方法:使用htmlspecialchars函数将特殊字符转换成HTML编码,过滤输出的变量 ## 3.csrf攻击: CSRF 顾名思义,是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。 要完成一次CSRF攻击,受害者必须依次完成两个步骤: - 1.登录受信任网站A,并在本地生成Cookie。 - 2.在不登出A的情况下,访问危险网站B。 XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。因为网站A有漏洞,当我们访问的时候,可能已经被注入了访问危险网站B的操作 防范方法: - 1、检查网页的来源,比如laravel使用令牌 - 2、检查内置的隐藏变量 - 3、使用POST,不要使用GET,处理变量也不要直接使用$_REQUEST # php安全三板斧:过滤输入、验证数据,以及转义输出。 ## 1.数据过滤: 过滤输入是指转义或删除不安全的字符。在数据到达应用的存储层之前,一定要过滤输入数据,这是第一道防线, - HTML 我们可以使用PHP提供的htmlentities函数过滤HTML,该函数会将所有HTML标签字符(&、<、>等)转化为对应的HTML实体,以便在应用存储层取出后安全渲染。 - SQL查询 在SQL查询中一定不能使用未过滤的输入数据,如果要在SQL查询中使用输入数据,一定要使用PDO预处理语句(PDO是PHP内置的数据库抽象层,为不同的数据库驱动提供统一接口),PDO预处理语句是PDO提供的一个功能,可以用于过滤外部数据,然后把过滤后的数据嵌入SQL语句,避免出现上述SQL注入问题,此外预处理语句一次编译多次运行,可以有效减少对系统资源的占用,获取更高的执行效率。关于PDO后我们后续还会在数据库部分重点讨论。 值得注意的是,很多现代PHP框架都使用了MVC架构模式,将数据库的操作封装到了Model层,框架底层已经做好了对SQL注入的规避,只要我们使用模型类提供的方法执行对数据库的操作,基本上可以避免SQL注入风险。 ## 2.验证数据: 可以php原生实现,借助php组件实现,laravel中是有专门的验证类validation ## 3.转义输出: 把输出渲染成网页或API响应时,一定要转义输出,这也是一种防护措施,能避免渲染恶意代码,造成XSS攻击,还能防止应用的用户无意中执行恶意代码。 laravel中使用blade模板,模板引擎在底层已经为了做好了转义处理 # laravel 中如何避免csrf攻击 ![](http://markdown.archerwong.cn/2019-01-02-00-49-21_1.png) 用户在网站b的时候发送了操作a网站的请求,同时a网站下已经有了cookie信息,所以是合法的。laravel通过csrf_token来进行判定是不是我们自己的网站访问的,因为每次访问都带着这个token,如果是网站b过来的非法操作a网站请求,肯定不会带着这个token,也就不合法了。 参考资料: http://www.freebuf.com/articles/web/39234.html http://www.cnblogs.com/luyucheng/p/6234524.html http://laravelacademy.org/post/4610.html http://laravelacademy.org/post/4628.html http://laravelacademy.org/post/4699.html

php和js中数组的总结

[TOC] 注意: - php中数组的表示方法:array()或者[] - js中数组的表示方法:new array()或者[] # 一.php中初始化数组 在PHP中声明数组的方式主要有两种:一是应用array()函数声明数组,一是直接为数组元素赋值。 - 1>有两种创建索引数组的方法: 索引是自动分配的(索引从 0 开始): ```   $cars=array("Volvo","BMW","SAAB"); ``` 或者也可以手动分配索引: ```   $cars[0]="Volvo";   $cars[1]="BMW";   $cars[2]="SAAB"; ``` - 2>有两种创建关联数组的方法: ``` $age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43"); ``` 或者: ``` $age['Peter']="35"; $age['Ben']="37"; $age['Joe']="43"; ``` 自 PHP 5.4 起,可以使用短数组语法,用 [] 代替 array()。 例如,用 `$cars=["Volvo","BMW"]`; 代替 `$cars=array("Volvo","BMW")`; - 3>数组的形式 一维数组形式: ```   $cars=array("Volvo","BMW","SAAB");   $array=array("1"=>"编","2"=>"程","3"=>"词","4"=>"典"); ``` 二维数组形式 ```   $cars = array(     array("Volvo",22,18),     array("BMW",15,13)   );   $role_info = array(     array("role_name" =>"管理员","desc" =>"","level"=>1)),     array("role_name" =>"普通用户","desc" =>"","level"=>2)   ); ``` # 二.js中初始化数组 var arr = [];是一个数组(或者说数组对象); var arr = {};是一个对象 初始化数组 方法一: ``` var arr=[12, 5, 8, 9]; var arr=[12, 5, 8, [9,10,11], { x:1, y:3}]; ``` 方法二: 1> ``` var arr=new Array(12, 5, 8, 9); var arr=new Array(12, 5, 8, [9,10,11], { x:1, y:3}); ``` 2> ``` var arr=new Array(); arr[0]="bmw"; arr[1]="mini"; arr[2] = { x:1, y:3} ``` # 三.php和js中的区别 数组表示有序数据的集合,对象表示无序数据的结合,直观上数组的数据没有名称,对象的数据有名称, 但是很多编程语言中都有'关联数组'这种形式,也正是这就造成了js和php中的些许不同 php中关联数组也是数组,用array()或者[]表示 js中'关联数组'叫做对象,用{}表示 定义一个关联数组 ``` a['name'] = 'archer'; a['city'] = 'beijing'; ``` 1>在js中以上等于一个对象,而不是数组 ```   var a = {'name':'archer','city':'beijing'} ``` 2>在php中这就是一个数组 ```   var a = ['name':'archer','city':'beijing'] ```

php中redis的使用

经常用到redis,但基本上都是基于集成开发环境,redis的连接也是基于框架自身,总感觉缺点什么,恰好在ubuntu系统中apt-get并不直接提供php的redis扩展,借此机会总结了下redis对php的支持 如果想想要在程序中使用redis,那么要有两个先决条件,第一要安装redis程序,第二要使redis支持php 这里主要谈一下redis对php的支持,我们常见的有两种 - 1.phpredis(实现方式:php扩展,c语言) 特点:需要我们在开发环境中安装php扩展,使用比较方便,缺点是扩展的门槛比较高,但是运行速度快,ci框架官方文档中采此方式 - 2.Predis(实现方式:纯php语言) 特点:比较方便,易于扩展,laraval框架官方文档中采用此方式 # 一.ubuntu安装phpredis扩展,php版本是7.0 1.准备工作:安装扩展需要phpize 查看可供安装的扩展 apt search php7 发现 php7.0-dev扩展是支持的,开始安装 apt-get install php7.0-dev 2.开始安装php的redis扩展 ``` [email protected]:/tmp# git clone -b php7 https://github.com/phpredis/phpredis.git [email protected]:/tmp# mv phpredis/ /etc/ [email protected]:/tmp# cd /etc/phpredis [email protected]://etc/phpredis# phpize [email protected]://etc/phpredis# ./configure [email protected]://etc/phpredis#make && make install ``` 3.启动扩展 `vim / etc / php / 7.0 / fpm / php .ini` ,在配置文件中添加如下语句:extension=redis.so 最后使用命令来重启 PHP 服务: `service php7.0-fpm restart` 4.测试是否成功,书写test.php文件,加入以下内容 ``` <?php phpinfo();  成功显示redis项,证明安装成功 5.使用php去访问redis 书写testredis.php文件,内容如下 <?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->set('say','hello world'); echo 'archer: '.$redis->get('say'); ``` 输出以下内容,证明访问成功 ``` archer: hello world ``` # 二.predis的使用 1.首先考虑,如果你已经安装了phpredis扩展,那么可能要考虑冲突问题,具体没有验证,网上有此说法 ,所以我的做法是将redisphp启动扩展操作中extension=redis.so这句注释掉 2.下载predis:https://codeload.github.com/nrk/predis/zip/v1.1, 然后解压到你相应的站点下 目录结构如下 3.引入predis进行测试,创建testpredis.php文件,内容如下 ``` <?php require './predis-1.1/autoload.php'; $client = new Predis\Client(); $client->set('foo', 'bar'); $value = $client->get('foo'); var_dump($value);exit; ``` 输出结果:`string(3) "bar"` 证明引入成功 4.关于连接,默认是127.0.0.1 端口6379,我们也可以自己传入参数 ``` $client = new Predis\Client([ 'scheme' => 'tcp', 'host' => '10.0.0.1', 'port' => 6379, ]); // Same set of parameters, passed using an URI string: $client = new Predis\Client('tcp://10.0.0.1:6379'); ``` 参考资料 http://blog.csdn.net/u013474436/article/details/53131259 http://www.tuicool.com/articles/JVnqUnE http://blog.csdn.net/zls986992484/article/details/52730725 https://github.com/nrk/predis https://github.com/phpredis/phpredis

php中关于时间的总结

## 获取时间和转换格式 ``` //1.time():返回当前时间的Unix时间戳 $stimestamp = time(); $date = date("Y-m-d h:i:sa",$stimestamp) //2.mktime():返回一个指定日期的Unix时间戳 //语法:mktime(hour,minute,second,month,day,year,is_dst); $stimestamp = mktime(9,12,31,6,10,2016) $date = date("Y-m-d h:i:sa",$stimestamp) //3.date(formate,timestamp);返回时间戳对应的日期 $date = date("Y-m-d h:i:sa",$stimestamp) //4.strtotime();函数将任何英文文本的日期或时间描述解析为 Unix 时间戳(自 January 1 1970 00:00:00 GMT 起的秒数)。 //语法:strtotime(time,now); strtotime('2014-09-10 11:01:46'); echo(strtotime("now") . "<br>"); echo(strtotime("15 October 1980") . "<br>"); echo(strtotime("+5 hours") . "<br>"); echo(strtotime("+1 week") . "<br>"); echo(strtotime("+1 week 3 days 7 hours 5 seconds") . "<br>"); echo(strtotime("next Monday") . "<br>"); echo(strtotime("last Sunday")); ```

php中使用curl

[TOC] # 1. curl简单使用步骤 要使用cURL来发送url请求,具体步骤大体分为以下四步: - 1.初始化 - 2.设置请求选项 - 3.执行一个cURL会话并且获取相关回复 - 4.释放cURL句柄,关闭一个cURL会话 ``` // 1. 初始化一个cURL会话 $ch = curl_init(); // 2. 设置请求选项, 包括具体的url curl_setopt($ch, CURLOPT_URL, "http://www.baidu.com"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); // 3. 执行一个cURL会话并获取相关回复 $response = curl_exec($ch); // 4. 释放cURL句柄,关闭一个cURL会话 curl_close($ch); ``` 上述代码中使用到了四个函数 - curl_init() 初始化CURL连接。 - curl_exec() 执行CURL请求,如果没有错误发生,该函数的返回是对应URL返回的数据;如果发生错误,该函数返回 FALSE。 - curl_setopt() 可以通过设定CURL函数库定义的选项来定制HTTP请求。上述代码片段中使用了三个重要的选项: - CURLOPT_URL 指定请求的URL; - CURLOPT_RETURNTRANSFER 设置为1表示稍后执行的curl_exec函数的返回是URL的返回字符串,而不是把返回字符串定向到标准输出并返回TRUE; - CURLLOPT_HEADER设置为0表示不返回HTTP头部信息。 - curl_close() 关闭CURL连接。 # 2. 错误处理 可以处理response的结果,根据结果进一步操作 ``` $response = curl_exec($ch); if ($response === FALSE) { echo "cURL 具体出错信息: " . curl_error($ch); } ``` 需要注意的是,判断输出是否为FALSE用的是全等号,这是为了区分返回空串和出错的情况。 # 3. 获取curl请求的具体信息 在curl_exec()函数执行之后,可以使用curl_getinfo()函数获取CURL请求输出的相关信息,如下面的例子 ``` curl_exec($ch); $curl_info= curl_getinfo($ch); echo "收到的http回复的code为: {$curl_info['http_code']}"; ``` curl_getinfo 的用法,参考地址 http://cn2.php.net/manual/zh/function.curl-getinfo.php 语法 ``` mixed curl_getinfo ( resource $ch [, int $opt = 0 ] ) ``` 上面有两个参数 - ch 由 curl_init() 返回的 cURL 句柄。 - opt 这个参数可能是以下常量之一: ``` CURLINFO_EFFECTIVE_URL - 最后一个有效的URL地址 CURLINFO_HTTP_CODE - 最后一个收到的HTTP代码 CURLINFO_FILETIME - 远程获取文档的时间,如果无法获取,则返回值为“-1” CURLINFO_TOTAL_TIME - 最后一次传输所消耗的时间 CURLINFO_NAMELOOKUP_TIME - 名称解析所消耗的时间 CURLINFO_CONNECT_TIME - 建立连接所消耗的时间 CURLINFO_PRETRANSFER_TIME - 从建立连接到准备传输所使用的时间 CURLINFO_STARTTRANSFER_TIME - 从建立连接到传输开始所使用的时间 CURLINFO_REDIRECT_TIME - 在事务传输开始前重定向所使用的时间 CURLINFO_SIZE_UPLOAD - 以字节为单位返回上传数据量的总值 CURLINFO_SIZE_DOWNLOAD - 以字节为单位返回下载数据量的总值 CURLINFO_SPEED_DOWNLOAD - 平均下载速度 CURLINFO_SPEED_UPLOAD - 平均上传速度 CURLINFO_HEADER_SIZE - header部分的大小 CURLINFO_HEADER_OUT - 发送请求的字符串 CURLINFO_REQUEST_SIZE - 在HTTP请求中有问题的请求的大小 CURLINFO_SSL_VERIFYRESULT - 通过设置CURLOPT_SSL_VERIFYPEER返回的SSL证书验证请求的结果 CURLINFO_CONTENT_LENGTH_DOWNLOAD - 从Content-Length: field中读取的下载内容长度 CURLINFO_CONTENT_LENGTH_UPLOAD - 上传内容大小的说明 CURLINFO_CONTENT_TYPE - 下载内容的Content-Type:值,NULL表示服务器没有发送有效的Content-Type: header ``` 1、如果不设置第二个参数opt,curl_getinfo返回的是一个关联数组,包含以下数据 ``` url:网络地址。 content_type:内容编码。 http_code:HTTP状态码。 header_size:header的大小。 request_size:请求的大小。 filetime:文件创建的时间。 ssl_verify_result:SSL验证结果。 redirect_count:跳转计数。 total_time:总耗时。 namelookup_time:DNS查询耗时。 connect_time:等待连接耗时。 pretransfer_time:传输前准备耗时。 size_uplpad:上传数据的大小。 size_download:下载数据的大小。 speed_download:下载速度。 speed_upload:上传速度。 download_content_length:下载内容的长度。 upload_content_length:上传内容的长度。 starttransfer_time:开始传输的时间表。 redirect_time:重定向耗时。 ``` 2、如果设置了第二个参数,那么返回的只有指定的信息。 例如设置$opt为`CURLINFO_TOTAL_TIME`,则curl_getinfo()函数只返回`total_time`,即总传输消耗的时间,在只需要关注某些传输信息时,设置$opt参数很有意义。 # 4. 使用curl发送get请求 这个比较简单,其实就是指定正确的url就可以了 ``` $url = 'https://www.google.com/search?ei=F0h1W8nkL6rJjwTRpYyoCA&q=php+curl&oq=php+curl&gs_l=psy-ab.3..35i39k1l2j0i67k1l2j0i203k1l3j0i67k1j0i203k1j0i67k1.326850.328536.0.328745.10.7.1.0.0.0.339.651.3-2.2.0....0...1c.1j4.64.psy-ab..7.3.656.0...0.8TB_OPcyWsY'; //初始化 $ch = curl_init(); //设置选项,包括URL curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); //执行并获取HTML文档内容 $output = curl_exec($ch); //释放curl句柄 curl_close($ch); //打印获得的数据 print_r($output); ``` # 5. 使用curl发送post请求 ``` $url = "http://www.baidu.com"; $post_data = array ( "blog_name" => "test", "action" => "Submit" ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, false); // 设置请求为post类型 curl_setopt($ch, CURLOPT_POST, 1); // 添加post数据到请求中 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); // 执行post请求,获得回复 $response= curl_exec($ch); curl_close($ch); echo $response; ``` 实际上我们上面用到了两个重要的参数 - CURLOPT_POSTFIELDS 设置该选项为POST字符串数据就可以把请求放在正文中。 - CURL_POST 设置了CURL_POST为true,标识这个请求是一个POST请求。 # 6. 使用curl上传文件 ``` <?php $url = "/xx/yy"; $post_data = ['myfile' => new \CURLFILE($file_path), 'dir_path' => $dir_path]; $ch = curl_init(); curl_setopt($ch , CURLOPT_URL , $url); curl_setopt($ch , CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch , CURLOPT_POST, 1); curl_setopt($ch , CURLOPT_POSTFIELDS, $post_data); $output = curl_exec($ch); curl_close($ch); ``` 这里需要特别说明下,以下这种写法 ``` $post_data = array( // 需要注意的是,在路径前必须带上@,不然只会当做是简单的键值对 'pic' => '@'.realpath($path) 'name' => 'issac' ); ``` 只有在php5.5以下版本有效,换言之现在根本没有用,而且现在网上充斥的全是这种过时的失效版本,@字符什么,现在根本没有用了,很多资料并没有注明,害我调试了好久。 所以为了兼容不同版本,可以参考这个方法 ``` function upload_file($url,$filename,$path,$type){ //php 5.5以上的用法 if (class_exists('\CURLFile')) { $data = array('file' => new \CURLFile(realpath($path),$type,$filename)); } else { $data = array( 'file'=>'@'.realpath($path).";type=".$type.";filename=".$filename ); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true ); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $return_data = curl_exec($ch); curl_close($ch); echo $return_data; } ``` # 参考 http://cn2.php.net/manual/zh/function.curl-getinfo.php https://www.cnblogs.com/52fhy/p/4971707.html https://www.cnblogs.com/manongxiaobing/p/4698990.html https://www.jianshu.com/p/63b32ceea742 https://my.oschina.net/forMemory/blog/374451

面向对象

[TOC] # 1. 面向对象典型例子 ``` //一个典型的类 class Person{ //注意这里用var定义,未确定用哪个关键词(protected,private,public)来修饰 var $name ; //构造函数, function __construct($name){ //通过构造函数传参来给类中的属性赋值 $this->name = $name; } //说话这个方法 function say(){ echo "我的名字是".$this->name; } //实例化类.实例化后人就有了自己的专有特征,下面三个实例是不同的 $people1 = new Person("王王","男",20); $people2 = new Person("李李","女",21); $people3 = new Person("张张","男",22); //每个具体的人都有自己的说法方式,能说出不同的内容 $people1->say() //我的名字是王王 $people2->say() //我的名字是李李 $people3->say() //我的名字是张张 ``` 总结: 1. 一个类,可以有自己的属性,自己的方法,我们可以在构造函数中实现很多方法 2. 实例化类是用new关键字,并且可以传入参数 3. 访问这个实例化后的具体对象的方法是: 对象->属性 =xxxx 4. 类和对象就像是,图纸和房子的关系,我们按照图纸施工就得到了一个有血有肉的真实的房子.这个过程就像是实例化,实例化后我们就可以在房子里做饭,洗衣服... # 2. $this关键字 1. $this在构造函数中指代该构造函数所创建的新对象,就是指当前对象 2. 在当前类中使用属性和方法, $this->属性 或者 $this->方法 3. 对象的方法内的局部变量,只在当前方法内有效,所以不使用$this关键字,直接使用 4. 局部变量和类的属性要认清,可以同名,但是要注意区分,尽量不同名,以免混淆 ``` class Person{ var $name = "李四" ; function say($name){ echo "我是类的属性中的name".$this->name; echo "我是say方法中传递过来的参数".$name; $this->dacnce(); //同样我们可以在say方法中调用person类中的dance方法; } function dance(){ echo "我会跳霹雳舞"; } } $people = new Person(); $people->say('张三'); //注意这里和上面例子中的区别,上面是在人实例化的时候就传入了初始属性,这里是给人的方法传入参数,注意理解 ``` # 3. $parent关键字 1. parent用来引用父类的方法 2. parent::的追溯不仅于直接父类 ``` ## 一.子类继承父类 ## 例子1 //父类 class employee{ protected $sal=3000; public function getSal(){ $this->sal = $this->sal + 1200; return $this->sal ; } } //子类对父类继承 class Manager extends employee { public function getSal(){ // 这里调用了父类的方法, parent::getSal(); //employee类中的return $this->sal 是为echo服务的 //下面的$this->sal中的sal是通过继承得到的,而不是return回来的 $this->sal = $this->sal + 1500; return $this->sal ; } } $emp = new employee(); echo "普通员工的工资是 " . $emp->getSal(); $manager = new Manager(); echo "经理的工资是: " . $manager->getSal(); //普通员工的工资是 4200 //经理的工资是: 5700 ## 二.子类对父类属性重写 ## 例子2.1 //父类的private属性不能被继承,如果父类有私有属性,那么父类的的方法只为父类的私有属性服务 //父类 private $sal =3000 //子类 protected $sal = 5000 class employee{ private $sal=3000; public function getSal(){ return $this->sal ; } } class Manager extends employee { protected $sal=5000; public function getParentSal(){ return parent::getSal(); } } $manager = new Manager(); //子类并且有getsal方法,这个方法是子类继承了父类的getsal方法,但是父类的私有属性$sal并没有被继承,这个被继承的父类getsal方法还是为父类的私有属性$sal服务,而不为子类的$sal服务 echo $manager->getSal(); echo $manager->getParentSal(); //输出结果是 3000 3000 ## 例子2.2 //子类将父类的属性重写的情况下 //父类 protected $sal =3000 //子类 protected $sal = 5000 class employee{ protected $sal=3000; public function getSal(){ return $this->sal ; } } class Manager extends employee { protected $sal=5000; public function getParentSal(){ return parent::getSal(); } } $manager = new Manager(); echo $manager->getSal(); echo $manager->getParentSal(); //输出结果是 5000 5000 ## 三.子类对父类方法重写 //子类重写的方法对当前private有效 class employee{ private $sal=3000; public function getSal(){ return $this->sal ; } } class Manager extends employee { private $sal=5000; //重写父类的方法 public function getSal(){ return $this->sal ; //这里返回的是子类的private属性 } public function getParentSal(){ //这里返回的是父类的priavte属性 return parent::getSal(); } } $manager = new Manager(); echo $manager->getSal(); echo $manager->getParentSal(); //输出结果是 5000 3000 ``` # 4. static关键字 1. static关键字用来修饰**属性和方法** 2. static关键字不经过实例化就能使用,不根据实例的不同而改变 3. 静态属性在内存中只有一份,为所有实例共用 4. 使用方法是 类名::静态属性 类名::静态方法名 或者 使用self::调用当前类中的其它静态方法 5. 静态方法不能调用非静态属性(也就是不能用$this关键字来调用非静态属性,更不能用self:非静态属性这种错误的方式调用) # 5. const修饰 1. const用来修饰常量,**使用方法和静态属性一样** 类名::常量名 2. 常量不能被修改,修改会报错 3. 常量要大写 # 6. final关键字 final关键字用于**类和方法**之前,final类不可以被继承,final方法不可以被覆盖(重写) # 7. 抽象方法和抽象类 1. abstract修饰**一个类和方法** 2. 抽象类不能被实例化,实例化会报错 3. 子类(非抽象类)继承父类(抽象类),这时候子类是可以被实例化的,不会报错 4. 抽象类继承抽象类的话,不需要重写其中的抽象方法,但是此时子类仍然不能被实例化 5. 抽象类继承抽象类目的是对抽象类进行扩展 1. 抽象方法只有声明,没有具体的实现方法 2. 抽象方法没有{},而是采用;结束 3. 抽象方法在子类中必须被重写,如果不重写就会报错,同时要注意参数的个数.如果父类中没有抽象方法,子类继承的时候不重写也不会报错 4. 如果有抽象方法,当前类必须被声明为抽象类(但是抽象类中可以有非抽象方法) ``` abstract class User{ protexted $sal = 0; abstract function getSal(); //注意这里没有{} } ``` # 8. 接口的定义和规范 1. 接口是一种特殊的抽象类,只包含抽象方法和静态常量,没有其他类型的内容 2. 接口的写法 : interface 接口名{} 3. 接口抽象方法只能用public修饰,默认也是public权限,即使是final和abstract也不行 4. 注意其他抽象方法是用abstract修饰的,这里不用,只需写法一直就可以了 5. 接口中的静态常量就是用const修饰的就是了 ``` interface User{ function getName(); //这是个抽象方法,最后是以 ; 结束的 //function getAge(){}; //注意这是个非抽象方法,这样会报错的 } ``` # 9. 接口的实现 1. 接口实现使用implements 2. 一个类可以实现多个接口(解决了php单继承问题) 3. 同抽象类的继承一样,实现接口要实现其中的抽象方法,否则会报错 ``` class Student{ protect $grade ; public function getGrade(){ return $this->grade; } } interface User{ function getName(); } interface Administrator{ function setBulletin($_bulletin); } //可以一次实现多个接口 class Admin implements User,Administrator{ function getName(){} function setBulletin($_bulletin){} } //可以实现继承并且实现多个接口 class Admin extend Student implements User,Administrator{ function getName(){} function setBulletin($_bulletin){} } ``` # 10. 多态 1. 多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息将可以产生不同的结果,这种现象称为多态性 2. 同一个操作作用于不同的类的实例,将产生不同的执行结果。也即不同类的对象收到相同的消息时,将得到不同的结果 # 11. 接口的作用 1. 继承"基类"跟继承"接口"都能实现某些相同的功能,但有些接口能够完成的功能是只用基类无法实现的 2. 接口用于描述一组类的公共方法/公共属性.它不实现任何的方法或属性,只是告诉继承它的类《至少》要实现哪些功能,继承它的类可以增加自己的方法. 3. 使用接口可以使继承它的类: 命名统一/规范,易于维护 4. 提供永远的接口。 当类增加时,现有接口方法能够满足继承类中的大多数方法,没必要重新给新类设计一组方法,也节省了代码,提高了开发效率. ``` # 下面是一个关于usb接口的例子 //首先定义一个usb接口 interface USB{ public function run(); } # 下面定义三个不同的类key,mouse,store,都实现了上面的接口 //键盘 class key implements USB{ public function run(){ $this -> init(); } public function init(){ echo "key running .."; } } //鼠标 class mouse implements USB{ public function run(){ $this -> init(); } public function init(){ echo "mouse running ..."; } } //移动硬盘 class store implements USB{ public function run(){ $this -> initialize(); } private function initialize(){ echo "store running .."; } } //以上三种设备都要遵循接口的run方法 # 下面实现多态 //1.首先我们可以 $key = new key(); $Key->run(); $mouse = new mouse(); $mouse->run(); //上调用不同的类的相同名方法,会输出不同的东东,也就是说每个类里面的同名方法完成的功能可以是完全不同的 //2.我们可以更进一步,用多态实现其调用 class computer{ public function useUSB($obj){ $obj -> run(); } } //实例化computer $computer = new computer(); //给这个实例传入不同的对象(对象是根据接口实现的) $computer -> useUSB(new mouse()); $computer -> useUSB(new store()); $computer -> useUSB(new key()); //3.再进一步,此时我们又有个声卡设备, //如果按照传统的方式,我们需要创建声卡类并实现接口,然后实例化声卡类,调用声卡方法,没增加一种设备,就要多一种调用方法,多了就不容易维护了 //现在我们如果有了上面的方法,只需要创建新的声卡类,然后调用的时候还是使用computer中的useUSB方法,就可以了,这样看起来更容易维护,更容易扩展 ``` # 12. 对象克隆 __clone()方法 1. 在项目中,使用两个或多个一样的对象,重新new关键对象,并且赋予相同的属性,是比较繁琐的 2. 使用__clone()方法,可以得到两个一模一样的对象,并且互相不干扰 3. __clone方法中有this和that指针 ``` class Person{ var $name; var $age; //构造函数, function __construct($name='',$age=''){ //通过构造函数传参来给类中的属性赋值 $this->name = $name; $this->age = $age; } function say(){ echo "我的名字是".$this->name; echo ",年龄是".$this->name; } function __clone(){ //注意这里的this和that指针,$this指向复本people2,$that指向原本people1 $this->name = "我是假的$that->name"; $this->age = 30; } } $people1 = new Person("张三",20); $people2 = clone $people1; $people1->say(); $people2->say(); //结果: //我的名字是张三,年龄是20 //我的名字是假的张三,年龄格式30 ``` # 13. call方法 1. 当我们调用的方法在对象内容不存在时候,会出错,并停止运行程序 2. 我们可以使用call方法来弹出一个提示说此方法不存在,然后程序可以继续执行 ``` class Test{ //调用不存在的方法时候自动调用次方法,第一个参数是方法名,第二个参数是数组参数 public function __call($methodName, $args){ //我们可以在这里写一些处理逻辑 print_r($args); echo "您传入的方法不存在"; } } $test = new Test(); $test->demo("one","two","three",) //因为demo方法并不存在,所以会自动调用上面的__call方法 ```

用swoole实现nginx日志解析

[TOC] # 1.原技术路线解析 在nging配置中将日志信息交给syslog处理,rsyslog配置中将数据传递给了514端口解析,然后将解析好的数据传入elasticsearch中。 nginx配置 ``` server { listen 80; listen [::]:80; server_name test.86dev.wrddns.com; # 以下两行将日志写入syslog access_log syslog:server=unix:/dev/log,facility=local5,tag=web_1,severity=info main; error_log syslog:server=unix:/dev/log,facility=local5,tag=web_1,severity=error warn; # ....其他配置 } ``` /etc/rsyslog.conf ``` # 配置文件,存放解析规则xxx.conf和ruleBase文件xx.rb $IncludeConfig /etc/rsyslog.d/*.conf # 配置将日志放到哪个端口解析 local5.* @10.3.19.86:514 ``` 在实际应用过程中有一些问题,不能和php上面的一些配置进行配合记录,解析规则不好配置,有些内容解析不好,所以探索使用新的技术路线。 # 2.新技术路线 尝试使用新技术路线,通过swoole起一个服务,然后监听9502端口,rsyslog将日志推向该端口,对日志进行解析后推入elasticsearch,此时可以获取到php端的一些配置。以下是大体思路 ## 2.1 选择swoole的server类型。 这里不再赘述swoole的安装,首要考虑的问题是原推向514的协议类型。 先查看端口的协议类型 ``` # root @ WENGINE in ~ [9:48:51] $ netstat -antup | grep 514 udp 0 0 0.0.0.0:514 0.0.0.0:* 23560/rsyslogd udp 0 0 :::514 :::* 23560/rsyslogd ``` 可以看到是udp协议,所以选用swoole的 upd服务 结合laravel的commands来编写服务端 ``` <?php namespace App\Console\Swoole; use Illuminate\Console\Command; use swoole_websocket_server; use swoole_server; use swoole_process; use swoole_sock_udp; use UAParser\Parser; use GeoIp2\Database\Reader; use Wrd\Framework\Models\SysConfig; use Elasticsearch\ClientBuilder; class SwooleServer extends Command { protected $signature = 'swoole-server start {cmd=start : can use start} {--daemon : set to run in daemonize mode} '; protected $description = 'swoole server control'; public $access_buffer = []; public function __construct() { parent::__construct(); } public function handle() { $command = $this->argument('cmd'); $option = $this->option('daemon'); switch ($command) { case 'start': $this->initWs($option); break; default: $this->info('请按照下面格式输入命令:php artisan swoole-server {start}'); break; } } public function initWs($daemonize = false) { if ($daemonize) { $this->info('Starting Websocket server in daemon mode...'); } else { $this->info('Starting Websocket server in interactive mode...'); } $server = new swoole_server('0.0.0.0', 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set([ 'daemonize' => $daemonize, 'log_file' => '/var/www/html/storage/logs/websocket.log', 'worker_num' => 1, 'task_worker_num' => 1, ]); $server->on('Packet', function($serv, $data, $clientInfo) { $serv->task($data); }); $server->on('Task', function ($serv, $task_id, $from_id, $data) { //通过正则表达式提取出需要的信息,不同的日志格式需要不同的正则,这里只写一种情况 $rule = '/\<\d*\>.*\d{2}\:\d{2}\:\d{2}\s[^\s]*\s[^\s]*\s(\w*\_\d*)\:\s\[Customize-format\]/'; preg_match($rule, $data, $matches); if (empty($matches)) { $this->writeLog($data); //记录下无法解析的日志,更正正则 return false; } $vhost = $matches[1]; $ip = $matches[2]; //...更多参数 $ua = $matches[12]; //解析UA,这里使用的解析库https://github.com/ua-parser/uap-php $parser = Parser::create(); $parser_ua = $parser->parse($ua); $browser = $parser_ua->ua->family; $os = $parser_ua->os->family; $device = $parser_ua->device->family; //解析IP,这里使用的解析库https://github.com/maxmind/GeoIP2-php $reader = new Reader(public_path().'/geoip2/GeoLite2-City.mmdb'); try{ $record = $reader->city($ip); $country = $record->country->isoCode; $continent = $record->continent->names['zh-CN']; $subdivisions = $record->mostSpecificSubdivision->names['zh-CN']; $city = $record->city->names['zh-CN']; $geoip = array( 'location' => array($record->location->longitude, $record->location->latitude) ); } catch (\Exception $e) { //如果ip没有被收录(项目有很多内网ip),则拿数据库中的提前配置项,进行解析 } $res = array( 'vhost' => $vhost, 'ip' => $ip, // ...其它项 'token' => $token, 'browser' => $browser, 'os' => $os, 'device' => $device, 'continent' => $continent, 'country' => $country, 'subdivisions' => $subdivisions, 'city' => $city, 'geoip' => $geoip, ); $this->access_buffer[] = $res; //每隔一段时间,写入到elasticsearch if (count($this->access_buffer) > 0 && time() - strtotime($this->access_buffer[0]['@timestamp']) > 10) { $insert_data = $this->access_buffer; $this->access_buffer = []; $this->insertElasticsearch('access', $insert_data); } //return 数据 给 Finish return "Task {$task_id}'s result"; }); $server->on('Finish', function ($serv,$task_id, $data) { echo "Task {$task_id} finish\n"; }); $server->start(); } public function insertElasticsearch($type='access', $data){ foreach($data as $item){ $params['body'][] = [ 'index' => [ '_index' => $type.'-'.date('Y.m.d', time()), '_type' => 'events', ] ]; $params['body'][] = $item; } extract(\Config::get('app.elastic', [ 'host' => '127.0.0.1', 'port' => '9200' ])); //往elasticsearch写数据,这里使用的库https://github.com/elastic/elasticsearch-php $helper = ClientBuilder::create() ->setHosts([$host.":".$port]) ->build(); if (!empty($params['body'])) { $response = $helper->bulk($params); //var_dump($response); } } public function writeLog($info){ $alert_message = array( 'error' => '此条信息未能命中日志格式,未写入elasticsearch', 'info' => $info ); \Log::alert($alert_message); } ``` ## 2.2 通过更改nginx的main日志格式简化正则表达式 nginx的配置中的 ``` log_format main [$proxy_add_x_forwarded_for]-[$remote_user]-[$time_local]-[$request]-[$status]-[$bytes_sent]-[$http_host]-[$http_referer]-[$http_user_agent]-[$cookie_wengine_ticket]-[archer-main]; ``` 加了特殊符号,并且最后给了一个标识,这样能提高命中准确度 ## 2.3 更改rsyslog的端口 ``` local5.* @10.3.19.86:9502 ``` ## 2.4 其它一些问题 可以通过nc来检测swoole的udp服务是否通 ``` yum install -y nc ``` ``` # root @ WENGINE in ~ [10:17:07] C:130 $ nc -u 127.0.0.1 9502 ceshi ``` 可以写supervisor的脚本来使swoole服务器一直启动 ``` [program:swooleserver] directory = /var/www/html command=php artisan swoole-server user=apache autostart=true startsecs=2 autorestart=true redirect_stderr=true stopsignal=INT stderr_logfile_maxbytes=1MB stderr_logfile_backups=10 stderr_capture_maxbytes=1MB stderr_events_enabled=false ```