c#结束本次循环,结束循环return、break、continue的用法_橙cplvfx-技术踩坑记-CSDN博客

mikel阅读(1039)

来源: c#结束本次循环,结束循环return、break、continue的用法_橙cplvfx-技术踩坑记-CSDN博客

break //是跳出当前循环,
continue //跳过本次循环,执行下一次循环
return //是终止执行当前“函数”或“方法”,并可返回一个值
break语句:
break语句会使运行的程序立刻退出包含在最内层的循环或者退出一个switch语句。由于它是用来退出循环或者switch语句,所以只有当它出现在这些语句时,这种形式的break语句才是合法的。

程序代码

continue语句:

continue语句和break语句相似。所不同的是,它不是退出一个循环,而是开始循环的一次新迭代。
continue语句只能用在while语句、do/while语句、for语句、或者for/in语句的循环体内,在其它地方使用都会引起错误!

程序代码

 

return语句:

return语句就是用于指定函数返回的值。return语句只能出现在函数体内,出现在代码中的其他任何地方都会造成语法错误!
当执行return语句时,即使函数主体中还有其他语句,函数执行也会停止! return;不返回什么,直接跳出正在执行的函数.不执行return后面的代码
————————————————
版权声明:本文为CSDN博主「cplvfx」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cplvfx/article/details/120151537

thinkphp3.2+workerman(GatewayWorker)+ Layim做即时通讯_菜鸟的博客-CSDN博客

mikel阅读(704)

来源: thinkphp3.2+workerman(GatewayWorker)+ Layim做即时通讯_菜鸟的博客-CSDN博客

此博客参考https://fly.layui.com/jie/12624/

一,程序实现的逻辑:

后端还是和以前一样,传值给前端。
前端与GatewayWorker建立连接获取特定的连接id
(这样前端就有用户id和连接id)

3.前端通过ajax方法发送用户id和链接id给后端,后端进行绑定

4.监控前端发送消息,发送人id,当触发该事件,传到后端,后端根据用户id值,发送给特定的人

5.监控前端接收消息,当有新消息过来就在页面执行相应的代码

 

 

二,环境的搭建

1,下载GatewayWorker

