• PHP Swoole实现简易聊天室,附加小程序端连接websocket简易代码


    目录

    用到的工具:

    PHP Swoole拓展  |  PHP Redis拓展  | Redis 7

     一、安装上述必要工具(下面是以宝塔面板中操作为例)

    给PHP安装Swoole和Redis拓展:

    安装Redis软件

     二、创建websocket服务器文件"wss_server.php"

    具体看代码后面注释,主要注意点:

     三、通过php命令来运行服务端文件

     四、创建前端的chat.php文件

    具体看代码后面注释,主要注意点:

    附:小程序端连接websocket代码(需要将上面的服务端调整为只发给指定fd用户才行)


    Sham为了实现小程序中福利票券核销扫码时,用户能实时更新票券被核销的信息,所以学习使用websocket,以下通过搭建一个简单聊天室来记录备忘。

    用到的工具:

    PHP Swoole拓展  |  PHP Redis拓展  | Redis 7

     首先安装上述必要工具(下面是以宝塔面板中操作为例)

    给PHP安装Swoole和Redis拓展:

    找到PHP软件,进入“设置”,找到“安装拓展”,找到redis和Swoole4,点右面的安装,等待安装结束

    这里要注意系统默认的php版本是哪个,那么就安装哪个版本对应的拓展,查看php版本命令:

    php -v

     安装Redis软件

    如果用的宝塔面板,直接搜索Redis,然后安装等待结束即可

     下面就开始创建websocket服务器文件"wss_server.php"

    具体看代码后面注释,主要注意点:
    • Swoole用户连接后会生成唯一的fd(int格式的),需要缓存用户数据
    • 这里通过Redis来缓存websocket连接用户的数据,然后再后面调取
      这里Sham用的Redis的哈希表数据,通过hSet存储,然后hGet获取,
    • 这段代码是用于聊天室功能,用户发送消息后,会推送给发送者以外的其他所有在线用户。
    • 里面user_id,message需要和后面的chat.php对应
    1. use Swoole\WebSocket\Server;
    2. use Swoole\WebSocket\Frame;
    3. // 创建 Redis 客户端实例
    4. $redis = new Redis();
    5. $redis->connect('127.0.0.1', 6379); //端口号见Redis软件设置,默认是6379
    6. $redis->auth('xxxx'); //这里如果redis设置里密码,则增加这项
    7. // 创建 WebSocket 服务器
    8. $ws = new Server("0.0.0.0", 8040);
    9. // 设置服务器配置
    10. $ws->set([
    11. 'heartbeat_check_interval' => 60,
    12. 'heartbeat_idle_time' => 600,
    13. ]);
    14. // 监听 Worker 启动事件
    15. $ws->on('WorkerStart', function (Server $server, int $workerId) {
    16. // 可以在这里做一些初始化工作
    17. });
    18. // 监听连接事件
    19. $ws->on('open', function (Server $server, $request) use (&$redis) {
    20. // 当新用户连接时,记录用户信息
    21. $user_id = $request->get['user_id'] ?? null;
    22. $fd = $request->fd;
    23. if ($user_id) {
    24. // 将用户 ID 和 fd 存储到 Redis 哈希表
    25. //可以理解为类似数组中,online_users表示数组名称,$request->fd 表示key值,$user_id表示value值
    26. $redis->hSet('online_users', $request->fd, $user_id);
    27. //在服务器后端显示哪个用户上线了
    28. $msgs = [
    29. "from_user" => $user_id, //显示消息是来自哪个用户
    30. "messages"=>"我上线啦!"
    31. ];
    32. //给所有用户广播谁上线了
    33. broadcast($server,$redis, json_encode($msgs),$request->fd);
    34. //var_dump($redis);
    35. } else {
    36. //echo "未提供用户 ID,fd: $fd\n";
    37. }
    38. });
    39. // 监听消息事件
    40. $ws->on('message', function (Server $server, Frame $frame) use (&$redis) {
    41. //将收到的数据进行转换
    42. $data = json_decode($frame->data, true);
    43. if (isset($data['message'])) {
    44. $msgs = [
    45. //从哈希表中获取用户
    46. "from_user" => $redis->hGet('online_users',$frame->fd), //显示消息是来自哪个用户
    47. "messages"=>$data['message']
    48. ];
    49. // 当用户发送消息时,广播给所有在线用户
    50. broadcast($server,$redis, json_encode($msgs), $frame->fd);
    51. }
    52. });
    53. // 监听关闭事件
    54. $ws->on('close', function (Server $server, $fd) use (&$redis) {
    55. // 当用户断开连接时,从在线用户列表中移除该用户
    56. $msgs = [
    57. "from_user" => $redis->hGet('online_users',$fd), //显示消息是来自哪个用户
    58. "messages"=>"我下线啦!"
    59. ];
    60. //从redis的哈希表中删除用户
    61. $redis->hDel('online_users', $fd);
    62. // 用户离线时,广播给所有在线用户
    63. broadcast($server,$redis, json_encode($msgs),$fd);
    64. });
    65. // 广播消息给所有在线用户
    66. function broadcast(Server $server,$redis, $msg, $excludeFd = null) {
    67. // 从redis的哈希表中获取所有在线用户的 FD
    68. $onlineUsers = $redis->hKeys('online_users');
    69. // 遍历在线用户并发送消息
    70. foreach ($onlineUsers as $fds) {
    71. //判断不是当前用户,同时用户在线时推送信息
    72. if ($fds != $excludeFd && $server->isEstablished($fds)) {
    73. //给对应fds用户发送消息
    74. $server->push($fds, $msg);
    75. }
    76. }
    77. }
    78. // 启动服务器
    79. $ws->start();

    创建完"wss_server.php",然后我们需要通过php命令来运行它

    进入ssh或者创建定时任务执行shell都可以,运行如下命令,假设"wss_server.php"文件存放在"www/wwwroot"目录下

    php /www/wwwroot/wss_server.php

    如果没有报错等信息,则表示运行成功了

    下面创建前端的chat.php文件

    具体看代码后面注释,主要注意点:
    1. 为了防止因为空闲而导致与websocket服务器断开连接,需要设置心跳和重连(见代码)
    2. 这里因为是简易版,所有通过网址传参来通过user_id定于用户名,以区分用户,可修改其他方式,比如登录来获取
    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 简易聊天By websocket
    6. 简易聊天By websocket

    7. "dialogue">

    完成上面的文件,通过http://aa.com/chat.php?userid=xxx来访问上面的前端,其中xxx表示用户名,如果显示“已连接服务器”,则表示已经成功了,然后就可以发给别人来小小的聊下天啦。

    注意:这个人员&聊天数据都是暂存的,关掉前端 或者重启websocket服务端都会导致数据重置清空。

    写在结尾:

    初次结束websocket和swoole,目前只能实现简单信息发送,暂时能满足小程序中需求,后续用到更多功能的时候再来学习。

    附:小程序端连接websocket代码(需要将上面的服务端调整为只发给指定fd用户才行)

    1. let use_socket=true; //这个是用来判断是否需要连接websocket的
    2. let socket; // WebSocket 连接对象
    3. let heartBeatTimer; // 心跳计时器
    4. let reconnectTimer; // 重连计时器
    5. const HEARTBEAT_INTERVAL = 15000; // 心跳间隔时间(15秒)
    6. const RECONNECT_INTERVAL = 1000; // 重连间隔时间(5秒)
    7. Page({
    8. data: {
    9. },
    10. // 初始化 WebSocket 连接
    11. connectWebSocket() {
    12. var that =this;
    13. //定义socket来给后面使用
    14. socket = wx.connectSocket({
    15. url: 'wss://xxx.com?&user_id='+user_info[0].user_id, // 替换为你的 WebSocket 服务器地址,小程序需要通过wss连接,可以通过前面提到的ssl证书域名反代访问
    16. success() {
    17. console.log('WebSocket 连接成功');
    18. },
    19. fail(err) {
    20. console.error('WebSocket 连接失败:', err);
    21. that.reconnectWebSocket(); // 尝试重连
    22. }
    23. });
    24. // 监听 WebSocket 连接成功
    25. socket.onOpen(() => {
    26. var that=this;
    27. console.log('WebSocket 已打开');
    28. that.startHeartbeat(); // 开始心跳
    29. });
    30. // 监听 WebSocket 连接关闭
    31. socket.onClose(() => {
    32. var that=this;
    33. console.log('WebSocket 已关闭');
    34. //当断开后任允许连接的话,则重连(这里Sham是发现不加判断的话,退出当前页之后还是会自动重连socket)
    35. if(use_socket){
    36. that.stopHeartbeat(); // 停止心跳
    37. that.reconnectWebSocket(); // 尝试重连
    38. }
    39. });
    40. // 监听 WebSocket 错误
    41. socket.onError((err) => {
    42. var that=this;
    43. console.error('WebSocket 错误:', err);
    44. that.reconnectWebSocket(); // 尝试重连
    45. });
    46. // 监听 WebSocket 消息
    47. socket.onMessage((message) => {
    48. var datas = JSON.parse(message.data);
    49. //console.log(message) //保险点打印数据来核对是否符合要求
    50. var that = this;
    51. //这里要确保发送的消息是json字符串格式的,不然会报错
    52. var wss_res = JSON.parse(datas.messages);
    53. if(wss_res.id !=null && wss_res.status !=null){
    54. //这里以福利票券为例
    55. var welfare_list = that.data.welfare_list;
    56. //通过filter来筛选出指定id的票券
    57. var show_qrcode_welfare = welfare_list.filter(item => item.id == wss_res.id)
    58. var msg = show_qrcode_welfare[0].welfare_name
    59. //弹出提醒对应票券已经被扫码核销
    60. wx.showToast({
    61. title: msg+' - 已被扫码!',
    62. icon:'none',
    63. duration:2500
    64. })
    65. //刷新数据(会从服务器重新获取数据)
    66. setTimeout(function () {
    67. that.onShow()
    68. }, 2500)
    69. }
    70. console.log(JSON.parse(message.data));
    71. // 处理收到的消息
    72. });
    73. },
    74. // 开始心跳
    75. startHeartbeat() {
    76. var that=this;
    77. that.stopHeartbeat(); // 防止多次启动
    78. heartBeatTimer = setInterval(() => {
    79. if (socket && socket.readyState === socket.OPEN) {
    80. socket.send({
    81. data: JSON.stringify({ type: 'ping' }), // 发送心跳包
    82. success() {
    83. console.log('心跳包已发送');
    84. },
    85. fail(err) {
    86. console.error('心跳包发送失败:', err);
    87. that.reconnectWebSocket(); // 如果发送失败,尝试重连
    88. }
    89. });
    90. }
    91. }, HEARTBEAT_INTERVAL);
    92. },
    93. // 停止心跳
    94. stopHeartbeat() {
    95. if (heartBeatTimer) {
    96. clearInterval(heartBeatTimer);
    97. heartBeatTimer = null;
    98. }
    99. },
    100. // 尝试重连
    101. reconnectWebSocket() {
    102. var that=this;
    103. if (reconnectTimer) return; // 防止重复重连
    104. reconnectTimer = setTimeout(() => {
    105. console.log('尝试重新连接 WebSocket');
    106. that.connectWebSocket(); // 重新建立连接
    107. reconnectTimer = null; // 重置重连计时器
    108. }, RECONNECT_INTERVAL);
    109. },
    110. /**
    111. * 生命周期函数--监听页面加载
    112. */
    113. onLoad(options) {
    114. var that = this;
    115. that.connectWebSocket();
    116. }
    117. },
    118. /**
    119. * 生命周期函数--监听页面初次渲染完成
    120. */
    121. onReady() {
    122. },
    123. /**
    124. * 生命周期函数--监听页面显示
    125. */
    126. onShow() {
    127. var that = this;
    128. //页面显示后就执行心跳
    129. that.startHeartbeat();
    130. /**
    131. * 生命周期函数--监听页面隐藏
    132. */
    133. onHide() {
    134. //页面隐藏时停止心跳并设置不再连接socket
    135. that.stopHeartbeat();
    136. clearInterval(heartBeatTimer);
    137. heartBeatTimer = null;
    138. use_socket=false
    139. wx.closeSocket({
    140. success() {
    141. console.log('WebSocket连接关闭成功');
    142. },
    143. fail(error) {
    144. console.error('WebSocket连接关闭失败', error);
    145. }
    146. });
    147. },
    148. /**
    149. * 生命周期函数--监听页面卸载
    150. */
    151. onUnload() {
    152. //页面销毁时停止心跳并设置不再连接socket
    153. var that=this;
    154. that.stopHeartbeat();
    155. clearInterval(heartBeatTimer);
    156. heartBeatTimer = null;
    157. use_socket=false
    158. wx.closeSocket({
    159. success() {
    160. console.log('WebSocket连接关闭成功');
    161. },
    162. fail(error) {
    163. onsole.error('WebSocket连接关闭失败', error);
    164. }
    165. });
    166. },
    167. /**
    168. * 页面相关事件处理函数--监听用户下拉动作
    169. */
    170. onPullDownRefresh() {
    171. },
    172. /**
    173. * 页面上拉触底事件的处理函数
    174. */
    175. onReachBottom() {
    176. },
    177. /**
    178. * 用户点击右上角分享
    179. onShareAppMessage() {
    180. }*/
    181. })

  • 相关阅读:
    10.12广州见 | 第十六届智慧城市大会报名通道全面开启
    基于 Github 平台的 .NET 开源项目模板. 嘎嘎实用!
    【主流技术】ElasticSearch 在 Spring 项目中的实践
    GO sync.Map Store、Delete 、Load 、Range等方法使用举例
    Centos7下安装MySQL详细步骤
    基于Java+Springboot+vue体育用品销售商城平台设计和实现
    Apache Hive安装部署详细图文教程
    不要忽视web渗透测试在项目中起到的重要性
    OllamaFunctions 学习笔记
    划词标注或打标签的实现方案
  • 原文地址:https://blog.csdn.net/shamqu/article/details/142304735