redis中setbit(位操作)的实际应用

mikel阅读(858)

来源: redis中setbit(位操作)的实际应用

edis的五种数据类型相信大家都非常清楚了,任何人问你基本都能脱口而出(string,hash,list,set,zset)。如果还不清楚,建议先看看redis基础,但是还有这几种类型很多人会忽略到。比如 Setbit(位操作),GEO(地理位置信息)等等。

今天我们就来淦一淦setbit,看一看实际场景中到底是怎么用的,以及有哪些优势?

我们先来回顾一下setbit吧,大家知道位操作,只有两个值,0和1,8个位正好是1b,所以位操作是非常节省空间的一种操作。

1 Byte(B) = 8 bit

1 Kilo Byte(KB) = 1024B

1 Mega Byte(MB) = 1024 KB

1 Giga Byte (GB)= 1024 MB

在redis中他的用法也非常简单, 基本语法如下:

redis 127.0.0.1:6379> Setbit KEY_NAME OFFSET

例子:

redis> SETBIT bit 10086 1  #把第10086个位置设置为1
(integer) 0

redis> GETBIT bit 10086    #获取第10086个位置的值  看是0还是1
(integer) 1

redis> GETBIT bit 100   # bit 默认被初始化为 0
(integer) 0

其实就是把某个位标记为1或者0而已,但是它的好处在于非常节省空间。另外既然是位,就会涉及到或运算或者与运算(后面会有实例)。

我们来看一个实例吧

场景: 1亿个用户,每个用户登陆/做任意操作,记为 今天活跃,否则记为不活跃。

每周评出: 有奖活跃用户: 连续7天活动

每月评,等等…

其实简单说就是统计一下连续7天(或者连续30天)有多少人连续登陆过

咱们先来想一想传统的方案

很容易就会想到只要用户登陆了,我在表中插入一条数据,并且记录上对应的日期,然后用mySQL里面的记录来逐个判断,类似于这样:

Userid   date        active

1       2020-07-27  1
1       2020-07-26   1
2       2020-07-27  1
...

但这样是存在一些问题的,主要的问题在于用户量高达1亿,每个用户登陆一次就远远的超过mySQL的极限了,更不要说统计一星期了,而且用上group ,sum运算,计算也是非常慢的。所以在这种用户量大,而且统计比较简单的问题上,咱们可以运用位(setbit)操作来解决问题。

先分析一下思路,对于某一天来说,我们可以把这一天想像成一根小木棍,分成了不同的段落,每个段落对应的就是用户的位(因为有user_id),默认值都是0,只要有人登陆了,就把对应的用户的位置标为1即可。

图片

如上图所示,这个就是一天的登陆情况,user_id为6和user_id为8的用户登陆过。其余的都为没有登陆过。因为这个是位操作,所以占的空间很小,1亿的用户,所占的空间也就不到12M。

一天的问题咱们解决了,如何解决他们是否连续登陆过呢?

我们可以用上多个”木棍”

图片

我们可以把每一天作为一个键,然后每天对用户登陆状态进行标记,在最后用每天做一个”与运算”就可以准确的知道哪些用户连续登陆了。

其实总结一下过程如下:

1、记录用户登陆:

每天按日期生成一个位图, 用户登陆后,把user_id位上的bit值置为1

2:、把1周的位图  and 计算,

位上为1的,即是连续登陆的用户

代码实现如下:

redis 127.0.0.1:6379> setbit mon 100000000 0

(integer) 0

redis 127.0.0.1:6379> setbit mon 3 1

(integer) 0

redis 127.0.0.1:6379> setbit mon 5 1

(integer) 0

redis 127.0.0.1:6379> setbit mon 7 1

(integer) 0

redis 127.0.0.1:6379> setbit thur 100000000 0

(integer) 0

redis 127.0.0.1:6379> setbit thur 3 1

(integer) 0

redis 127.0.0.1:6379> setbit thur 5 1

(integer) 0

redis 127.0.0.1:6379> setbit thur 8 1

(integer) 0

redis 127.0.0.1:6379> setbit wen 100000000 0

(integer) 0

redis 127.0.0.1:6379> setbit wen 3 1

(integer) 0

redis 127.0.0.1:6379> setbit wen 4 1

(integer) 0

redis 127.0.0.1:6379> setbit wen 6 1

(integer) 0

redis 127.0.0.1:6379> bitop and  res mon feb wen

(integer) 12500001

 

如上例,优点为:

1、节约空间, 1亿人每天的登陆情况,用1亿bit,约1200WByte,约10M 的字符就能表示;

2、计算方便。

图片

尝试打开或创建物理文件时,CREATE FILE遇到操作系统错误5 (拒绝访问_麻木博客-CSDN博客

mikel阅读(1850)

来源: 尝试打开或创建物理文件时,CREATE FILE遇到操作系统错误5 (拒绝访问_麻木博客-CSDN博客

错误图
使用SQL Server Management Studio 数据库查询语句创建数据出现如下错误,语法没错,但是就是创建不了

 

尝试打开或创建物理文件 REATE FILE 遇到操作系统错误 5(拒绝访问)

 

原因解析
是因为这个盘符少了users这个用户组,所以你现在的用户没有权限

 

怎么做?
右击该盘,选择属性

 

选择安全-编辑

 

选择添加,在输入对象名称下方输入 users,点击确定

 

 

 

等待配置完成,此账户则拥有权限

 

权限有了就可以继续去执行查看是否可以

 

再次执行语句即可发现命令执行成功没报错了

————————————————
版权声明:本文为CSDN博主「麻木博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44708572/article/details/106832675

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

mikel阅读(1351)

来源: 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阅读(834)

来源: 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阅读(1135)

来源: 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阅读(912)

来源: 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阅读(834)

来源: 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阅读(1158)

来源: 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阅读(1230)

来源: 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阅读(1168)

来源: 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。