配置
1,小程序域名必须是https
2,小程序的话证书必须要设置,因为是https的
(如果用的宝塔免费证书:证书路径是 /www/server/panel/vhost/ssl/站点名称/证书。如果前面路径跟我不一样的话必须找站点下面的那个证书,否则不能使呀)
3,阿里云安全组需要放行你用的端口(我开的是9501 - 9502),如果用的宝塔面板,宝塔上也需要在安全里面放行9501 - 9502端口
代码
1,放一下代码,tp5.1我在项目根目录放了一个这样文件(这样直接访问这个文件就可以运行服务了)
Socket.php(名字随便)
<?php
// [ 应用入口文件 ]
namespace think;
// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';
// 支持事先使用静态方法设置Request对象和Config对象
// 绑定到index模块 执行应用并响应
Container::get('app')->bind('socket/Notice')->run()->send();
5,Server代码(注释都在里面):
app/socket/controller/Index.php
<?php
namespace app\Socket\controller;
use think\Db;
class Index
{
private $server;
private $redis;
public function __construct()
{
// 创建服务,因为是小程序所以设置成,0.0.0.0 都可以访问的
$this->server = new \swoole_websocket_server("0.0.0.0", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
// 小程序需要设置证书
$this->server->set([
'ssl_cert_file'=>'/www/xxxxxxxxxxx/fullchain.pem',
'ssl_key_file'=>'/www/xxxxxxxxxxxxx/privkey.pem',
]);
// 建立连接
$this->server->on('Open', [$this, 'onOpen']);
// 发送消息
$this->server->on('Message', [$this, 'onMessage']);
// 关闭连接
$this->server->on('Close', [$this, 'onClose']);
$this->server->start();
}
/**
* 客户端连接
* @param [type] $server 服务端
* @param [type] $req 请求数据
* @return [type] [description]
*/
public function onOpen($server, $req)
{
// 使用 redis 存一下客户端ID,用来推送消息
// 可能有人要问为啥没有把实例化放在构造函数呢?
// 这个构造函数只是在开启服务的时候执行一次,如果放在构造函数的话时间稍微长一点,下面使用redis就会出现断开的情况。
// 不过可以改成redis长连接
$this->redis->connect('127.0.0.1', 6379);
// var_dump($req);
// 让前端打开连接的时候存一个 用户ID => 客户端ID,这样就可以推送
$id = $req->get['uid'];
$this->redis->hSet('clientId', $id, $req->fd);
echo "客户端:{$req->fd} 已连接 \n";
}
/**
* 消息推送
* @param [type] $server 服务端
* @param [type] $frame 请求数据
* @return [type] [description]
*/
public function onMessage($server, $frame)
{
// 数据解析成对象
$data = json_decode($frame->data);
// 内容名字和带哪些参数(比如:用户ID,对方的用户ID,头像,等等。。)是由前端决定的。最主要的就是自己的ID,对方的ID,和聊天内容
var_dump($data);
// uid => 用户ID
// tid => 对方用户ID
// cont => 聊天内容
// 判断用户是否在聊天页面上: 在 :推送内容,并将内容加入数据库
// 不在:直接将内容加入数据库,设置未读
$rr = $this->redis->hExists('clientId', $data->tid);
if($rr) $server->push($this->redis->hGet('clientId', $data->tid), $frame->data);
// 数据插入数据库
$this->chatInsert($data);
// 推送给自己
$server->push($frame->fd, $frame->data);
// 如果 A 和 B 在一个页面聊天,C 又给 A 发了一条消息,这样 A 和 B 的聊天列表里面,A 也会收到 C 发送的消息,因为 A 满足了'在线'的要求,所以在前端接收消息那里需要判断推送给自己的消息 tid 不等于 对方ID 的时候不显示,(uid == 自己登录的uid && tid == 对方的ID)
// 这样就知道只要你在聊天页面就能收到所有人的消息,就可以实现一个你在聊天的过程中,顶部弹出一个提示框 【孙某人给您发了一条消息】
}
/**
* 客户端关闭
* @param [type] $server [description]
* @param [type] $fd [description]
* @return [type] [description]
*/
public function onClose($server, $fd)
{
// 删除 redis 用户的客户端ID,百度了一下 hash 不能通过值获取键名所以全部获取循环获取键名吧。
$all = $this->redis->hGetAll('clientId');
if($all)
{
foreach ($all as $k => $v) {
if($v == $fd){
$this->redis->hDel('clientId', $k);
break;
}
}
}
echo "客户端:{$fd} 已关闭 \n";
}
/**
* 聊天记录插入数据库,并且更新聊天列表最后一次聊天
* @param [type] $data [description]
*/
public function chatInsert($data)
{
// 插入聊天记录和聊天列表,逻辑用自己的吧
// ——————————————————————————————————————————————————————
// 放我一个例子。
$time = time();
// 插入聊天记录
$insert = [
'cl_uid' => $data->uid,
'cl_tid' => $data->tid,
'cl_cont' => $data->cont,
'cl_time' => $time
];
Db::name('chats_log')->insert($insert);
// 插入聊天列表,更新最后一条消息用于显示在列表
Db::name('msg_log')
->where("(ml_tid = $data->tid AND ml_uid = $data->uid) OR (ml_tid = $data->uid AND ml_uid = $data->tid)")
->update(['ml_cont' => $data->cont, 'ml_time' => $time]);
//
// 比如用户表是 users,这样的联查就可以实现聊天列表了(users表里有用户头像,昵称,等等。。), 不过会有点慢
// us_id 是用户ID(亲测能用)
// $list = Db::name('msg_log')
// ->alias('a')
// ->join('users b', '(a.ml_uid <> 971 AND a.ml_uid = b.us_id) OR (a.ml_tid <> 971 AND a.ml_tid = b.us_id)')
// ->where('ml_uid', 971)
// ->whereOr('ml_pid', 971)
// ->select();
}
}
// 启动服务器
$server = new Index();