由于自己写了一个博客,一直对workman有想法的我,突发奇想的想在博客里面弄个聊天室,因此就研究了一下workman,看了下workman的手册,然后了解了一下,workman,发现他是通过socket进行通讯,由于socket是tcp层的,因此可以进行长链接,接着双向通讯,而不像php的普通情况,通过http或者https进行通讯,因为http或者https每次通讯需要三次握手,四次挥手,而且服务器始终是处于被动状态,无法主动和客户端进行交流,因此若是客户端不进行轮询的情况下,几乎是做不到实时通讯。
而workman可以做到,workman是用PHP开发的,他就是用来调用socket来进行通讯的。此外还需要补充一点就是,workman他存在的一个缺陷,由于他本身是属于单线程的,如果你的服务器是多线程的,那workman是始终无法占满cpu的,所以对于多线程的服务器来说,workman的工作效率的偏低的,因此考虑到多线程的问题,设计workman的大佬们,又设计了一款workman的升级版,gateway,它是属于多线程的,如果用户量大的话,只要CPU处理的过来,他就完全能继续扛着,其实gateway的设计原理就是:你有几个线程,我就开几个work满。
废话不多说,接着开始说workman.
下载和安装workman就不说了,直接说如何使用吧。
由于workman的启动是需要命令框的,因此在laravel中,有专门用来生成自定义命令框的一个模块,commond,具体的话,可以去laravel中查看。
php artisan make:command WorkermanCommand (创建自己的 laravel Artisan Console);
接着就在handle()方法中写上,你的命令逻辑。
我自己定义的命令逻辑是 wk {action} :action 就是你要执行的方法。
例如,我需要执行start ,就可以写成: php artisan wk start
从handle()方法中看到,当action为start 的时候,调用chat_room()这个方法。
可以看到执行的逻辑就是:
1.先实例化workman.
2.开启1个进程,至于为什么不开启多个进程,那是因为,当开启多个进程以后,如果在不同进程中,用户和用户之间是不能通讯得,因此就只开一个进程就ok了,如果无法理解,就举个例子,现在有4个房间,然后一堆人站在房间前面,每一个人只能随机选择一个房间进入,当他们进入房间后,同一个房间的人能相互交流,不同房间的人只能干瞪眼,没法交流。(这就是为什么开一个进程的原因)
3.设置一个变量,用来存放用户信息。以便于对用户进行通讯发送消息。
4.当用户连接的时候,发送信息的时候,这时候获取到用户的信息,并且将他存放到定义好的变量中,然后将他发送的信息遍历给其他的每个一用户,做到信息的实时通讯。
5.当用户断开连接的时候,为了减少workman推送消息的资源,首先吧断开连接的用户给剔除了,然后广播通知其他用户,该用户已经退出聊天。
6.设置一个心跳,看了官方文档,主要作用就是为了防止长链接长时间不通讯导致被路由节点强制断开,说白了就是需要定期的和路由节点说一声:兄弟,我和他还活着,别我们T了。 他这边的心跳功能,就是一个定时器,客户端长时间不发信息给服务器,但是服务器又怕被断开,咋办呢,那服务器只能,定时的给客户端发个消息。这样就能保持不被断开了。心跳间隔建议值:建议客户端发送心跳间隔小于60秒,比如55秒。(这是官方建议,我设置了59秒)
这就是PHP这块的代码。
在cmd命令框执行:php artisan wk start ---这样workman 就启动了。
下面贴一下前端的js代码。
这块是html.就是一个聊天框.
前端需要处理的很少,只需要发送信息和接受消息。
下面看一下,成果
这是模拟的两个用户的测试结果。
当然你也可以到www.youngxs.com/boke/boke_chat来测试玩一下。一个浏览器同时开两个就可以了。
有不懂的可以咨询我:601902897 欢迎来骚扰
下面就是贴下代码:
namespace App\Console\Commands;
use Workerman\Worker;
use \Workerman\Lib\Timer;
use Illuminate\Console\Command;
class WorkermanCommand extends Command{
private $server;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'wk {action}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Start a Workerman server.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
global $argv; //$argv 在ArgvInput类中被定义了,就是$_SERVER['argv'];
$arg = $this->argument('action');
$argv[1] = $argv[2];
$argv[2] = isset($argv[3]) ? "-{$argv[3]}" : '';
switch ($arg) {
case 'start':
$this->chat_room();
break;
}
}
private function chat_room()
{
//建立socket连接
$http_worker = new Worker('websocket://0.0.0.0:2001');
// 启动4个进程对外提供服务
$http_worker->count = 1;
//定义一个属性,用来存放用户的连接信息,$connection
$http_worker->uidConnections = array();
//socket事件类中的,链接属性,调用闭包函数
$http_worker->onConnect = function($connection)use($http_worker)
{
// 设置连接的onMessage回调
$connection->onMessage = function($connection, $data)use($http_worker)
{
//获取用户传输过来的数据,并且进行json_decode解析
$data=json_decode($data,true);
//获取用户ID接着将用户信息$connection,存储在$http_worker->uidConnections中,
$http_worker->uidConnections[$data['id']]=$connection;
//给其他用户发送,该用户发送的信息,为了保持数据的实时性。
foreach ($http_worker->uidConnections as $k =>$v){
if($k != $data['id']){
if(isset($data['content'])){
$v->send(json_encode(['status'=>0,'msg'=>$data['content']]));
}
}
}
};
//设置用户断开连接的回调。
$connection->onClose = function($connection)use($http_worker)
{
foreach ($http_worker->uidConnections as $k=>$v){
if($v==$connection){
unset( $http_worker->uidConnections[$k]);
}else{
$v->send(json_encode(['status'=>0,'msg'=>$k.'下线了']));
}
}
};
/*设置心跳,为防止长链接断开 * 原理:就是设置一个定时器,定时的向客户端发送数据 * $time_interval 时间间隔* */
$time_interval = 59;
Timer::add($time_interval, function()use($http_worker)
{
if(count($http_worker->uidConnections)>0){
foreach ($http_worker->uidConnections as $connection) {
$connection->send(json_encode(['status'=>1,'smg'=>'socket_hot']));
}
}
});
};
Worker::runAll();
}
}
下面的是JS代码
var id='youngxs_name'+ Date.parse(new Date());
$('.italic').html('游客:'+id)
var url='ws://127.0.01:2001'
var ws =new WebSocket(url);
ws.onopen =function() {
var data={'id':id}
data=JSON.stringify(data);
ws.send(data);
$('#chat_sure').click(function () {
var content=$('#chat_send').val();
var str=id+'说:'+content;
var data={'id':id,'content':str}
data=JSON.stringify(data);
ws.send(data);
$('#chat_room').append('
您说:'+content+'
');$('#chat_send').val('')
})
$("#chat_send").keypress(function (e) {
if (e.which ==13) {
var content=$('#chat_send').val();
var str=id+'说:'+content;
var data={'id':id,'content':str}
var $chatRoom =$('#chat_room')
data=JSON.stringify(data);
ws.send(data);
$('#chat_room').append('
您说:'+content+'
')$('#chat_send').val('')
$chatRoom[0].scrollTop = $chatRoom[0].scrollHeight-$chatRoom[0].clientHeight;
}
});
};
ws.onmessage =function(e) {
var msg=JSON.parse(e.data);
if(msg.status==0){
$('#chat_room').append('
'+msg.msg+'
');var $chatRoom =$('#chat_room')
$chatRoom[0].scrollTop = $chatRoom[0].scrollHeight-$chatRoom[0].clientHeight;
}
};