一、前期准备 环境搭建
GatewayWorker手册页面直接下载demo(根据自己使用的环境下载)
觉得我的不标准的话可以去哔哩哔哩上看看《码农技术社区》这位大佬的视频挺详细的我就是跟着视频学会的
下载出来是长这个样子的
运行的话直接双击start_for_win.bat就可以了,停止的话ctrl+c,再打y回车就可以了,如图:
启动的样子
停止的样子
首先把GatewayWorker复制到tp5的vendor里面,如图:
打开vendor/GatewayWorker/Applications/YourApp/start_gateway.php,把tcp协议改成Websocket
二、长连接实现群发,初次体验
然后随便找个模板,随便发个消息试试,如图:
实例代码:
var ws = new WebSocket("ws://127.0.0.1:8282");
ws.onmessage = function(e){
console.log(e)
}
$(".send-btn").click(function(){
var content = $(".send-input").val();
ws.send(content)
$(".send-input").val("");
})
三、群发及客户端和socket服务器保持长连接双向消息推送
然后再稍微改动一下就可以得到一个有点小问题的聊天(也不能说问题因为是向所有人发送信息所以回自己哪里也会多一条信息)
代码改动部分(改完Events.php一定要重启start_for_win.bat,否则不生效)
/**
* 当客户端发来消息时触发
* @param int $client_id 连接id
* @param mixed $message 具体消息
*/
public static function onMessage($client_id, $message)
{
$message_data = json_decode($message,true);
if(!$message_data){
return;
}
switch($message_data['type']){
case 'say';
$data = [
'type' => 'text',
'id' => $client_id,
'data' => $message_data['data'],
];
Gateway::sendToAll(json_encode($data));
return;
}
// 向所有人发送
// Gateway::sendToAll("$client_id said $message\r\n");
}
页面js部分
var ws = new WebSocket("ws://127.0.0.1:8282");
ws.onmessage = function(e){
// eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。
// console.log(e)
var msg = eval("("+e.data+")");
// console.log(msg)
switch(msg.type){
case 'text':
var html = '<div class="chat-text section-left flex">'
+'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/123.jpg)"></span>'
+'<span class="text"><i class="icon icon-sanjiao4 t-32"></i>'+msg.data+'</span>'
+'</div>'
$(".chat-content").append(html);
return;
}
}
$(".send-btn").click(function(){
var content = $(".send-input").val();
// var res = '{"data":"'+content+'","type":"say"}';
var res = {
'data':content,
'type':'say',
};
//JSON.stringify json转字符串
ws.send(JSON.stringify(res))
var html = '<div class="chat-text section-right flex">'
+'<span class="text"><i class="icon icon-sanjiao3 t-32"></i>'+content+'</span>'
+'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/132.jpg)"></span>'
+'</div>';
$(".chat-content").append(html);
$(".send-input").val("");
})
四、长连接绑定客户id实现一对一聊天
消息通过用户id来发送信息,效果如图:
这里1给2发送了一个你好啊,2给1发了个我很好
而3哪里啥都没有
首先呢获取你的id跟对方的id发给页面
public function index(){
$input = input('get.');
//fromid个人id toid 对方id
$this->assign(['fromid'=>$input['fromid'],'toid'=>$input['toid']]);
return $this->fetch();
}
并在前台接受并传给终端告诉他我是谁又要发给谁
var fromid = {$fromid};//个人id
var toid = {$toid};//他人id
// console.log(fromid)
// console.log(toid)
var ws = new WebSocket("ws://127.0.0.1:8282");
ws.onmessage = function(e){
// eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。
// console.log(e)
var msg = eval("("+e.data+")");
// console.log(msg)
switch(msg.type){
case 'init':
var bind = {
'type':'bind',
'fromid':fromid,
};
//JSON.stringify json转字符串
ws.send(JSON.stringify(bind))
return
case 'text':
//判断是否是当前聊天对象 是的话才会展示
if(toid == msg.toid){
var html = '<div class="chat-text section-left flex">'
+'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/123.jpg)"></span>'
+'<span class="text"><i class="icon icon-sanjiao4 t-32"></i>'+msg.data+'</span>'
+'</div>'
$(".chat-content").append(html);
}
return;
}
}
$(".send-btn").click(function(){
var content = $(".send-input").val();
// var res = '{"data":"'+content+'","type":"say"}';
var res = {
'data':content,
'type':'say',
'fromid':fromid,
'toid':toid,
};
//JSON.stringify json转字符串
ws.send(JSON.stringify(res))
var html = '<div class="chat-text section-right flex">'
+'<span class="text"><i class="icon icon-sanjiao3 t-32"></i>'+content+'</span>'
+'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/132.jpg)"></span>'
+'</div>';
$(".chat-content").append(html);
$(".send-input").val("");
})
然后Events.php里面进行获取并绑定,还有发送信息绑定对方id,其他根据自己要求来 (改完Events.php一定要重启start_for_win.bat,否则不生效)
public static function onConnect($client_id)
{
// 向当前client_id发送数据
// Gateway::sendToClient($client_id, "Hello $client_id\r\n");
// 向所有人发送
// Gateway::sendToAll("$client_id login\r\n");
//向客户端client_id发送$send_data数据。如果client_id对应的客户端不存在或者不在线则自动丢弃发送数据
Gateway::sendToClient($client_id,json_encode([
'type'=>'init',
'client_id'=>$client_id,
]));
}
/**
* 当客户端发来消息时触发
* @param int $client_id 连接id
* @param mixed $message 具体消息
*/
public static function onMessage($client_id, $message)
{
$message_data = json_decode($message,true);
if(!$message_data){
return;
}
switch($message_data['type']){
case 'bind':
//为啥这个绑定不在一开始连接时触发的时候绑定,因为是一开始才会触发的绑定,给别人发的话还得刷新才行,所以就在发消息的时候绑定id
//将client_id与uid绑定,以便通过Gateway::sendToUid($uid)发送数据,通过Gateway::isUidOnline($uid)用户是否在线。
//uid解释:这里uid泛指用户id或者设备id,用来唯一确定一个客户端用户或者设备。
Gateway::bindUid($client_id,$message_data['fromid']);
return;
case 'say';
$data = [
'type' => 'text',
'data' => $message_data['data'],
'fromid'=>$message_data['fromid'],
'toid'=>$message_data['toid'],
'time'=>time(),
];
// 向uid绑定的所有在线client_id发送数据。
// 注意:默认uid与client_id是一对多的关系,如果当前uid下绑定了多个client_id,则多个client_id对应的客户端都会收到消息,这类似于PC QQ和手机QQ同时在线接收消息。
Gateway::sendToUid($message_data['toid'],json_encode($data));
// 向所有人发送
// Gateway::sendToAll(json_encode($data));
return;
}
// 向所有人发送
// Gateway::sendToAll("$client_id said $message\r\n");
}
五、getwayworker长连接下的普通文本消息之聊天记录持久化
前台代码:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no" />
<title>沟通中</title>
<link rel="stylesheet" type="text/css" href="__STATIC__/Gatewayworker/css/themes.css?v=2017129">
<link rel="stylesheet" type="text/css" href="__STATIC__/Gatewayworker/css/h5app.css">
<link rel="stylesheet" type="text/css" href="__STATIC__/Gatewayworker/fonts/iconfont.css?v=2016070717">
<script src="__STATIC__/Gatewayworker/js/jquery.min.js"></script>
<script src="__STATIC__/Gatewayworker/js/dist/flexible/flexible_css.debug.js"></script>
<script src="__STATIC__/Gatewayworker/js/dist/flexible/flexible.debug.js"></script>
</head>
<body ontouchstart>
<div class='fui-page-group'>
<div class='fui-page chatDetail-page'>
<div class="chat-header flex">
<i class="icon icon-toleft t-48"></i>
<span class="shop-titlte t-30">{$to.name}</span>
<span class="shop-online t-26"></span>
<span class="into-shop">进店</span>
</div>
<div class="fui-content navbar" style="padding:1.2rem 0 1.35rem 0;">
<div class="chat-content">
<p style="display: none;text-align: center;padding-top: 0.5rem" id="more"><a>加载更多</a></p>
{foreach $chat_record as $k => $v}
<p class="chat-time"><span class="time">{$v.add_time}</span></p>
{if $from.id == $v.fromid}
<div class="chat-text section-left flex">
<!-- <span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/123.jpg)"></span> -->
<span class="char-img" style="background-image: url({$to.toux})"></span>
<span class="text"><i class="icon icon-sanjiao4 t-32"></i>{$v.content}</span>
</div>
{else}
<div class="chat-text section-right flex">
<span class="text"><i class="icon icon-sanjiao3 t-32"></i>{$v.content}</span>
<!-- <span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/132.jpg)"></span> -->
<span class="char-img" style="background-image: url({$from.toux})"></span>
</div>
{/if}
{/foreach}
</div>
</div>
<div class="fix-send flex footer-bar">
<i class="icon icon-emoji1 t-50"></i>
<input class="send-input t-28" maxlength="200">
<i class="icon icon-add t-50" style="color: #888;"></i>
<span class="send-btn">发送</span>
</div>
</div>
</div>
<script>
var fromid = {$from.id};//发送者id
var toid = {$to.id};//接收者id
// console.log(fromid)
// console.log(toid)
//连接服务器
var ws = new WebSocket("ws://127.0.0.1:8282");
//调用的方法均在GatewayWorker/Applications/Events.php里面的生命周期函数里面规定
//作用与Worker::$onMessage回调相同,区别是只针对当前连接有效,也就是可以针对某个连接的设置onMessage回调。
ws.onmessage = function(e){
// eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。
// console.log(e)
//获取他人发送来的信息
var msg = eval("("+e.data+")");
switch(msg.type){
case 'init':
//绑定id
var bind = {
'type':'bind',
'fromid':fromid,
};
//JSON.stringify json转字符串 send执行异步连接操作。此方法会立刻返回。 向客户端发送数据
ws.send(JSON.stringify(bind))
//判断是否在线 发过去 //初次加载判断是否在线
var online = {
'type':'online',
'toid':toid,
'fromid':fromid,
};
ws.send(JSON.stringify(online))
//聊天内容定位到最下面
$('.chat-content').scrollTop(3000);
return
//接受消息
case 'text':
//用当前他人id跟他人发送过的fromid来对比 判断是否是当前聊天对象 是的话才会展示
if(toid == msg.fromid){
var html = '<div class="chat-text section-left flex">'
// +'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/123.jpg)"></span>'
+'<span class="char-img" style="background-image: url({$to.toux})"></span>'
+'<span class="text"><i class="icon icon-sanjiao4 t-32"></i>'+msg.data+'</span>'
+'</div>'
$(".chat-content").append(html);
//聊天内容定位到最下面
$('.chat-content').scrollTop(3000);
}
return;
//保存消息 从而消息持久化
case 'save':
//发送消息更新在线状态
if(msg.isread == 2){
$('.shop-online').text('在线');
}else{
$('.shop-online').text('不在线');
}
save_message(msg);//调用 保存聊天数据 方法
//聊天内容定位到最下面
$('.chat-content').scrollTop(3000);
return;
//判断是否在线 回调
case 'online':
//初次加载判断是否在线
if(msg.status == 1){
$('.shop-online').text('在线');
}else{
$('.shop-online').text('不在线');
}
return;
}
}
//发送信息
$(".send-btn").click(function(){
var content = $(".send-input").val();
// var res = '{"data":"'+content+'","type":"say"}';
var res = {
'data':content,
'type':'say',
'fromid':fromid,
'toid':toid,
};
//聊天内容定位到最下面
$('.chat-content').scrollTop(3000);
//JSON.stringify json转字符串
ws.send(JSON.stringify(res))
var html = '<div class="chat-text section-right flex">'
+'<span class="text"><i class="icon icon-sanjiao3 t-32"></i>'+content+'</span>'
// +'<span class="char-img" style="background-image: url(__STATIC__/Gatewayworker/img/132.jpg)"></span>'
+'<span class="char-img" style="background-image: url({$from.toux})"></span>'
+'</div>';
$(".chat-content").append(html);
$(".send-input").val("");
})
//保存聊天数据
function save_message(data){
$.ajax({
type: "POST",
url: "{:url('Gatewayworker/save_message')}",
dataType: "json",
data:{data},
success: function(res) {
}
})
}
</script>
</body>
</html>
后台代码:
<?php
/*
* @Author: 杨某人
* @Date: 2022-06-29 14:51:20
* @LastEditTime: 2022-07-29 09:12:55
* @Description: file content
* @FilePath: \WWW\hebing.com\application\index\controller\Gatewayworker.php
*/
namespace app\index\controller;
use think\Db;
use think\Controller;
class Gatewayworker extends Controller
{
//聊天页面
public function index(){
$input = input('get.');
//fromid 发送者id toid 接收者id
$from = db('admin')->where('id',$input['fromid'])->field('id,name,toux')->find();
$to = db('admin')->where('id',$input['toid'])->field('id,name,toux')->find();
//聊天记录 (不完美,除了二者的聊天记录别人的只要满足条件的也会显示出来)
$chat_record = db('chat_record')
->where(['fromid'=>$input['fromid'],'toid'=>$input['toid']])
->whereOr(['fromid'=>$input['toid'],'toid'=>$input['fromid']])
->page(1,10)
->order('chat_record_id')
->select();
// halt($chat_record);
foreach($chat_record as $k => $v){
$chat_record[$k]['add_time'] = date('Y-m-d H:i:s',$v['add_time']);
}
$this->assign(['from'=>$from,'to'=>$to,'chat_record'=>$chat_record]);
return $this->fetch();
}
//保存聊天数据
public function save_message(){
$input = input('post.data');
$data=[
'fromid'=>$input['fromid'],
'fromname'=>$this->getname($input['fromid']),//获取发送者name
'toid'=>$input['toid'],
'toname'=>$this->getname($input['toid']),//获取接收者name
'content'=>$input['data'],
'add_time'=>$input['time'],
'isread'=>$input['isread'],//消息是否在线(1在线 2下线)
'type'=>1,//消息类型(1文本消息 2图片 )
];
db('chat_record')->insert($data);
}
//获取发送者或接收者name
public function getname($id){
$data = db('admin')->where('id',$id)->field('id,name')->find();
return $data['name'];
}
}
Events.php里面的代码:
<?php
/*
* @Author: 杨某人
* @Date: 2022-06-29 15:01:00
* @LastEditTime: 2022-07-28 17:00:48
* @Description: file content
* @FilePath: \WWW\hebing.com\vendor\GatewayWorker\Applications\YourApp\Events.php
*/
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
//declare(ticks=1);
use \GatewayWorker\Lib\Gateway;
/**
* 主逻辑
* 主要是处理 onConnect onMessage onClose 三个方法
* onConnect 和 onClose 如果不需要可以不用实现并删除
*/
/修改了这个文件一定要重启,否则不起作用
class Events
{
/**
* 当客户端连接时触发
* 如果业务不需此回调可以删除onConnect
*
* @param int $client_id 连接id
*/
public static function onConnect($client_id)
{
// 向当前client_id发送数据
// Gateway::sendToClient($client_id, "Hello $client_id\r\n");
// 向所有人发送
// Gateway::sendToAll("$client_id login\r\n");
//向客户端client_id发送$send_data数据。如果client_id对应的客户端不存在或者不在线则自动丢弃发送数据
Gateway::sendToClient($client_id,json_encode([
'type'=>'init',
'client_id'=>$client_id,
]));
}
/**
* 当客户端发来消息时触发
* @param int $client_id 连接id
* @param mixed $message 具体消息
*/
public static function onMessage($client_id, $message)
{
//把前台发送过来的字符串转成数组
$message_data = json_decode($message,true);
if(!$message_data){
return;
}
switch($message_data['type']){
//绑定id
case 'bind':
//为啥这个绑定不在一开始连接时触发的时候绑定,因为是一开始才会触发的绑定,给别人发的话还得刷新才行,所以就在发消息的时候绑定id
//将client_id与uid绑定,以便通过Gateway::sendToUid($uid)发送数据,通过Gateway::isUidOnline($uid)用户是否在线。
//uid解释:这里uid泛指用户id或者设备id,用来唯一确定一个客户端用户或者设备。
Gateway::bindUid($client_id,$message_data['fromid']);
return;
case 'online':
//初次加载 判断接收者是否在线
$status = Gateway::isUidOnline($message_data['toid']);
$data=[
'type'=>'online',
'status'=>$status,
];
// 向uid绑定的所有在线client_id发送数据。
// 注意:默认uid与client_id是一对多的关系,如果当前uid下绑定了多个client_id,则多个client_id对应的客户端都会收到消息,这类似于PC QQ和手机QQ同时在线接收消息。
//给发送者发送过去告诉他接收者是否在线
Gateway::sendToUid($message_data['fromid'],json_encode($data));
return;
//发送消息 数据保存
case 'say';
$data = [
'type' => 'text',//文本类型
'data' => $message_data['data'],//发送内容
'fromid'=>$message_data['fromid'],//发送者id
'toid'=>$message_data['toid'],//接收者id
'time'=>time(),//当前时间
];
//判断接收者是否在线 //发送消息更新在线状态
if(Gateway::isUidOnline($message_data['toid'])){
$data['isread'] = 2;//在线
// 向uid绑定的所有在线client_id发送数据。
// 注意:默认uid与client_id是一对多的关系,如果当前uid下绑定了多个client_id,则多个client_id对应的客户端都会收到消息,这类似于PC QQ和手机QQ同时在线接收消息。
Gateway::sendToUid($message_data['toid'],json_encode($data));
}else{
$data['isread'] = 1;//不在线
}
//类型设置为保存
$data['type']='save';
//告诉发送者 保存聊天数据 从来保证数据持久化 (workerman官方建议这里只写发送消息逻辑,所以保存到数据库交给前台ajax来)
Gateway::sendToUid($message_data['fromid'],json_encode($data));
// 向uid绑定的所有在线client_id发送数据。
// 注意:默认uid与client_id是一对多的关系,如果当前uid下绑定了多个client_id,则多个client_id对应的客户端都会收到消息,这类似于PC QQ和手机QQ同时在线接收消息。
// Gateway::sendToUid($message_data['toid'],json_encode($data));
// 向所有人发送
// Gateway::sendToAll(json_encode($data));
return;
}
// 向所有人发送
// Gateway::sendToAll("$client_id said $message\r\n");
}
/**
* 当用户断开连接时触发
* @param int $client_id 连接id
*/
public static function onClose($client_id)
{
// 向所有人发送
// GateWay::sendToAll("$client_id logout\r\n");
}
}