php artisan make:command swoole
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use App\Handlers\SwooleHandler;
class swoole extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'swoole:action {action}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'test swoole socket';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->fire();
}
public function fire(){
$arg=$this->argument('action');
switch($arg){
case 'start':
$this->info('swoole observer started');
$this->start();
break;
case 'stop':
$this->info('stoped');
break;
case 'restart':
$this->info('restarted');
break;
}
}
public function start(){
$this->serv=new \swoole_websocket_server('0.0.0.0',9501);
$this->serv->set(
array(
'worker_num'=>4,
'daemonize'=>1,
'log_file'=>'/home/log/swoole.log',
'max_request'=>100,
'dispatch_mode'=>2,
'debug_mode'=>1
)
);
$handler=SwooleHandler::getInstance();
$this->serv->on('open',array($handler,'onOpen'));
$this->serv->on('workerstart',array($handler,'onWorkStart'));
//$this->serv->on('Start',array($handler,'onStart'));
//$this->serv->on('Connect',array($handler,'onConnect'));
$this->serv->on('message',array($handler,'onMessage'));
//$this->serv->on('Receive',array($handler,'onReceive'));
$this->serv->on('close',array($handler,'onClose'));
$this->serv->start();
}
protected function getArguments(){
return array(
'action',InputArgument::REQUIRED,'start|stop|restart'
);
}
}
这里可以看到调用的swoole进程时候设置了几个方法,用来监听对应的事件。监听事件的方法使用了一个handler类来实现:
$handler=SwooleHandler::getInstance();
这里先把handler类列出来
/**
* Created by PhpStorm.
* User: sunnier
* Email:[email protected]
* Date: 2018/7/18
* Time: 10:37
*/
namespace App\Handlers;
use App\Models\OrderGoods;
use Illuminate\Support\Facades\Log;
use App\Models\StoreUser;
use Illuminate\Support\Facades\Redis;
class SwooleHandler
{
private static $_instance; //保存类实例的私有静态成员变量
private $redis;
public function __construct()
{
}
//定义私有的__clone()方法,确保单例类不能被复制或克隆
private function __clone() {}
//对外提供获取唯一实例的方法
public static function getInstance()
{
//检测类是否被实例化
if ( ! (self::$_instance instanceof self) ) {
self::$_instance = new SwooleHandler();
}
return self::$_instance;
}
public function onWorkStart($serv, $rq)
{
echo "Swoole onWorkStart ";
$redis = Redis::connection();
$serv->redis = $redis;
}
public function onOpen($serv, $rq)
{
echo "Swoole http server is started at http://0.0.0.0:9501\n";
}
public function onStart($serv)
{
Log::info('swoole start');
}
public function onConnect($serv, $fd, $from_id)
{
Log::info('swoole connect' . $from_id);
}
public function onMessage($serv, $frame)
{
//获取token和操作
$this->handleByAction($serv, $frame);
Log::info('swoole message' . json_encode($frame));
}
/**
* 根据传来的字符串处理相应的内容
* create by sunnier
* Email:[email protected]
* @param $server
* @param $str
*/
private function handleByAction($server, $frame)
{
//根据,拆分所传的字符串,判断用户行为
$str=$this->decode($frame->data);
$arr=explode('||:||',$str);
if(is_array($arr)) {
//第一个是token
$token = addslashes(strval($arr[0]));
if(!empty($arr[1])) {
//第二个是行为
$action = strval($arr[1]);
switch ($action) {
case 'login':
$this->login($token, $server, $frame);
break;
case 'delete':
$this->delete($token, $arr[2], $server, $frame);
break;
case 'push':
$this->push($token, $arr[2], $server, $frame);
break;
default:
}
if (!empty($arr[2])) {
$server->push($frame->fd, $this->encode($arr[2]));
}
}else{
$server->push($frame->fd, $this->encode($arr[0]));
}
}else{
$server->push($frame->fd, $this->encode($frame->data));
}
}
//ascii码转换为字符串
private function decode($M){
$bytes=explode(',',$M);
$str = '';
foreach($bytes as $ch) {
$str .= chr($ch);
}
return $str;
}
/**
* 字符串转换为ascii码
* create by sunnier
* Email:[email protected]
* @param $message
* @return string
*/
private function encode($message){
$bytes = array();
for($i = 0; $i < strlen($message); $i++){
$bytes[] = ord($message[$i]);
}
return implode(',',$bytes);
}
/**
* 登录行为
* create by sunnier
* Email:[email protected]
* @param $token
* @param $server
* @param $frame
*/
private function login($token,$server,$frame){
$user=StoreUser::where('token',$token)->first();
if(!empty($user)) {
$server->redis->set($token, $frame->fd);
$server->push($frame->fd, $this->encode(json_encode(["连接成功!"])));
}else{
$server->push($frame->fd, $this->encode(json_encode(["连接失败,未找到用户!"])));
}
}
/**
* 删除内容行为
* create by sunnier
* Email:[email protected]
* @param $token
* @param $str
* @param $server
*/
private function delete($token,$str,$server,$frame){
$user=StoreUser::where('token',$token)->first();
if(!empty($user)) {
//删除对应的内容
$order_goods_id=intval($str);
if(OrderGoods::where('id',$order_goods_id)->update(['has_finish' => 1])) {
$server->push($frame->fd, $this->encode(json_encode(['action'=>'delete','id'=>$order_goods_id])));
}else{
$server->push($frame->fd, $this->encode(json_encode(["删除失败!"])));
}
}else{
$server->push($frame->fd, $this->encode(json_encode(["删除失败,未找到用户!"])));
}
}
/**
* 推送行为
* create by sunnier
* Email:[email protected]
* @param $token
* @param $str
* @param $server
* @param $frame
*/
private function push($token,$str,$server,$frame){
$user=StoreUser::where('token',$token)->first();
//查询是否存在对应用户
if(!empty($user)) {
$fd = $server->redis->get($token);
$server->push($fd,$this->encode($str));
}else{
$server->push($frame->fd, $this->encode(json_encode(["推送失败,未找到用户!"])));
}
}
public function onClose($serv, $fd, $from_id)
{
Log::info('swoole close');
}
public function onReceive($serv, $fd, $from_id, $data)
{
Log::info(json_encode($data));
}
}
具体如下:
$this->serv->on('workerstart',array($handler,'onWorkStart'));
实际上就是在workstart时调用了handler的onWorkStart方法,代码如下:
public function onWorkStart($serv, $rq)
{
echo "Swoole onWorkStart ";
$redis = Redis::connection();
$serv->redis = $redis;
}
这里就是给建立的每个swoole进程设置单独的redis连接,因为官方有这段说明:
是否可以共用1个redis或mysql连接
$this->serv->on('message',array($handler,'onMessage'));
这里对应了handler类中的onMessage方法,如上所示:
public function onMessage($serv, $frame)
{
//对接收的信息进行处理
$this->handleByAction($serv, $frame);
Log::info('swoole message' . json_encode($frame));
}
swoole会给每个连接websocket的客户端一个id存放在$frame(如onMessge方法中的$frame)中,即:$frame->fd;这样就可以向对应的$frame->fd发送消息。
//ascii码转换为字符串
private function decode($M){
$bytes=explode(',',$M);
$str = '';
foreach($bytes as $ch) {
$str .= chr($ch);
}
return $str;
}
/**
* 字符串转换为ascii码
* create by sunnier
* Email:[email protected]
* @param $message
* @return string
*/
private function encode($message){
$bytes = array();
for($i = 0; $i < strlen($message); $i++){
$bytes[] = ord($message[$i]);
}
return implode(',',$bytes);
}
<script>
//string转换ascii码
function stringToByte(str) {
var bytes = new Array();
var len, c;
len = str.length;
for (var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if (c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if (c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if (c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return bytes;
}
//ascii码转换string
function byteToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
$(function () {
var exampleSocket = new WebSocket("ws://192.168.10.10:9501");
//连接调用方法
exampleSocket.onopen = function (event) {
var str = stringToByte("login");
//发送消息
exampleSocket.send(str);
};
//接受消息
exampleSocket.onmessage = function (event) {
var toStr = byteToString(event.data.split(','));
var res = $.parseJSON(toStr);
console.log(res);
}
});
script>
参考链接