(地址:http://www.workerman.net/download/GatewayWorker-for-win.zip)

2,修改Applications/YourApp/目录下的start_geteway.php 24行左右 修改为$gateway = new Gateway(“websocket://0.0.0.0:8282″);(端口号和前端保持一致)

3,下载workerman的GatewayClient(https://github.com/walkor/GatewayClient);

4,将下载好的Gateway重命名为Gateway.class.php并修改命名空间为namespace Org\Util;

 

将前面重命名Gateway.class.php复制到thinkphp/library/org/util/目录下
双击启动GatewayWorker根目录下的start_for_win.bat()
如果出现下面:

 

说明启动成功

如果没有启动成功将有以下解决方法

将php设置为全局环境变量(网上有很多方法)测试是否成功在命令行执行php –v,如果提示“php不是内部或外部命令“之类的就说明没有设置好
如果有下图之类的提示

上图那样有三个问题

php_mcrypt.dll – 找不到指定的模块。
php_pdo.dll – 找不到指定的模块。
proc_open() has been disabled for security reasons.
找不到指定模块:解决方法打开php.ini文件,查找模块名(比如上图php_pdo.dll);找到在这句前面加上“;”。注释掉即可

has been disabled for security reasons.解决方法:就是把这个方法的禁止打开,也是在php.ini文件修改;找到这个方法,直接删掉即可

 

三,代码部分

后端部分
因为该篇主要讲即时通讯的实现,所以用户信息获取那部逻辑就不做展示。
后端绑定和发送信息

后端接收前端数据的时候,还做了把数据存入数据库的处理,这样就可以实现数据的持久化

 

前端部分

引入文件
<script src=”__PUBLIC__/JQuery/2.0.0/JQuery.js”></script>

<link rel=”stylesheet” href=”__PUBLIC__/layim/css/layui.mobile.css”>

<script src=”__PUBLIC__/layim/layui.js”></script>
自己信息的初始化
layui.use(‘mobile’, function(layim){

var mobile = layui.mobile

,layim = mobile.layim;

layim.config({

init: {

mine: {

“username”: “{$user.name}” //我的昵称

,”id”: “user{$user.id}” //我的ID

,type: ‘kh’//类型

,”avatar”: “{$user.img}” //我的头像

}

} , chatTitleColor:”#fff”,

chatLog: ‘/chat/log/’

});

(3)创建客服信息

layim.chat({

id: “kf{$kf.id}”

,name: ‘客服{$kf.id}’

,type: ‘kefu’ //friend、group等字符,如果是group,则创建的是群聊

,avatar: ‘/luntan/{$kf.img}’

});

(4)建立连接

var socket = new WebSocket(‘ws://localhost:8282’);//服务器改成ip地址加端口号

连接方法

第一次连接,会收到两次信息,接收后端传来数据也是在这里处理

 

我们可以在onmessage方法里面进行处理,取出连接值,再传给后端,这样就绑定好了

除了是登录事件,登出事件,就是后端正常我们自己发送的数据了

我们把接收到的值,再进行展示,这样就可以实现我们需要的效果了。

//连接成功时触发
socket.onopen = function(e){

//这里可以写一些提示语

}

//监听收到的消息
socket.onmessage = function(e){
var data = e.data

// console.log(e);

data=data.replace(‘\r\n’,”);//删除换行符

var arr=data.split(” “);

if(arr[0]==”Hello”){

var client_id=arr[1].trim();

//绑定client_id

$.ajax({type: “POST”, url: “{:U(‘Workerman/bind’)}”, data: {

‘uid’:”user{$user[‘id’]}”,

‘client_id’: client_id}, dataType: ‘json’, success: function (res) {

console.log(res);

}});

}else if(arr[1]!=”login”&&arr[1]!=”logout”){

//接收服务器信息

data=JSON.parse(data);

if(data[‘sendid’]==”kf{$kf.id}”){

$(“#link”).html(“可点击进行评价”);

//console.log(data[‘sendname’]);

var obj = {};

obj = {

username:data[‘sendname’]

,avatar: data[‘img’]

,id: ‘kf1’

,type:”kefu”

,content: data[‘content’]

}

layim.getMessage(obj);

}

}

(5)监听用户发送信息

layim.on(‘sendMessage’, function(data){

console.log(data);

var sendid=data.mine.id;

var sendtype=”kh”;

var sendname=data.mine.username;

var sendimg=data.mine.avatar;

var contents=data.mine.content;

var toid=data.to.id;

var toname=data.to.name;

var totype=data.to.type;

var toimg=data.to.avatar;

var talkid=data.mine.id+”|”+data.to.id;

var talktip=data.mine.username+”|”+data.to.name;

$.ajax({

type: “POST”,

url: “{:U(‘User/getcontents’)}”,

data: {

‘sendid’:sendid,

‘sendname’: sendname,

‘sendtype’:sendtype,

‘sendimg’:sendimg,

‘contents’:contents,

‘toid’:toid,

‘toname’:toname,

‘totype’:totype,

‘toimg’:toimg,

‘talkid’:talkid,

‘talktip’:talktip,

},

dataType: ‘json’,

success: function (res) {

console.log(res);

}

})
至此即时通讯的修改就出来

 

 

四,数据的持久化,跨设备储存

把聊天记录根据自己的需求存入数据库

单用户进入聊天界面时,根据是谁与谁的聊天,读取相应数据,然后前端得到数据再进行展示。

注意layim聊天记录的要求时间戳是精确到毫秒的

(1),php获取当前毫秒的方法

public function getMillisecond() {

$time = explode (” “, microtime () );

$time = $time [1] . ($time [0] * 1000);

$time2 = explode ( “.”, $time );

$time = $time2 [0];

return $time;

}
(2),前端初始化数据

localStorage.clear();
(3),前端获取数据,并展示

$.ajax({type: “POST”, url: “{:U(‘User/getmsg’)}”, data: {

‘uid’:”user{$user[‘id’]}”,’kid’:”kf{$kf.id}”},

dataType: ‘json’, success: function (res) {

if(res.stu==1){

$(“#link”).html(“获取历史记录成功”);

var msg=res.msg;

// console.log(msg)

for ($i=0;$i<msg.length;$i++){

var isme=false;

if(“user{$user[‘id’]}”==msg[$i].sendid){

isme=true;

}

var obj = {};

obj = {

username:msg[$i].sendname//消息来源用户名

,avatar: msg[$i].sendimg //消息来源用户头像

,id: msg[$i].sendid//消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id)

,type:msg[$i].totype //聊天窗口来源类型,从发送消息传递的to里面获取

,content: msg[$i].contents //消息内容

,mine: isme

,timestamp: parseInt(msg[$i].time) //服务端时间戳毫秒数。注意:如果你返回的是标准的 unix 时间戳,记得要 *1000

}

layim.getMessage(obj);

}

}

}})
这样数据的持久化算是实现了

不过这里还有一个小bug,当执行layim.getMessage()如果是对方数据的时候就有声音提示(也就是上面isme变量为false的时候),所以一打开这个页面就有声音提示,这个是默认打开的,因此我们要把他关闭了

在layim.config里面最后加上“,voice: false“

然后我们自己写收到实时信息的时候发送提示音

Js发送语音提示方法

function playSound() {

var borswer = window.navigator.userAgent.toLowerCase();

if ( borswer.indexOf( “ie” ) >= 0 )

{

//IE内核浏览器

var strEmbed = ‘<embed name=”embedPlay” src=”/Public/layim/css/modules/layim/voice/default.wav” autostart=”true” hidden=”true” loop=”false”></embed>’;

if ( $( “body” ).find( “embed” ).length <= 0 )

$( “body” ).append( strEmbed );

var embed = document.embedPlay;

embed.volume = 100;

} else

{

//非IE内核浏览器

var strAudio = “<audio id=’audioPlay’ src=’/Public/layim/css/modules/layim/voice/default.wav’ hidden=’true’>”;

if ( $( “body” ).find( “audio” ).length <= 0 )

$( “body” ).append( strAudio );

var audio = document.getElementById( “audioPlay” );

//浏览器支持 audion

audio.play();

}
然后再自己需要提示音的时候调用这个方法即可

 

效果图

 

 

 

 

 

最后提示:

如果在服务器搭建的时候记得打开你程序设定的端口的防火墙
命令行窗口不能关闭
以上都是在windows系统搭建的 ,linux搭建这种websocket环境,网上也有很方法
Layim如何修改默认展示的聊天记录的条数
因为layim默认展示20条聊天记录,多的话,以前的记录会被折叠

可以修改lay\modules\mobile.js文件 查找“c=20”后面的条数修改为你需要展示的条数,然后清除浏览器缓存即可(此方法适用于引入moblie的手机版js,pc端可去试试其他文件)

 

 

如若有误或者有其他问题请与我交流:2359582968(微信qq同号)
————————————————
版权声明:本文为CSDN博主「一个菜鸡路上不肯回头的人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38211838/article/details/82020708

thinkphp3.2引入第三方类方法_kobel的博客-CSDN博客

mikel阅读(857)

来源: thinkphp3.2引入第三方类方法_kobel的博客-CSDN博客

订阅专栏
1、在第三方类中,无需写namespace

2、在引用的方法中,用vendor(文件夹.类名),例如:vendor(‘yg.Yg’);

3、在使用时,需要实例化,在类里面new的时候要多加一个\,例如;$mail = new \ yg();

4、在引用类的方法时,例如:$mail::方法名称()。
————————————————
版权声明:本文为CSDN博主「kobel28」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luyongtao28/article/details/83013683

thinkphp 3.2公共类库、应用类库ThinkPHP/Library讲解_weixin_34293246的博客-CSDN博客

mikel阅读(749)

来源: thinkphp 3.2公共类库、应用类库ThinkPHP/Library讲解_weixin_34293246的博客-CSDN博客

 thinkphp 3.2公共类库、应用类库ThinkPHP/Library讲解

ThinkPHP的类库主要包括公共类库和应用类库,都是基于命名空间进行定义和扩展的。只要按照规范定义,都可以实现自动加载。

        公共类库

公共类库通常是指ThinkPHP/Library目录下面的类库,例如:

Think目录:系统核心类库
Org目录:第三方公共类库

这些目录下面的类库都可以自动加载,你只要把相应的类库放入目录中,然后添加或者修改命名空间定义。 你可以在Org/Util/目录下面添加一个Image.class.php 文件,然后添加命名空间如下:

namespace Org\Util;
class Image {
}

这样,就可以用下面的方式直接实例化Image类了:

$image = new \Org\Util\Image;

        除了这些目录之外,你完全可以在ThinkPHP/Library目录下面添加自己的类库目录,例如,我们添加一个Com目录用于企业类库扩展:

Com\Sina\App类(位于Com/Sina/App.class.php )

namespace Com\Sina;
class App {
}

Com\Sina\Rank类(位于Com/Sina/Rank.class.php)

namespace Com\Sina;
class Rank {
}

公共类库除了在系统的Library目录之外,还可以自定义其他的命名空间,我们只需要注册一个新的命名空间,在应用或者模块配置文件中添加下面的设置参数:

‘AUTOLOAD_NAMESPACE’ => array(
‘Lib’ => APP_PATH.’Lib’,
)

我们在应用目录下面创建了一个Lib目录用于放置公共的Lib扩展,如果我们要把上面两个类库放到Lib\Sina目录下面,只需要调整为:

Lib\Sina\App类(位于Lib/Sina/App.class.php )

namespace Lib\Sina;
class App {
}

Lib\Sina\Rank类(位于Lib/Sina/Rank.class.php)

namespace Lib\Sina;
class Rank {
}

如果你的类库没有采用命名空间的话,需要使用import方法先加载类库文件,然后再进行实例化,例如: 我们定义了一个Counter类(位于Com/Sina/Util/Counter.class.php):

class Counter {
}

在使用的时候,需要按下面方式调用:

import(‘Com.Sina.Util.Couter’);
$object = new \Counter();

        应用类库

应用类库通常是在应用或者模块目录下面的类库,应用类库的命名空间一般就是模块的名称为根命名空间,例如: Home\Model\UserModel类(位于Application\Home\Model)

namespace Home\Model;
use Think\Model;
class UserModel extends Model{
}

Common\Util\Pay类(位于Application\Common\Util)

namespace Common\Util;
class Pay {
}

Admin\Api\UserApi类(位于Application\Admin\Api)

namespace Admin\Api;
use Think\Model;
class UserApi extends Model{
}

记住一个原则,命名空间的路径和实际的文件路径对应的话 就可以实现直接实例化的时候自动加载。

转载于:https://my.oschina.net/u/2247058/blog/358065

tp3.2/thinkphp3.2引入外部类文件/.php文件总结_尼古拉斯鹏-CSDN博客

mikel阅读(728)

来源: tp3.2/thinkphp3.2引入外部类文件/.php文件总结_尼古拉斯鹏-CSDN博客

一、引入第三方类库 将文件放在Org/Util下面 比如:.class.php文件

1.可以将文件放在Org/Util下面如test.class.php (也就是说以Think、Org为根命名空间的类都可以自动加载:)

ThinkPHP/Library/Org/Util/test.class.php。

2.给类库加命名空间如下

namespaceOrg\Util;

3.在控制器中实例化这个类的方式如下:

new\Org\Util\Auth();

二、引入类库放在项目模块中然后引入的方法

1.要给类名以.php后缀的改为以.class.php后缀的 如上图

2. a.如果被引入的类文件没有命名空间 如下:

$c = new \AopClient;

b.如果有命名空间

可以 use Wechat\ORG\AopClient; 然后$c = new \AopClient;实例化

也可以加个根命名空间 $c = new \AopClient;实例化

三、手动加载第三方普通.php后缀的原生文件

如果你的第三方类库都放在Vendor目录下面,并且都以.php为类文件后缀,也没用采用命名空间的话,

那么可以使用系统内置的Vendor函数简化导入。

例如,我们把 Zend \ Filter\Dir.php 放到 Vendor 目录下面,这个时候 Dir 文件的路径就是 Vendor\Zend\Filter\Dir.php,我们使用vendor 方法导入只需要使用:

实例化:

Vendor(‘Zend.Filter.Dir’);

$obj = new \Dir();

注意:如果你的文件是a.b.php(b不是class)的话,也可以这样导入:

Vendor(‘目录.a#b’);

实例化对象时候,以class ab {…}示例:new \ab();

四、手动加载.php后缀且面向过程的文件

文件内容是面向过程的,就是文件里面没有class aaa{} ,不用实例化操作,直接使用。

我们可以使用原生的语法:

在控制器的方法里面使用:

include_once ‘./ThinkPHP/Library/Vendor/lib/aaa.bbb.php’;
————————————————
版权声明:本文为CSDN博主「小鹏程序」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35979073/article/details/79361600

Swoole整合ThinkPHP3.2系列教程六_一个不靠谱的程序员-CSDN博客

mikel阅读(898)

来源: Swoole整合ThinkPHP3.2系列教程六_一个不靠谱的程序员-CSDN博客

终结篇
哈哈,我就说这一系列是完整的吧。

我们并没有用swoole框架重构系统代码,只是当成一个长连接扩展库来使用的。

swoole很强大,我们只是用了swoole很小很小的一部分。如果用swoole做更多精细化的处理,很麻烦。

毕竟我这种面向工资编程的开发者得服从leader的安排(尽快上线)。

如果有同学在使用过程中出现问题了欢迎留言讨论。

我踩过的坑
swoole_client只能在cli模式下运行,不要指望在浏览器里调用。

最早想要搭建一个TCP连接,然后用浏览器也访问这个server,虽然无法在页面输出,但是在swoole的onReceive回调里依然是可以接收到值的,想利用这个特性做浏览器访问。如果你也有这个思路,可以停止了。

swoole_client是没有办法连接websocket服务端的,必须使用swoole_http_client,设置项增加’websocket_mask’ => true

开发过程中临时补了一些TCP/IP协议的东西,望着这些东西,想起上学时浪费时间打的LOL,留下了悔恨的眼泪。

使用本教程里的案例 php swoole.php start启动时,请一定要确保在onWorkerStart回调了加载TP框架东西的时候,确保要在指定的模块里含有StartController并且里面有index方法,不然你就会看到惊喜的一幕。TP框架抛出错误exit,swoole重新拉起worker进程,TP框架抛出错误exit,swoole重新拉起worker进程,这种死循环简直过瘾。log里记录的错误信息是[zm_deactivate_swoole: worker process is terminated by exit()/die().]

当在线上部署时,将swoole设置为守护进程运行,此时记得把所有路径定义的部分 比如require(TP框架)的部分都换成绝对路径 dirname(__DIR__)这种的。。
————————————————
版权声明:本文为CSDN博主「一个不靠谱的程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013705066/article/details/77680328

Swoole整合ThinkPHP3.2系列教程五_一个不靠谱的程序员-CSDN博客

mikel阅读(956)

来源: Swoole整合ThinkPHP3.2系列教程五_一个不靠谱的程序员-CSDN博客

如何开启SSL
对于已经升级成了https协议的网站,我们需要使用websocket连接swoole的话,无法正常连接上swoole服务。因为https认为这是不安全的连接,所以我们必须为swoole开启SSL

1.检查当前的swoole扩展是否开启了openssl:
php –ri swoole
如果看到openssl=>enabled,则表示当前安装的swoole扩展已经开启了SSL。否则请重新编译,携带编译参数–enable-openssl

下载源代码包后,在终端进入源码目录,执行下面的命令进行编译和安装 (和重新编译安装步骤一样)
cd swoole
phpize
./configure –enable-openssl (如果这一步提示让你设置php-config的路径,在这加上就行了)
make
sudo make install

如果是之前安装过swoole,这里就不用配置php.ini文件里的extension=swoole.so,否则需要自己配置一下。
安装完成以后重新执行上述命令检测是否启用SSL

2.修改swoole.php里文件,定义crt证书路径和key密匙路径。

//笔者测试环境里是把这两个文件都放到了Swoole目录下,根据自定义设置修改
define(‘SSL_CRT’, SWOOLE_PATH.’/server.crt’);
define(‘SSL_KEY’, SWOOLE_PATH.’/server.key’);

3.修改Server.php里配置项$options数组,增加以下参数:

‘ssl_cert_file’ => SSL_CRT,
‘ssl_key_file’ => SSL_KEY,

4.修改Server.php里init()方法里的创建server时增加额外的参数

$this->swoole = new swoole_websocket_server($this->host, $this->port , SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);

5.将websocket客户端里的连接地址ws改成wss(注意这里只能走域名,走IP的话无法连接,因为SSL证书是绑定在域名上的)

6.重启swoole服务,打开浏览器测试一下吧。
————————————————
版权声明:本文为CSDN博主「一个不靠谱的程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013705066/article/details/77774379

Swoole:PHP 协程框架 - DianThink点想网络

mikel阅读(941)

来源: Swoole:PHP 协程框架 – DianThink点想网络

Swoole 是一个 PHP 的 协程 高性能 网络通信引擎,使用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块。可以方便快速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等,使 PHP 不再局限于传统的 Web 领域。

Swoole 使 PHP 开发人员可以编写高性能高并发的 TCP、UDP、Unix Socket、HTTP、 WebSocket 等服务,让 PHP 不再局限于 Web 领域。

Swoole4 协程的成熟将 PHP 带入了前所未有的时期, 为性能的提升提供了独一无二的可能性。

Swoole 可以广泛应用于互联网、移动通信、云计算、 网络游戏、物联网(IOT)、车联网、智能家居等领域。

使用 PHP + Swoole 可以使企业 IT 研发团队的效率大大提升,更加专注于开发创新产品。

HTTP Server

//高性能HTTP服务器
$http = new Swoole\Http\Server("127.0.0.1", 9501);

$http->on("start", function ($server) {
    echo "Swoole http server is started at http://127.0.0.1:9501\n";
});

$http->on("request", function ($request, $response) {
    $response->header("Content-Type", "text/plain");
    $response->end("Hello World\n");
});

$http->start();

WebSocket Server

$server = new Swoole\Websocket\Server("127.0.0.1", 9502);

$server->on('open', function($server, $req) {
    echo "connection open: {$req->fd}\n";
});

$server->on('message', function($server, $frame) {
    echo "received message: {$frame->data}\n";
    $server->push($frame->fd, json_encode(["hello", "world"]));
});

$server->on('close', function($server, $fd) {
    echo "connection close: {$fd}\n";
});

$server->start();

TCP Server

$server = new Swoole\Server("127.0.0.1", 9503);
$server->on('connect', function ($server, $fd){
    echo "connection open: {$fd}\n";
});
$server->on('receive', function ($server, $fd, $reactor_id, $data) {
    $server->send($fd, "Swoole: {$data}");
    $server->close($fd);
});
$server->on('close', function ($server, $fd) {
    echo "connection close: {$fd}\n";
});
$server->start();

UDP Server

$serv = new Swoole\Server("127.0.0.1", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);

//监听数据接收事件
$serv->on('Packet', function ($serv, $data, $clientInfo) {
    $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);
    var_dump($clientInfo);
});

//启动服务器
$serv->start();

Task

$server = new Swoole\Server("127.0.0.1", 9502);
$server->set(array('task_worker_num' => 4));
$server->on('receive', function($server, $fd, $reactor_id, $data) {
    $task_id = $server->task("Async");
    echo "Dispatch AsyncTask: [id=$task_id]\n";
});
$server->on('task', function ($server, $task_id, $reactor_id, $data) {
    echo "New AsyncTask[id=$task_id]\n";
    $server->finish("$data -> OK");
});
$server->on('finish', function ($server, $task_id, $data) {
    echo "AsyncTask[$task_id] finished: {$data}\n";
});
$server->start();

Coroutine

//睡眠 1 万次,读取,写入,检查和删除文件 1 万次,使用 PDO 和 MySQLi 与数据库通信 1 万次,创建 TCP 服务器和多个客户端相互通信 1 万次,
//创建 UDP 服务器和多个客户端到相互通信 1 万次...... 一切都在一个进程一秒内完美完成!

Swoole\Runtime::enableCoroutine();//此行代码后,文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,见文档'一键协程化'章节
$s = microtime(true);
//Co/run()见文档'协程容器'章节
Co\run(function() {
// i just want to sleep...
for ($c = 100; $c--;) {
    go(function () {
        for ($n = 100; $n--;) {
            usleep(1000);
        }
    });
}

// 10k file read and write
for ($c = 100; $c--;) {
    go(function () use ($c) {
        $tmp_filename = "/tmp/test-{$c}.php";
        for ($n = 100; $n--;) {
            $self = file_get_contents(__FILE__);
            file_put_contents($tmp_filename, $self);
            assert(file_get_contents($tmp_filename) === $self);
        }
        unlink($tmp_filename);
    });
}

// 10k pdo and mysqli read
for ($c = 50; $c--;) {
    go(function () {
        $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root');
        $statement = $pdo->prepare('SELECT * FROM `user`');
        for ($n = 100; $n--;) {
            $statement->execute();
            assert(count($statement->fetchAll()) > 0);
        }
    });
}
for ($c = 50; $c--;) {
    go(function () {
        $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');
        $statement = $mysqli->prepare('SELECT `id` FROM `user`');
        for ($n = 100; $n--;) {
            $statement->bind_result($id);
            $statement->execute();
            $statement->fetch();
            assert($id > 0);
        }
    });
}

// php_stream tcp server & client with 12.8k requests in single process
function tcp_pack(string $data): string
{
    return pack('n', strlen($data)) . $data;
}

function tcp_length(string $head): int
{
    return unpack('n', $head)[1];
}

go(function () {
    $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);
    $socket = stream_socket_server(
        'tcp://0.0.0.0:9502',
        $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx
    );
    if (!$socket) {
        echo "$errstr ($errno)\n";
    } else {
        $i = 0;
        while ($conn = stream_socket_accept($socket, 1)) {
            stream_set_timeout($conn, 5);
            for ($n = 100; $n--;) {
                $data = fread($conn, tcp_length(fread($conn, 2)));
                assert($data === "Hello Swoole Server #{$n}!");
                fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!"));
            }
            if (++$i === 128) {
                fclose($socket);
                break;
            }
        }
    }
});
for ($c = 128; $c--;) {
    go(function () {
        $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1);
        if (!$fp) {
            echo "$errstr ($errno)\n";
        } else {
            stream_set_timeout($fp, 5);
            for ($n = 100; $n--;) {
                fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!"));
                $data = fread($fp, tcp_length(fread($fp, 2)));
                assert($data === "Hello Swoole Client #{$n}!");
            }
            fclose($fp);
        }
    });
}

// udp server & client with 12.8k requests in single process
go(function () {
    $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);
    $socket->bind('127.0.0.1', 9503);
    $client_map = [];
    for ($c = 128; $c--;) {
        for ($n = 0; $n < 100; $n++) {
            $recv = $socket->recvfrom($peer);
            $client_uid = "{$peer['address']}:{$peer['port']}";
            $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1;
            assert($recv === "Client: Hello #{$id}!");
            $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!");
        }
    }
    $socket->close();
});
for ($c = 128; $c--;) {
    go(function () {
        $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1);
        if (!$fp) {
            echo "$errstr ($errno)\n";
        } else {
            for ($n = 0; $n < 100; $n++) {
                fwrite($fp, "Client: Hello #{$n}!");
                $recv = fread($fp, 1024);
                list($address, $port) = explode(':', (stream_socket_get_name($fp, true)));
                assert($address === '127.0.0.1' && (int)$port === 9503);
                assert($recv === "Server: Hello #{$n}!");
            }
            fclose($fp);
        }
    });
}
});
echo 'use ' . (microtime(true) - $s) . ' s';

Channel

Co\run(function(){
        //使用Channel进行协程间通讯
        $chan = new Swoole\Coroutine\Channel(1);
        Swoole\Coroutine::create(function () use ($chan) {
            for($i = 0; $i < 100000; $i++) {
                co::sleep(1.0);
                $chan->push(['rand' => rand(1000, 9999), 'index' => $i]);
                echo "$i\n";
            }
        });
        Swoole\Coroutine::create(function () use ($chan) {
            while(1) {
                $data = $chan->pop();
                var_dump($data);
            }
        });
  });

开源、高性能、高生产力

如果您有基于Swoole的优秀项目并想展现在下处,欢迎扫描侧边二维码来联系我们

Swoole源码      Swoole Plus        Swoole Tracker        Swoole Compiler

客服系统

即信在线客服系统是一套使用 Laravel+Swoole 编写的在线客服系统,支持 PC Web 和移动端 H5 网页,支持完善的权限管理和后台报表,聊天功能十分强大,支持语音聊天,微信留言等,并可以无缝接入微信公众号, 网页端只需嵌入一段 js 即可快速接入开源客服系统,可以无限添加用户,不限制坐席数 ,授权后可私有化部署,无数据安全问题, 并提供客服系统源码

聊天系统(JIM)

JIM 是一套使用 Swoole 编写的轻量级 IM 系统,界面简洁清爽,支持换肤,让在线办公更便捷,用 JIM 手机电脑上的文件都能收发自如,轻松完成你的工作和娱乐,支持丰富的表情包,视频聊天,语音聊天,群聊,单聊,消息漫游,输入检测等等常用的 IM 功能,授权后可私有化部署并二次开发,无数据安全问题,并提供一年的技术支持

物联网(IOT)解决方案

MQTT 是一个客户端服务端架构的发布 / 订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。可以使用 Swoole 作为 MQTT 服务端或客户端,实现一套完整物联网(IOT)解决方案

新冠疫情抓取

基于 Swoole + imi 框架的新型冠状病毒肺炎疫情实时动态爬虫抓取项目,采集数据来源为丁香园。演示地址:https://test.yurunsoft.com/ncov/

PHP微服务(Micro Service)

Hyperf 是基于 Swoole 4.4+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是可替换与可复用的

斗地主

基于 Swoole + Hyperf 框架开发 demo 级的斗地主游戏,实现斗地主游戏服务端逻辑,并采用原生 js 和 WebSocket 实现简单的客户端打牌逻辑,可以做到简单的玩斗地主游戏

魔兽世界

魔兽模拟游戏服务器,项目采用 PHP 开发,TCP 长连接基于 Swoole,支持鉴权,角色的创建,地图的加载,NPC 和生物的构建及各种眼花缭乱的物品和技能等等

HyperfCMS

HyperfCMS 是基于 Swoole+Hyperf 框架前后端分离架构的一套开源且完美的建站系统,拥有简单大气设计、友好的交互体验、清晰明了的代码规范。组件化的封装应用,编写复杂的管理应用,效率是质的提升、时间成倍缩短,人员可以减半,事半功倍。可以提供定制化服务!

实时视频语音 (Webrtc)

基于 Swoole4 高性能协程的 demo 级实时视频和语音通话方案,采用 webrtc 协议,并已经做好 p2p 打洞,中继服务器,演示地址:https://webrtc.dingjw.com/room.php?cid=2

MySQL Proxy(MySQL中间件)

一个基于 MySQL 协议,Swoole 开发的 MySQL 数据库连接池,支持读写分离支持数据库连接池,能够有效解决 PHP 带来的数据库连接瓶颈,支持 SQL92 标准,采用协程调度,遵守 MySQL 原生协议,跨语言,跨平台的通用中间件代理,支持 MySQL 事务,完美兼容 MySQL5.5 – 8.0

分布式定时任务 (Swoole Crontab)

基于 Swoole 的定时器程序,支持秒级处理,完全兼容 crontab 语法,且支持秒的配置,可使用数组规定好精确操作时间,Web 界面管理,增删改查任务,完整的权限控制

ThinkCMF

ThinkCMF 是一款基于 ThinkPHP+Mysql 开发的 CMS,完美支持 Swoole,框架自身提供基础的管理功能,而开发者可以根据自身的需求以应用的形式进行扩展。每个应用都能独立的完成自己的任务,也可通过系统调用其他应用进行协同工作。在这种运行机制下,开发商场应用的用户无需关心开发 SNS 应用时如何工作的,但他们之间又可通过系统本身进行协调,大大的降低了开发成本和沟通成本

Markdown文档系统

软擎文档系统是基于 Swoole + Rangine 框架开发的开源版 MarkDown 文档管理系统,不同于 docsify.js 等前端文档系统,本文档系统是偏后端的文档系统,对于广大PHPer来说更加友好。支持多用户协同操作,管理员审核发布等功能。 让您的工作更高效,更智慧。

Ain’t Queue 异步队列

Ain’t Queue 借助 Swoole 提供的便捷多进程 API 和 CSP 编程能力实现了主进程+监控进程+多工作进程的进程模型,并且提供了各类事件自定义注册的能力(队列监控、快照记录、任务中间件等)。默认使用 Redis 驱动,全原子操作,可延时可重试,自带漂亮的仪表盘,稳定可靠,已经在公司生产环境使用。

PaySDK

PHP 集成支付 SDK ,集成了支付宝、微信支付的支付接口和其它相关接口的操作。支持 php-fpm 和 Swoole,所有框架通用。

ZooKeeper

基于 Swoole 协程的PHP ZooKeeper客户端

LaravelS

LaravelS 是 Swoole 和 Laravel/Lumen 之间开箱即用的适配器,内置 HTTP/WebSocket Server,支持 TCP/UDP Server、自定义进程、异步的事件监听、异步任务、毫秒级定时任务、平滑Reload等特性,让 Laravel 如虎添翼。

xlswriter

xlswriter是一个 PHP C 扩展,支持 Swoole 协程环境,可用于在 Excel 2007+ XLSX 文件中读取数据,插入多个工作表,写入文本、数字、公式、日期、图表、图片和超链接。

Swoole 特性

Swoole 使用 C/C++ 语言编写,提供了 PHP 语言的异步多线程服务器、异步 TCP/UDP 网络客户端、异步 MySQL、异步 Redis、数据库连接池、AsyncTask、消息队列、毫秒定时器、异步文件读写、异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
除了异步 IO 的支持之外,Swoole 为 PHP 多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大 简化多进程并发编程的工作。其中包括了并发原子计数器、并发 HashTable、Channel、Lock、进程间通信IPC 等丰富的功能特性。
Swoole4.0 支持了类似 Go 语言的协程,可以使用完全同步的代码实现异步程序。PHP 代码无需额外增加任何 关键词,底层自动进行协程调度,实现异步IO。

Swoole整合ThinkPHP3.2系列教程四_一个不靠谱的程序员-CSDN博客

mikel阅读(905)

来源: Swoole整合ThinkPHP3.2系列教程四_一个不靠谱的程序员-CSDN博客

在cli环境下,PHP程序需要长时间运行,客户端与MySQL服务器之间的TCP连接是不稳定的。

MySQL-Server会在一定时间内自动切断连接
PHP程序遇到空闲期时长时间没有MySQL查询,MySQL-Server也会切断连接回收资源
其他情况,在MySQL服务器中执行kill process杀掉某个连接,MySQL服务器重启
所以我们要考虑数据库断线重连的问题,但是ThinkPHP3.2里DB驱动类里并没有断线重连的例子,好消息是TP5里有,于是我照着TP5里的源码,改了TP3.2里的DB驱动类,来达到数据库断线自动重连。

附上代码
完整的DB.class.php文件 保证可用。

<?php
// +———————————————————————-
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +———————————————————————-
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +———————————————————————-
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +———————————————————————-
// | Author: liu21st <liu21st@gmail.com>
// +———————————————————————-
// 2017-8-24 09:26:23 增加了断线重连功能

namespace Think\Db;
use Think\Config;
use Think\Debug;
use Think\Log;
use PDO;

abstract class Driver {
// PDO操作实例
protected $PDOStatement = null;
// 当前操作所属的模型名
protected $model = ‘_think_’;
// 当前SQL指令
protected $queryStr = ”;
protected $modelSql = array();
// 最后插入ID
protected $lastInsID = null;
// 返回或者影响记录数
protected $numRows = 0;
// 事务指令数
protected $transTimes = 0;
// 错误信息
protected $error = ”;
// 数据库连接ID 支持多个连接
protected $linkID = array();
// 当前连接ID
protected $_linkID = null;
// 数据库连接参数配置
protected $config = array(
‘type’ => ”, // 数据库类型
‘hostname’ => ‘127.0.0.1’, // 服务器地址
‘database’ => ”, // 数据库名
‘username’ => ”, // 用户名
‘password’ => ”, // 密码
‘hostport’ => ”, // 端口
‘dsn’ => ”, //
‘params’ => array(), // 数据库连接参数
‘charset’ => ‘utf8’, // 数据库编码默认采用utf8
‘prefix’ => ”, // 数据库表前缀
Debug’ => false, // 数据库调试模式
‘deploy’ => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
‘rw_separate’ => false, // 数据库读写是否分离 主从式有效
‘master_num’ => 1, // 读写分离后 主服务器数量
‘slave_no’ => ”, // 指定从服务器序号
‘db_like_fields’ => ”,
);
// 数据库表达式
protected $exp = array(‘eq’=>’=’,’neq’=>'<>’,’gt’=>’>’,’egt’=>’>=’,’lt’=>'<‘,’elt’=>'<=’,’notlike’=>’NOT LIKE’,’like’=>’LIKE’,’in’=>’IN’,’notin’=>’NOT IN’,’not in’=>’NOT IN’,’between’=>’BETWEEN’,’not between’=>’NOT BETWEEN’,’notbetween’=>’NOT BETWEEN’);
// 查询表达式
protected $selectSql = ‘SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%’;
// 查询次数
protected $queryTimes = 0;
// 执行次数
protected $executeTimes = 0;
// PDO连接参数
protected $options = array(
PDO::ATTR_CASE => PDO::CASE_LOWER,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
);
protected $bind = array(); // 参数绑定

/**
* 架构函数 读取数据库配置信息
* @access public
* @param array $config 数据库配置数组
*/
public function __construct($config=”){
if(!empty($config)) {
$this->config = array_merge($this->config,$config);
if(is_array($this->config[‘params’])){
$this->options = $this->config[‘params’] + $this->options;
}
}
}

/**
* 连接数据库方法
* @access public
*/
public function connect($config=”,$linkNum=0,$autoConnection=false) {
if ( !isset($this->linkID[$linkNum]) ) {
if(empty($config)) $config = $this->config;
try{
if(empty($config[‘dsn’])) {
$config[‘dsn’] = $this->parseDsn($config);
}
if(version_compare(PHP_VERSION,’5.3.6′,'<=’)){
// 禁用模拟预处理语句
$this->options[PDO::ATTR_EMULATE_PREPARES] = false;
}
$this->linkID[$linkNum] = new PDO( $config[‘dsn’], $config[‘username’], $config[‘password’],$this->options);
}catch (\PDOException $e) {
if($autoConnection){
trace($e->getMessage(),”,’ERR’);
return $this->connect($autoConnection,$linkNum);
}elseif($config[‘debug’]){
E($e->getMessage());
}
}
}
return $this->linkID[$linkNum];
}

/**
* 解析pdo连接的dsn信息
* @access public
* @param array $config 连接信息
* @return string
*/
protected function parseDsn($config){}

/**
* 释放查询结果
* @access public
*/
public function free() {
$this->PDOStatement = null;
}

/**
* 执行查询 返回数据集
* @access public
* @param string $str sql指令
* @param boolean $fetchSql 不执行只是获取SQL
* @return mixed
*/
public function query($str,$fetchSql=false) {
$this->initConnect(false);
if ( !$this->_linkID ) return false;
$this->queryStr = $str;
if(!empty($this->bind)){
$that = $this;
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return ‘\”.$that->escapeString($val).’\”; },$this->bind));
}
if($fetchSql){
return $this->queryStr;
}
//释放前次的查询结果
if ( !empty($this->PDOStatement) ) $this->free();
$this->queryTimes++;
N(‘db_query’,1); // 兼容代码
// 调试开始
$this->debug(true);
$this->PDOStatement = $this->_linkID->prepare($str);
if(false === $this->PDOStatement){
$this->error(‘query’);
return false;
}
foreach ($this->bind as $key => $val) {
if(is_array($val)){
$this->PDOStatement->bindValue($key, $val[0], $val[1]);
}else{
$this->PDOStatement->bindValue($key, $val);
}
}
$this->bind = array();
$result = $this->PDOStatement->execute();
// 调试结束
$this->debug(false);
if ( false === $result ) {
$this->error(‘query’);
return false;
} else {
return $this->getResult();
}
}

/**
* 执行语句
* @access public
* @param string $str sql指令
* @param boolean $fetchSql 不执行只是获取SQL
* @return mixed
*/
public function execute($str,$fetchSql=false) {
$this->initConnect(true);
if ( !$this->_linkID ) return false;
$this->queryStr = $str;
if(!empty($this->bind)){
$that = $this;
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return ‘\”.$that->escapeString($val).’\”; },$this->bind));
}
if($fetchSql){
return $this->queryStr;
}
//释放前次的查询结果
if ( !empty($this->PDOStatement) ) $this->free();
$this->executeTimes++;
N(‘db_write’,1); // 兼容代码
// 记录开始执行时间
$this->debug(true);
$this->PDOStatement = $this->_linkID->prepare($str);
if(false === $this->PDOStatement) {
$this->error(‘execute’);
return false;
}
foreach ($this->bind as $key => $val) {
if(is_array($val)){
$this->PDOStatement->bindValue($key, $val[0], $val[1]);
}else{
$this->PDOStatement->bindValue($key, $val);
}
}
$this->bind = array();
$result = $this->PDOStatement->execute();
$this->debug(false);
if ( false === $result) {
$this->error(‘execute’);
return false;
} else {
$this->numRows = $this->PDOStatement->rowCount();
if(preg_match(“/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i”, $str)) {
$this->lastInsID = $this->_linkID->lastInsertId();
}
return $this->numRows;
}
}

/**
* 启动事务
* @access public
* @return void
*/
public function startTrans() {
$this->initConnect(true);
if ( !$this->_linkID ) return false;
//数据rollback 支持
if ($this->transTimes == 0) {
$this->_linkID->beginTransaction();
}
$this->transTimes++;
return ;
}

/**
* 用于非自动提交状态下面的查询提交
* @access public
* @return boolean
*/
public function commit() {
if ($this->transTimes > 0) {
$result = $this->_linkID->commit();
$this->transTimes = 0;
if(!$result){
$this->error();
return false;
}
}
return true;
}

/**
* 事务回滚
* @access public
* @return boolean
*/
public function rollback() {
if ($this->transTimes > 0) {
$result = $this->_linkID->rollback();
$this->transTimes = 0;
if(!$result){
$this->error();
return false;
}
}
return true;
}

/**
* 获得所有的查询数据
* @access private
* @return array
*/
private function getResult() {
//返回数据集
$result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
$this->numRows = count( $result );
return $result;
}

/**
* 获得查询次数
* @access public
* @param boolean $execute 是否包含所有查询
* @return integer
*/
public function getQueryTimes($execute=false){
return $execute?$this->queryTimes+$this->executeTimes:$this->queryTimes;
}

/**
* 获得执行次数
* @access public
* @return integer
*/
public function getExecuteTimes(){
return $this->executeTimes;
}

/**
* 关闭数据库
* @access public
*/
public function close() {
$this->_linkID = null;
$this->linkID = [];
return $this;
}

/**
* 数据库错误信息
* 并显示当前的SQL语句
* @access public
* @return string
*/
public function error($method=”) {
if($this->PDOStatement) {
$error = $this->PDOStatement->errorInfo();
$this->error = $error[1].’:’.$error[2];
}else{
$this->error = ”;
}

//2017-8-24 09:18:45 判断是否断线 增加断线重连功能
if($this->isBreak($error[2]) && $method){
return $this->close()->{$method}($this->queryStr);
}

if(” != $this->queryStr){
$this->error .= “\n [ SQL语句 ] : “.$this->queryStr;
}
// 记录错误日志
trace($this->error,”,’ERR’);
if($this->config[‘debug’]) {// 开启数据库调试模式
E($this->error);
}else{
return $this->error;
}
}

/**
* 设置锁机制
* @access protected
* @return string
*/
protected function parseLock($lock=false) {
return $lock? ‘ FOR UPDATE ‘ : ”;
}

/**
* set分析
* @access protected
* @param array $data
* @return string
*/
protected function parseSet($data) {
foreach ($data as $key=>$val){
if(is_array($val) && ‘exp’ == $val[0]){
$set[] = $this->parseKey($key).’=’.$val[1];
}elseif(is_null($val)){
$set[] = $this->parseKey($key).’=NULL’;
}elseif(is_scalar($val)) {// 过滤非标量数据
if(0===strpos($val,’:’) && in_array($val,array_keys($this->bind)) ){
$set[] = $this->parseKey($key).’=’.$this->escapeString($val);
}else{
$name = count($this->bind);
$set[] = $this->parseKey($key).’=:’.$name;
$this->bindParam($name,$val);
}
}
}
return ‘ SET ‘.implode(‘,’,$set);
}

/**
* 参数绑定
* @access protected
* @param string $name 绑定参数名
* @param mixed $value 绑定值
* @return void
*/
protected function bindParam($name,$value){
$this->bind[‘:’.$name] = $value;
}

/**
* 字段名分析
* @access protected
* @param string $key
* @return string
*/
protected function parseKey(&$key) {
return $key;
}

/**
* value分析
* @access protected
* @param mixed $value
* @return string
*/
protected function parseValue($value) {
if(is_string($value)) {
$value = strpos($value,’:’) === 0 && in_array($value,array_keys($this->bind))? $this->escapeString($value) : ‘\”.$this->escapeString($value).’\”;
}elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == ‘exp’){
$value = $this->escapeString($value[1]);
}elseif(is_array($value)) {
$value = array_map(array($this, ‘parseValue’),$value);
}elseif(is_bool($value)){
$value = $value ? ‘1’ : ‘0’;
}elseif(is_null($value)){
$value = ‘null’;
}
return $value;
}

/**
* field分析
* @access protected
* @param mixed $fields
* @return string
*/
protected function parseField($fields) {
if(is_string($fields) && ” !== $fields) {
$fields = explode(‘,’,$fields);
}
if(is_array($fields)) {
// 完善数组方式传字段名的支持
// 支持 ‘field1’=>’field2′ 这样的字段别名定义
$array = array();
foreach ($fields as $key=>$field){
if(!is_numeric($key))
$array[] = $this->parseKey($key).’ AS ‘.$this->parseKey($field);
else
$array[] = $this->parseKey($field);
}
$fieldsStr = implode(‘,’, $array);
}else{
$fieldsStr = ‘*’;
}
//TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖
return $fieldsStr;
}

/**
* table分析
* @access protected
* @param mixed $table
* @return string
*/
protected function parseTable($tables) {
if(is_array($tables)) {// 支持别名定义
$array = array();
foreach ($tables as $table=>$alias){
if(!is_numeric($table))
$array[] = $this->parseKey($table).’ ‘.$this->parseKey($alias);
else
$array[] = $this->parseKey($alias);
}
$tables = $array;
}elseif(is_string($tables)){
$tables = explode(‘,’,$tables);
array_walk($tables, array(&$this, ‘parseKey’));
}
return implode(‘,’,$tables);
}

/**
* where分析
* @access protected
* @param mixed $where
* @return string
*/
protected function parseWhere($where) {
$whereStr = ”;
if(is_string($where)) {
// 直接使用字符串条件
$whereStr = $where;
}else{ // 使用数组表达式
$operate = isset($where[‘_logic’])?strtoupper($where[‘_logic’]):”;
if(in_array($operate,array(‘AND’,’OR’,’XOR’))){
// 定义逻辑运算规则 例如 OR XOR AND NOT
$operate = ‘ ‘.$operate.’ ‘;
unset($where[‘_logic’]);
}else{
// 默认进行 AND 运算
$operate = ‘ AND ‘;
}
foreach ($where as $key=>$val){
if(is_numeric($key)){
$key = ‘_complex’;
}
if(0===strpos($key,’_’)) {
// 解析特殊条件表达式
$whereStr .= $this->parseThinkWhere($key,$val);
}else{
// 查询字段的安全过滤
// if(!preg_match(‘/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/’,trim($key))){
// E(L(‘_EXPRESS_ERROR_’).’:’.$key);
// }
// 多条件支持
$multi = is_array($val) && isset($val[‘_multi’]);
$key = trim($key);
if(strpos($key,’|’)) { // 支持 name|title|nickname 方式定义查询字段
$array = explode(‘|’,$key);
$str = array();
foreach ($array as $m=>$k){
$v = $multi?$val[$m]:$val;
$str[] = $this->parseWhereItem($this->parseKey($k),$v);
}
$whereStr .= ‘( ‘.implode(‘ OR ‘,$str).’ )’;
}elseif(strpos($key,’&’)){
$array = explode(‘&’,$key);
$str = array();
foreach ($array as $m=>$k){
$v = $multi?$val[$m]:$val;
$str[] = ‘(‘.$this->parseWhereItem($this->parseKey($k),$v).’)’;
}
$whereStr .= ‘( ‘.implode(‘ AND ‘,$str).’ )’;
}else{
$whereStr .= $this->parseWhereItem($this->parseKey($key),$val);
}
}
$whereStr .= $operate;
}
$whereStr = substr($whereStr,0,-strlen($operate));
}
return empty($whereStr)?”:’ WHERE ‘.$whereStr;
}

// where子单元分析
protected function parseWhereItem($key,$val) {
$whereStr = ”;
if(is_array($val)) {
if(is_string($val[0])) {
$exp = strtolower($val[0]);
if(preg_match(‘/^(eq|neq|gt|egt|lt|elt)$/’,$exp)) { // 比较运算
$whereStr .= $key.’ ‘.$this->exp[$exp].’ ‘.$this->parseValue($val[1]);
}elseif(preg_match(‘/^(notlike|like)$/’,$exp)){// 模糊查找
if(is_array($val[1])) {
$likeLogic = isset($val[2])?strtoupper($val[2]):’OR’;
if(in_array($likeLogic,array(‘AND’,’OR’,’XOR’))){
$like = array();
foreach ($val[1] as $item){
$like[] = $key.’ ‘.$this->exp[$exp].’ ‘.$this->parseValue($item);
}
$whereStr .= ‘(‘.implode(‘ ‘.$likeLogic.’ ‘,$like).’)’;
}
}else{
$whereStr .= $key.’ ‘.$this->exp[$exp].’ ‘.$this->parseValue($val[1]);
}
}elseif(‘bind’ == $exp ){ // 使用表达式
$whereStr .= $key.’ = :’.$val[1];
}elseif(‘exp’ == $exp ){ // 使用表达式
$whereStr .= $key.’ ‘.$val[1];
}elseif(preg_match(‘/^(notin|not in|in)$/’,$exp)){ // IN 运算
if(isset($val[2]) && ‘exp’==$val[2]) {
$whereStr .= $key.’ ‘.$this->exp[$exp].’ ‘.$val[1];
}else{
if(is_string($val[1])) {
$val[1] = explode(‘,’,$val[1]);
}
$zone = implode(‘,’,$this->parseValue($val[1]));
$whereStr .= $key.’ ‘.$this->exp[$exp].’ (‘.$zone.’)’;
}
}elseif(preg_match(‘/^(notbetween|not between|between)$/’,$exp)){ // BETWEEN运算
$data = is_string($val[1])? explode(‘,’,$val[1]):$val[1];
$whereStr .= $key.’ ‘.$this->exp[$exp].’ ‘.$this->parseValue($data[0]).’ AND ‘.$this->parseValue($data[1]);
}else{
E(L(‘_EXPRESS_ERROR_’).’:’.$val[0]);
}
}else {
$count = count($val);
$rule = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : ” ;
if(in_array($rule,array(‘AND’,’OR’,’XOR’))) {
$count = $count -1;
}else{
$rule = ‘AND’;
}
for($i=0;$i<$count;$i++) {
$data = is_array($val[$i])?$val[$i][1]:$val[$i];
if(‘exp’==strtolower($val[$i][0])) {
$whereStr .= $key.’ ‘.$data.’ ‘.$rule.’ ‘;
}else{
$whereStr .= $this->parseWhereItem($key,$val[$i]).’ ‘.$rule.’ ‘;
}
}
$whereStr = ‘( ‘.substr($whereStr,0,-4).’ )’;
}
}else {
//对字符串类型字段采用模糊匹配
$likeFields = $this->config[‘db_like_fields’];
if($likeFields && preg_match(‘/^(‘.$likeFields.’)$/i’,$key)) {
$whereStr .= $key.’ LIKE ‘.$this->parseValue(‘%’.$val.’%’);
}else {
$whereStr .= $key.’ = ‘.$this->parseValue($val);
}
}
return $whereStr;
}

/**
* 特殊条件分析
* @access protected
* @param string $key
* @param mixed $val
* @return string
*/
protected function parseThinkWhere($key,$val) {
$whereStr = ”;
switch($key) {
case ‘_string’:
// 字符串模式查询条件
$whereStr = $val;
break;
case ‘_complex’:
// 复合查询条件
$whereStr = substr($this->parseWhere($val),6);
break;
case ‘_query’:
// 字符串模式查询条件
parse_str($val,$where);
if(isset($where[‘_logic’])) {
$op = ‘ ‘.strtoupper($where[‘_logic’]).’ ‘;
unset($where[‘_logic’]);
}else{
$op = ‘ AND ‘;
}
$array = array();
foreach ($where as $field=>$data)
$array[] = $this->parseKey($field).’ = ‘.$this->parseValue($data);
$whereStr = implode($op,$array);
break;
}
return ‘( ‘.$whereStr.’ )’;
}

/**
* limit分析
* @access protected
* @param mixed $lmit
* @return string
*/
protected function parseLimit($limit) {
return !empty($limit)? ‘ LIMIT ‘.$limit.’ ‘:”;
}

/**
* join分析
* @access protected
* @param mixed $join
* @return string
*/
protected function parseJoin($join) {
$joinStr = ”;
if(!empty($join)) {
$joinStr = ‘ ‘.implode(‘ ‘,$join).’ ‘;
}
return $joinStr;
}

/**
* order分析
* @access protected
* @param mixed $order
* @return string
*/
protected function parSEOrder($order) {
if(is_array($order)) {
$array = array();
foreach ($order as $key=>$val){
if(is_numeric($key)) {
$array[] = $this->parseKey($val);
}else{
$array[] = $this->parseKey($key).’ ‘.$val;
}
}
$order = implode(‘,’,$array);
}
return !empty($order)? ‘ ORDER BY ‘.$order:”;
}

/**
* group分析
* @access protected
* @param mixed $group
* @return string
*/
protected function parseGroup($group) {
return !empty($group)? ‘ GROUP BY ‘.$group:”;
}

/**
* having分析
* @access protected
* @param string $having
* @return string
*/
protected function parseHaving($having) {
return !empty($having)? ‘ HAVING ‘.$having:”;
}

/**
* comment分析
* @access protected
* @param string $comment
* @return string
*/
protected function parseComment($comment) {
return !empty($comment)? ‘ /* ‘.$comment.’ */’:”;
}

/**
* distinct分析
* @access protected
* @param mixed $distinct
* @return string
*/
protected function parseDistinct($distinct) {
return !empty($distinct)? ‘ DISTINCT ‘ :”;
}

/**
* union分析
* @access protected
* @param mixed $union
* @return string
*/
protected function parseUnion($union) {
if(empty($union)) return ”;
if(isset($union[‘_all’])) {
$str = ‘UNION ALL ‘;
unset($union[‘_all’]);
}else{
$str = ‘UNION ‘;
}
foreach ($union as $u){
$sql[] = $str.(is_array($u)?$this->buildSelectSql($u):$u);
}
return implode(‘ ‘,$sql);
}

/**
* 参数绑定分析
* @access protected
* @param array $bind
* @return array
*/
protected function parseBind($bind){
$this->bind = array_merge($this->bind,$bind);
}

/**
* index分析,可在操作链中指定需要强制使用的索引
* @access protected
* @param mixed $index
* @return string
*/
protected function parseForce($index) {
if(empty($index)) return ”;
if(is_array($index)) $index = join(“,”, $index);
return sprintf(” FORCE INDEX ( %s ) “, $index);
}

/**
* ON DUPLICATE KEY UPDATE 分析
* @access protected
* @param mixed $duplicate
* @return string
*/
protected function parseDuplicate($duplicate){
return ”;
}

/**
* 插入记录
* @access public
* @param mixed $data 数据
* @param array $options 参数表达式
* @param boolean $replace 是否replace
* @return false | integer
*/
public function insert($data,$options=array(),$replace=false) {
$values = $fields = array();
$this->model = $options[‘model’];
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
foreach ($data as $key=>$val){
if(is_array($val) && ‘exp’ == $val[0]){
$fields[] = $this->parseKey($key);
$values[] = $val[1];
}elseif(is_null($val)){
$fields[] = $this->parseKey($key);
$values[] = ‘NULL’;
}elseif(is_scalar($val)) { // 过滤非标量数据
$fields[] = $this->parseKey($key);
if(0===strpos($val,’:’) && in_array($val,array_keys($this->bind))){
$values[] = $this->parseValue($val);
}else{
$name = count($this->bind);
$values[] = ‘:’.$name;
$this->bindParam($name,$val);
}
}
}
// 兼容数字传入方式
$replace= (is_numeric($replace) && $replace>0)?true:$replace;
$sql = (true===$replace?’REPLACE’:’INSERT’).’ INTO ‘.$this->parseTable($options[‘table’]).’ (‘.implode(‘,’, $fields).’) VALUES (‘.implode(‘,’, $values).’)’.$this->parseDuplicate($replace);
$sql .= $this->parseComment(!empty($options[‘comment’])?$options[‘comment’]:”);
return $this->execute($sql,!empty($options[‘fetch_sql’]) ? true : false);
}

/**
* 批量插入记录
* @access public
* @param mixed $dataSet 数据集
* @param array $options 参数表达式
* @param boolean $replace 是否replace
* @return false | integer
*/
public function insertAll($dataSet,$options=array(),$replace=false) {
$values = array();
$this->model = $options[‘model’];
if(!is_array($dataSet[0])) return false;
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
$fields = array_map(array($this,’parseKey’),array_keys($dataSet[0]));
foreach ($dataSet as $data){
$value = array();
foreach ($data as $key=>$val){
if(is_array($val) && ‘exp’ == $val[0]){
$value[] = $val[1];
}elseif(is_null($val)){
$value[] = ‘NULL’;
}elseif(is_scalar($val)){
if(0===strpos($val,’:’) && in_array($val,array_keys($this->bind))){
$value[] = $this->parseValue($val);
}else{
$name = count($this->bind);
$value[] = ‘:’.$name;
$this->bindParam($name,$val);
}
}
}
$values[] = ‘SELECT ‘.implode(‘,’, $value);
}
$sql = ‘INSERT INTO ‘.$this->parseTable($options[‘table’]).’ (‘.implode(‘,’, $fields).’) ‘.implode(‘ UNION ALL ‘,$values);
$sql .= $this->parseComment(!empty($options[‘comment’])?$options[‘comment’]:”);
return $this->execute($sql,!empty($options[‘fetch_sql’]) ? true : false);
}

/**
* 通过Select方式插入记录
* @access public
* @param string $fields 要插入的数据表字段名
* @param string $table 要插入的数据表名
* @param array $option 查询数据参数
* @return false | integer
*/
public function selectInsert($fields,$table,$options=array()) {
$this->model = $options[‘model’];
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
if(is_string($fields)) $fields = explode(‘,’,$fields);
array_walk($fields, array($this, ‘parseKey’));
$sql = ‘INSERT INTO ‘.$this->parseTable($table).’ (‘.implode(‘,’, $fields).’) ‘;
$sql .= $this->buildSelectSql($options);
return $this->execute($sql,!empty($options[‘fetch_sql’]) ? true : false);
}

/**
* 更新记录
* @access public
* @param mixed $data 数据
* @param array $options 表达式
* @return false | integer
*/
public function update($data,$options) {
$this->model = $options[‘model’];
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
$table = $this->parseTable($options[‘table’]);
$sql = ‘UPDATE ‘ . $table . $this->parseSet($data);
if(strpos($table,’,’)){// 多表更新支持JOIN操作
$sql .= $this->parseJoin(!empty($options[‘join’])?$options[‘join’]:”);
}
$sql .= $this->parseWhere(!empty($options[‘where’])?$options[‘where’]:”);
if(!strpos($table,’,’)){
// 单表更新支持order和lmit
$sql .= $this->parSEOrder(!empty($options[‘order’])?$options[‘order’]:”)
.$this->parseLimit(!empty($options[‘limit’])?$options[‘limit’]:”);
}
$sql .= $this->parseComment(!empty($options[‘comment’])?$options[‘comment’]:”);
return $this->execute($sql,!empty($options[‘fetch_sql’]) ? true : false);
}

/**
* 删除记录
* @access public
* @param array $options 表达式
* @return false | integer
*/
public function delete($options=array()) {
$this->model = $options[‘model’];
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
$table = $this->parseTable($options[‘table’]);
$sql = ‘DELETE FROM ‘.$table;
if(strpos($table,’,’)){// 多表删除支持USING和JOIN操作
if(!empty($options[‘using’])){
$sql .= ‘ USING ‘.$this->parseTable($options[‘using’]).’ ‘;
}
$sql .= $this->parseJoin(!empty($options[‘join’])?$options[‘join’]:”);
}
$sql .= $this->parseWhere(!empty($options[‘where’])?$options[‘where’]:”);
if(!strpos($table,’,’)){
// 单表删除支持order和limit
$sql .= $this->parseOrder(!empty($options[‘order’])?$options[‘order’]:”)
.$this->parseLimit(!empty($options[‘limit’])?$options[‘limit’]:”);
}
$sql .= $this->parseComment(!empty($options[‘comment’])?$options[‘comment’]:”);
return $this->execute($sql,!empty($options[‘fetch_sql’]) ? true : false);
}

/**
* 查找记录
* @access public
* @param array $options 表达式
* @return mixed
*/
public function select($options=array()) {
$this->model = $options[‘model’];
$this->parseBind(!empty($options[‘bind’])?$options[‘bind’]:array());
$sql = $this->buildSelectSql($options);
$result = $this->query($sql,!empty($options[‘fetch_sql’]) ? true : false);
return $result;
}

/**
* 生成查询SQL
* @access public
* @param array $options 表达式
* @return string
*/
public function buildSelectSql($options=array()) {
if(isset($options[‘page’])) {
// 根据页数计算limit
list($page,$listRows) = $options[‘page’];
$page = $page>0 ? $page : 1;
$listRows= $listRows>0 ? $listRows : (is_numeric($options[‘limit’])?$options[‘limit’]:20);
$offset = $listRows*($page-1);
$options[‘limit’] = $offset.’,’.$listRows;
}
$sql = $this->parseSql($this->selectSql,$options);
return $sql;
}

/**
* 替换SQL语句中表达式
* @access public
* @param array $options 表达式
* @return string
*/
public function parseSql($sql,$options=array()){
$sql = str_replace(
array(‘%TABLE%’,’%DISTINCT%’,’%FIELD%’,’%JOIN%’,’%WHERE%’,’%GROUP%’,’%HAVING%’,’%ORDER%’,’%LIMIT%’,’%UNION%’,’%LOCK%’,’%COMMENT%’,’%FORCE%’),
array(
$this->parseTable($options[‘table’]),
$this->parseDistinct(isset($options[‘distinct’])?$options[‘distinct’]:false),
$this->parseField(!empty($options[‘field’])?$options[‘field’]:’*’),
$this->parseJoin(!empty($options[‘join’])?$options[‘join’]:”),
$this->parseWhere(!empty($options[‘where’])?$options[‘where’]:”),
$this->parseGroup(!empty($options[‘group’])?$options[‘group’]:”),
$this->parseHaving(!empty($options[‘having’])?$options[‘having’]:”),
$this->parseOrder(!empty($options[‘order’])?$options[‘order’]:”),
$this->parseLimit(!empty($options[‘limit’])?$options[‘limit’]:”),
$this->parseUnion(!empty($options[‘union’])?$options[‘union’]:”),
$this->parseLock(isset($options[‘lock’])?$options[‘lock’]:false),
$this->parseComment(!empty($options[‘comment’])?$options[‘comment’]:”),
$this->parseForce(!empty($options[‘force’])?$options[‘force’]:”)
),$sql);
return $sql;
}

/**
* 获取最近一次查询的sql语句
* @param string $model 模型名
* @access public
* @return string
*/
public function getLastSql($model=”) {
return $model?$this->modelSql[$model]:$this->queryStr;
}

/**
* 获取最近插入的ID
* @access public
* @return string
*/
public function getLastInsID() {
return $this->lastInsID;
}

/**
* 获取最近的错误信息
* @access public
* @return string
*/
public function getError() {
return $this->error;
}

/**
* SQL指令安全过滤
* @access public
* @param string $str SQL字符串
* @return string
*/
public function escapeString($str) {
return addslashes($str);
}

/**
* 设置当前操作模型
* @access public
* @param string $model 模型名
* @return void
*/
public function setModel($model){
$this->model = $model;
}

/**
* 数据库调试 记录当前SQL
* @access protected
* @param boolean $start 调试开始标记 true 开始 false 结束
*/
protected function debug($start) {
if($this->config[‘debug’]) {// 开启数据库调试模式
if($start) {
G(‘queryStartTime’);
}else{
$this->modelSql[$this->model] = $this->queryStr;
//$this->model = ‘_think_’;
// 记录操作结束时间
G(‘queryEndTime’);
trace($this->queryStr.’ [ RunTime:’.G(‘queryStartTime’,’queryEndTime’).’s ]’,”,’SQL’);
}
}
}

/**
* 初始化数据库连接
* @access protected
* @param boolean $master 主服务器
* @return void
*/
protected function initConnect($master=true) {
if(!empty($this->config[‘deploy’]))
// 采用分布式数据库
$this->_linkID = $this->multiConnect($master);
else
// 默认单数据库
if ( !$this->_linkID ) $this->_linkID = $this->connect();
}

/**
* 连接分布式服务器
* @access protected
* @param boolean $master 主服务器
* @return void
*/
protected function multiConnect($master=false) {
// 分布式数据库配置解析
$_config[‘username’] = explode(‘,’,$this->config[‘username’]);
$_config[‘password’] = explode(‘,’,$this->config[‘password’]);
$_config[‘hostname’] = explode(‘,’,$this->config[‘hostname’]);
$_config[‘hostport’] = explode(‘,’,$this->config[‘hostport’]);
$_config[‘database’] = explode(‘,’,$this->config[‘database’]);
$_config[‘dsn’] = explode(‘,’,$this->config[‘dsn’]);
$_config[‘charset’] = explode(‘,’,$this->config[‘charset’]);

$m = floor(mt_rand(0,$this->config[‘master_num’]-1));
// 数据库读写是否分离
if($this->config[‘rw_separate’]){
// 主从式采用读写分离
if($master)
// 主服务器写入
$r = $m;
else{
if(is_numeric($this->config[‘slave_no’])) {// 指定服务器读
$r = $this->config[‘slave_no’];
}else{
// 读操作连接从服务器
$r = floor(mt_rand($this->config[‘master_num’],count($_config[‘hostname’])-1)); // 每次随机连接的数据库
}
}
}else{
// 读写操作不区分服务器
$r = floor(mt_rand(0,count($_config[‘hostname’])-1)); // 每次随机连接的数据库
}

if($m != $r ){
$db_master = array(
‘username’ => isset($_config[‘username’][$m])?$_config[‘username’][$m]:$_config[‘username’][0],
‘password’ => isset($_config[‘password’][$m])?$_config[‘password’][$m]:$_config[‘password’][0],
‘hostname’ => isset($_config[‘hostname’][$m])?$_config[‘hostname’][$m]:$_config[‘hostname’][0],
‘hostport’ => isset($_config[‘hostport’][$m])?$_config[‘hostport’][$m]:$_config[‘hostport’][0],
‘database’ => isset($_config[‘database’][$m])?$_config[‘database’][$m]:$_config[‘database’][0],
‘dsn’ => isset($_config[‘dsn’][$m])?$_config[‘dsn’][$m]:$_config[‘dsn’][0],
‘charset’ => isset($_config[‘charset’][$m])?$_config[‘charset’][$m]:$_config[‘charset’][0],
);
}
$db_config = array(
‘username’ => isset($_config[‘username’][$r])?$_config[‘username’][$r]:$_config[‘username’][0],
‘password’ => isset($_config[‘password’][$r])?$_config[‘password’][$r]:$_config[‘password’][0],
‘hostname’ => isset($_config[‘hostname’][$r])?$_config[‘hostname’][$r]:$_config[‘hostname’][0],
‘hostport’ => isset($_config[‘hostport’][$r])?$_config[‘hostport’][$r]:$_config[‘hostport’][0],
‘database’ => isset($_config[‘database’][$r])?$_config[‘database’][$r]:$_config[‘database’][0],
‘dsn’ => isset($_config[‘dsn’][$r])?$_config[‘dsn’][$r]:$_config[‘dsn’][0],
‘charset’ => isset($_config[‘charset’][$r])?$_config[‘charset’][$r]:$_config[‘charset’][0],
);
return $this->connect($db_config,$r,$r == $m ? false : $db_master);
}

/**
* 是否断线
* 2017-8-24 09:12:40 摘自TP5 DB/Connection.php
* 修改传入的参数 来适配TP3.2
* @access protected
* @param String $error 返回的错误信息
* @return bool
*/
protected function isBreak($error)
{
$info = [
‘server has gone away’,
‘no connection to the server’,
‘Lost connection’,
‘is dead or not enabled’,
‘Error while sending’,
‘decryption failed or bad record mac’,
‘server closed the connection unexpectedly’,
‘SSL connection has been closed unexpectedly’,
‘Error writing data to the connection’,
‘Resource deadlock avoided’,
];

foreach ($info as $msg) {
if (false !== stripos($error, $msg)) {
return true;
}
}
return false;
}

/**
* 析构方法
* @access public
*/
public function __destruct() {
// 释放查询
if ($this->PDOStatement){
$this->free();
}
// 关闭连接
$this->close();
}
}
————————————————
版权声明:本文为CSDN博主「一个不靠谱的程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013705066/article/details/77679827

Swoole整合ThinkPHP3.2系列教程三_一个不靠谱的程序员-CSDN博客

mikel阅读(951)

来源: Swoole整合ThinkPHP3.2系列教程三_一个不靠谱的程序员-CSDN博客

使用说明:
作为管理员:
0.安装swoole扩展
swoole项目已收录到PHP官方扩展库,直接使用:
– pecl install swoole

1.启动swoole服务
在cli模式下,进入Swoole目录,执行以下命令行:
– php swoole.php start

2.柔性重启swoole服务
用于SwooleController框架里的代码更新时,执行以下命令行重新加载代码:
– php swoole.php reload

3.关闭swoole服务
– 直接用kill命令杀死swoole主进程即可(除非特殊情况),一般使用柔性重启就会重启worker进程

4.常用的一些命令:
– lsof -i:9501 查看端口的使用情况
– ps -aux|grep swoole 查看swoole的进程(通常是有一共会创建2 + n + m个进程,其中n为Worker进程数,m为TaskWorker进程数,2为一个Master进程和一个Manager进程,需要修改worker进程和task进程的数量,修改Server.php里的option)

作为开发者:
尽管放心大胆地把你的耗时操作的业务逻辑代码写到Cli模块下的SwooleController里。
当浏览器需要请求耗时任务之前,必须去业务服务器swooleLog表里备案一下,即获取swoole_log表里的ID,携带这个ID去请求swoole服务,否则swoole忽略此次请求,但并不会关闭连接。
我们约定请求耗时操作或者任务的过程按照如下规则进行:
1. 浏览器请求业务服务器,备案此次操作,主要包括耗时操作的function名字以及参数存入业务服务器那边的SwooleLog表(注意重复任务不要多次提交,记得排重)
2. 业务服务器返回浏览器swoole服务的连接地址,以及备案的log_id
3. 浏览器收到业务服务器的返回值以后,创建websocket客户端:
ws = new WebSocket(config.server+’?uid=1′);//请求的连接地址使用get请求加上uid
请求swoole服务执行该任务我们约定请求swoole时的数据格式按如下规则统一:
{
cmd:”test” ,//SwooleController里必须有对应的方法名称,
args:{
id: 1,//SwooleLog表里的id
…//其他需要携带的参数

}
}
4. 处理websocket收到消息时的回调
wx.onmessage=function(e){
var message = JSON.parse(e.data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
swoole服务的返回值
code info
202 任务已经提交,请等待服务器计算数据!
404 任务提交失败,请联系管理员进行处理!
200 任务执行完毕!
附上一份JS版的websocket代码案例(暂不支持低版本IE,近期会想办法解决)
function swooleClient(){
var config = {
‘server’ : ‘ws://192.168.0.166:9501′,
};

var ws = {};

$(document).ready(function () {
ws = new WebSocket(config.server+’?uid=1′);
listenEvent();
});

function listenEvent() {
/**
* 连接建立时触发
*/
ws.onopen = function (e) {
//连接成功
console.log(“connect webim server success.”);
//发送登录信息

ws.send(JSON.stringify({
cmd : ‘test’,
args : {
id : 3,
name: ‘哈哈’
}
}));
};

//有消息到来时触发
ws.onmessage = function (e) {
console.log(e);
var message = JSON.parse(e.data);
$(document.body).html(“<h1 style=’text-align: center’>”+message.info+”</h1>”);
};

/**
* 连接关闭事件
*/
ws.onclose = function (e) {
$(document.body).html(“<h1 style=’text-align: center’>连接已断开,请刷新页面重新登录。</h1>”);
};

/**
* 异常事件
*/
ws.onerror = function (e) {
$(document.body).html(“<h1 style=’text-align: center’>服务器”+
“: 拒绝了连接. 请检查服务器是否启动. </h1>”);
console.log(“onerror: ” + e.data);
};
}
————————————————
版权声明:本文为CSDN博主「一个不靠谱的程序员」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013705066/article/details/77679670