一、程序目录结构

二、代码展示

附LayIM开发文档:https://www.layui.com/doc/modules/layim.html

1、前端代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>websocket连接</title>
  6. <link rel="stylesheet" href="./layui/css/layui.css" media="all">
  7. <script src="./layui/jquery.min.js"></script>
  8. <script src="./layui/layui.js"></script>
  9. </head>
  10. <body style="background: url(./layui/images/irongrip.png) repeat">
  11.  
  12. </body>
  13. <script>
  14. layui.use('layim', function(layim){
  15. //基础配置
  16. layim.config({
  17. //获取主面板列表信息
  18. init: {
  19. url: "./getUserList.php?id=<?php echo $_GET['id']?>" //接口地址(返回的数据格式见下文)
  20. ,type: 'get' //默认get,一般可不填
  21. ,data: {} //额外参数
  22. }
  23. //获取群员接口
  24. ,members: {
  25. url: "./getMembers.php" //接口地址(返回的数据格式见下文)
  26. ,type: 'get' //默认get,一般可不填
  27. ,data: {} //额外参数
  28. },
  29. uploadFile: {
  30. url: ""
  31. }
  32. ,uploadImage: {
  33. url: ""
  34. }
  35. ,brief: false //是否简约模式(默认false,如果只用到在线客服,且不想显示主面板,可以设置 true)
  36. ,title: '我的LayIM' //主面板最小化后显示的名称
  37. ,maxLength: //最长发送的字符长度,默认3000
  38. ,isfriend: true //是否开启好友(默认true,即开启)
  39. ,isgroup: true //是否开启群组(默认true,即开启)
  40. ,right: '0px' //默认0px,用于设定主面板右偏移量。该参数可避免遮盖你页面右下角已经的bar。
  41. ,chatLog: "" //聊天记录地址(如果未填则不显示)
  42. ,find: "" //查找好友/群的地址(如果未填则不显示)
  43. ,copyright: false //是否授权,如果通过官网捐赠获得LayIM,此处可填true
  44. });
  45.  
  46. var socket = new WebSocket("ws://127.0.0.1:7272");
  47. socket.onopen = function(){
  48. <?php
  49.  
  50. $id = $_GET['id'];
  51. $conn = @mysql_connect("127.0.0.1", "root", "root") or die("连接数据库失败");
  52. mysql_select_db("layim");
  53. $rows = mysql_query("select * from snake_chatuser where id = $id");
  54. $info = array();
  55. while ($row = mysql_fetch_array($rows)) {
  56. $info = $row;
  57. }
  58. ?>
  59. var id = '<?php echo $info['id'];?>';
  60. var username = '<?php echo $info['username'];?>';
  61. var avatar = '<?php echo $info['avatar'];?>';
  62. var sign = '<?php echo $info['sign'];?>';
  63. //登录
  64. var login_data = '{"type":"init","id":"'+id+'","username":"'+username+'","avatar":"'+avatar+'","sign":"'+sign+'"}';
  65. socket.send( login_data );
  66. console.log("websocket握手成功!");
  67. };
  68.  
  69. //监听收到的消息
  70. socket.onmessage = function(res){
  71. // console.log(res);
  72. var data = eval("("+res.data+")");
  73. switch(data['message_type']){
  74. // 服务端ping客户端
  75. case 'ping':
  76. socket.send('{"type":"ping"}');
  77. break;
  78. // 登录 更新用户列表
  79. case 'init':
  80. //console.log(data['id']+"登录成功");
  81. //layim.getMessage(res.data); //res.data即你发送消息传递的数据(阅读:监听发送的消息)
  82. break;
  83. //添加 用户
  84. case 'addUser':
  85. //console.log(data.data);
  86. layim.addList(data.data);
  87. break;
  88. //删除 用户
  89. case 'delUser':
  90. layim.removeList({
  91. type: 'friend'
  92. ,id: data.data.id //好友或者群组ID
  93. });
  94. break;
  95. // 添加 分组信息
  96. case 'addGroup':
  97. // console.log(data.data);
  98. layim.addList(data.data);
  99. break;
  100. case 'delGroup':
  101. layim.removeList({
  102. type: 'group'
  103. ,id: data.data.id //好友或者群组ID
  104. });
  105. break;
  106. // 检测聊天数据
  107. case 'chatMessage':
  108. //console.log(data.data);
  109. layim.getMessage(data.data);
  110. break;
  111. // 离线消息推送
  112. case 'logMessage':
  113. console.log();
  114. setTimeout(function(){layim.getMessage(data.data)}, );
  115. break;
  116. // 用户退出 更新用户列表
  117. case 'logout':
  118. break;
  119. //聊天还有不在线
  120. case 'ctUserOutline':
  121. console.log('');
  122. //layer.msg('好友不在线', {'time' : 1000});
  123. break;
  124.  
  125. }
  126. };
  127.  
  128. //layim建立就绪
  129. layim.on('ready', function(res){
  130.  
  131. layim.on('sendMessage', function(res){
  132. console.log(res);
  133. // 发送消息
  134. var mine = JSON.stringify(res.mine);
  135. var to = JSON.stringify(res.to);
  136. var login_data = '{"type":"chatMessage","data":{"mine":'+mine+', "to":'+to+'}}';
  137. socket.send( login_data );
  138.  
  139. });
  140. });
  141. });
  142.  
  143. </script>
  144. </html>

2、后台核心代码

  1. <?php
  2. use \GatewayWorker\Lib\Gateway;
  3. use \GatewayWorker\Lib\Db;
  4. /**
  5. * 主逻辑
  6. * 主要是处理 onConnect onMessage onClose 三个方法
  7. * onConnect 和 onClose 如果不需要可以不用实现并删除
  8. */
  9. class Events
  10. {
  11. public static function onConnect($client_id) {
  12.  
  13. }
  14. /**
  15. * 当客户端发来消息时触发
  16. * @param int $client_id 连接id
  17. * @param mixed $message 具体消息
  18. */
  19. public static function onMessage($client_id, $data) {
  20. $message = json_decode($data, true);
  21. $message_type = $message['type'];
  22. switch($message_type) {
  23. case 'init':
  24. // uid
  25. $uid = $message['id'];
  26. // 设置session
  27. $_SESSION = [
  28. 'username' => $message['username'],
  29. 'avatar' => $message['avatar'],
  30. 'id' => $uid,
  31. 'sign' => $message['sign']
  32. ];
  33.  
  34. // 将当前链接与uid绑定
  35. Gateway::bindUid($client_id, $uid);
  36. // 通知当前客户端初始化
  37. $init_message = array(
  38. 'message_type' => 'init',
  39. 'id' => $uid,
  40. );
  41. Gateway::sendToClient($client_id, json_encode($init_message));
  42.  
  43. //查询最近1周有无需要推送的离线信息
  44. $db1 = Db::instance('db1'); //数据库链接
  45. $time = time() - 7 * 3600 * 24;
  46. $resMsg = $db1->select('id,fromid,fromname,fromavatar,timeline,content')->from('snake_chatlog')
  47. ->where("toid= {$uid} and timeline > {$time} and type = 'friend' and needsend = 1" )
  48. ->query();
  49. //var_export($resMsg);
  50. if( !empty( $resMsg ) ){
  51.  
  52. foreach( $resMsg as $key=>$vo ){
  53.  
  54. $log_message = [
  55. 'message_type' => 'logMessage',
  56. 'data' => [
  57. 'username' => $vo['fromname'],
  58. 'avatar' => $vo['fromavatar'],
  59. 'id' => $vo['fromid'],
  60. 'type' => 'friend',
  61. 'content' => htmlspecialchars( $vo['content'] ),
  62. 'timestamp'=> $vo['timeline'] * 1000,
  63. ]
  64. ];
  65.  
  66. Gateway::sendToUid( $uid, json_encode($log_message) );
  67.  
  68. //设置推送状态为已经推送
  69. $db1->query("UPDATE `snake_chatlog` SET `needsend` = '0' WHERE id=" . $vo['id']);
  70.  
  71. }
  72. }
  73.  
  74. //查询当前的用户是在哪个分组中,将当前的链接加入该分组
  75. $ret = $db1->query("select `groupid` from `snake_groupdetail` where `userid` = {$uid} group by `groupid`");
  76. if( !empty( $ret ) ){
  77. foreach( $ret as $key=>$vo ){
  78. Gateway::joinGroup($client_id, $vo['groupid']); //将登录用户加入群组
  79. }
  80. }
  81. unset( $ret );
  82. return;
  83. break;
  84. case 'addUser' :
  85. //添加用户
  86. $add_message = [
  87. 'message_type' => 'addUser',
  88. 'data' => [
  89. 'type' => 'friend',
  90. 'avatar' => $message['data']['avatar'],
  91. 'username' => $message['data']['username'],
  92. 'groupid' => $message['data']['groupid'],
  93. 'id' => $message['data']['id'],
  94. 'sign' => $message['data']['sign']
  95. ]
  96. ];
  97. Gateway::sendToAll( json_encode($add_message), null, $client_id );
  98. return;
  99. break;
  100. case 'delUser' :
  101. //删除用户
  102. $del_message = [
  103. 'message_type' => 'delUser',
  104. 'data' => [
  105. 'type' => 'friend',
  106. 'id' => $message['data']['id']
  107. ]
  108. ];
  109. Gateway::sendToAll( json_encode($del_message), null, $client_id );
  110. return;
  111. break;
  112. case 'addGroup':
  113. //添加群组
  114. $uids = explode( ',', $message['data']['uids'] );
  115. $client_id_array = [];
  116. foreach( $uids as $vo ){
  117. $ret = Gateway::getClientIdByUid( $vo ); //当前组中在线的client_id
  118. if( !empty( $ret ) ){
  119. $client_id_array[] = $ret['0'];
  120.  
  121. Gateway::joinGroup($ret['0'], $message['data']['id']); //将这些用户加入群组
  122. }
  123. }
  124. unset( $ret, $uids );
  125.  
  126. $add_message = [
  127. 'message_type' => 'addGroup',
  128. 'data' => [
  129. 'type' => 'group',
  130. 'avatar' => $message['data']['avatar'],
  131. 'id' => $message['data']['id'],
  132. 'groupname' => $message['data']['groupname']
  133. ]
  134. ];
  135. Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id );
  136. return;
  137. break;
  138. case 'joinGroup':
  139. //加入群组
  140. $uid = $message['data']['uid'];
  141. $ret = Gateway::getClientIdByUid( $uid ); //若在线实时推送
  142. if( !empty( $ret ) ){
  143. Gateway::joinGroup($ret['0'], $message['data']['id']); //将该用户加入群组
  144.  
  145. $add_message = [
  146. 'message_type' => 'addGroup',
  147. 'data' => [
  148. 'type' => 'group',
  149. 'avatar' => $message['data']['avatar'],
  150. 'id' => $message['data']['id'],
  151. 'groupname' => $message['data']['groupname']
  152. ]
  153. ];
  154. Gateway::sendToAll( json_encode($add_message), [$ret['0']], $client_id ); //推送群组信息
  155. }
  156.  
  157. return;
  158. break;
  159. case 'addMember':
  160. //添加群组成员
  161. $uids = explode( ',', $message['data']['uid'] );
  162. $client_id_array = [];
  163. foreach( $uids as $vo ){
  164. $ret = Gateway::getClientIdByUid( $vo ); //当前组中在线的client_id
  165. if( !empty( $ret ) ){
  166. $client_id_array[] = $ret['0'];
  167.  
  168. Gateway::joinGroup($ret['0'], $message['data']['id']); //将这些用户加入群组
  169. }
  170. }
  171. unset( $ret, $uids );
  172.  
  173. $add_message = [
  174. 'message_type' => 'addGroup',
  175. 'data' => [
  176. 'type' => 'group',
  177. 'avatar' => $message['data']['avatar'],
  178. 'id' => $message['data']['id'],
  179. 'groupname' => $message['data']['groupname']
  180. ]
  181. ];
  182. Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id ); //推送群组信息
  183. return;
  184. break;
  185. case 'removeMember':
  186. //将移除群组的成员的群信息移除,并从讨论组移除
  187. $ret = Gateway::getClientIdByUid( $message['data']['uid'] );
  188. if( !empty( $ret ) ){
  189.  
  190. Gateway::leaveGroup($ret['0'], $message['data']['id']);
  191.  
  192. $del_message = [
  193. 'message_type' => 'delGroup',
  194. 'data' => [
  195. 'type' => 'group',
  196. 'id' => $message['data']['id']
  197. ]
  198. ];
  199. Gateway::sendToAll( json_encode($del_message), [$ret['0']], $client_id );
  200. }
  201.  
  202. return;
  203. break;
  204. case 'delGroup':
  205. //删除群组
  206. $del_message = [
  207. 'message_type' => 'delGroup',
  208. 'data' => [
  209. 'type' => 'group',
  210. 'id' => $message['data']['id']
  211. ]
  212. ];
  213. Gateway::sendToAll( json_encode($del_message), null, $client_id );
  214. return;
  215. break;
  216. case 'chatMessage':
  217. $db1 = Db::instance('db1'); //数据库链接
  218. // 聊天消息
  219. $type = $message['data']['to']['type'];
  220. $to_id = $message['data']['to']['id'];
  221. $uid = $message['data']['mine']['id'];
  222.  
  223. $chat_message = [
  224. 'message_type' => 'chatMessage',
  225. 'data' => [
  226. 'username' => $message['data']['mine']['username'],
  227. 'avatar' => $message['data']['mine']['avatar'],
  228. 'id' => $type === 'friend' ? $uid : $to_id,
  229. 'type' => $type,
  230. 'content' => htmlspecialchars($message['data']['mine']['content']),
  231. 'timestamp'=> time()*1000,
  232. ]
  233. ];
  234. //聊天记录数组
  235. $param = [
  236. 'fromid' => $uid,
  237. 'toid' => $to_id,
  238. 'fromname' => $message['data']['mine']['username'],
  239. 'fromavatar' => $message['data']['mine']['avatar'],
  240. 'content' => htmlspecialchars($message['data']['mine']['content']),
  241. 'timeline' => time(),
  242. 'needsend' => 0
  243. ];
  244. switch ($type) {
  245. // 私聊
  246. case 'friend':
  247. // 插入
  248. $param['type'] = 'friend';
  249. if( empty( Gateway::getClientIdByUid( $to_id ) ) ){
  250. $param['needsend'] = 1; //用户不在线,标记此消息推送
  251. }
  252. $db1->insert('snake_chatlog')->cols( $param )->query();
  253. return Gateway::sendToUid($to_id, json_encode($chat_message));
  254. // 群聊
  255. case 'group':
  256. $param['type'] = 'group';
  257. $db1->insert('snake_chatlog')->cols( $param )->query();
  258. return Gateway::sendToGroup($to_id, json_encode($chat_message), $client_id);
  259. }
  260. return;
  261. break;
  262. case 'hide':
  263. case 'online':
  264. $status_message = [
  265. 'message_type' => $message_type,
  266. 'id' => $_SESSION['id'],
  267. ];
  268. $_SESSION['online'] = $message_type;
  269. Gateway::sendToAll(json_encode($status_message));
  270. return;
  271. break;
  272. case 'ping':
  273. return;
  274. default:
  275. echo "unknown message $data" . PHP_EOL;
  276. }
  277. }
  278.  
  279. /**
  280. * 当用户断开连接时触发
  281. * @param int $client_id 连接id
  282. */
  283. public static function onClose($client_id) {
  284. $logout_message = [
  285. 'message_type' => 'logout',
  286. 'id' => $_SESSION['id']
  287. ];
  288. Gateway::sendToAll(json_encode($logout_message));
  289. }
  290. }

三、页面效果图

有需要了解的加QQ:2575404985,可以兼容linux与windows

在linux下运行方式,在workerman目录下创建start.sh,代码如下:

  1. #!/bin/bash
  2. php ./Applications/start_register.php start &
  3. php ./Applications/start_gateway.php start &
  4. php ./Applications/start_businessworker.php start &

四、案例

GatewayWorker + LayIM实现即时聊天的更多相关文章

  1. 使用GatewayWorker 开发个即时聊天demo

    前言: 上手册以示尊重:https://www.kancloud.cn/walkor/gateway-worker/326138: https://www.cnblogs.com/fuqiang88/ ...

  2. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  3. Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  4. 即时聊天IM之三 XMPP协议客户端库的和Android端框架概述

    合肥程序员群:49313181.    合肥实名程序员群:128131462 (不愿透露姓名和信息者勿加入) Q  Q:408365330     E-Mail:egojit@qq.com smack ...

  5. 即时聊天IM之二 openfire 整合现有系统用户

    合肥程序员群:49313181.    合肥实名程序员群:128131462 (不愿透露姓名和信息者勿加入) Q  Q:408365330     E-Mail:egojit@qq.com  综述: ...

  6. 即时聊天 / XMPP

    MQTT是第二个即时聊天协议(了解) 5.即时通讯 即时通讯网上有第三方的解决方案,比如环信,融云等.我们是自己搭的xmpp服务器,服务器使用的tigase,之前写过相关的博客,自己去年也做了对应的w ...

  7. 7.xmpp版即时聊天

    即时聊天的解决方案 socket: xmpp:xmpp+openfire+asmack 环信 常见协议 比较安全,tcp上还加了俩层 简单聊一下socket socket:套接字,连接需要ip和端口, ...

  8. 类似QQ的应用毗邻(Pilin)即时聊天源码

      这个应用是从安卓教程网分享过了的,个人觉得这个还是挺不错的,毗邻(Pilin)即时聊天应用源码,承诺的 基于xmpp openfire asmack 的即时聊天应用,继续完善,现在只完成了文字.表 ...

  9. 高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

    上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输 ...

随机推荐

  1. 一个简单的js队列,逻辑很清晰

    function Queue(type) { //type 是否是一个接着一个执行 function QueueConst() {} QueueConst.execute_ing=[], QueueC ...

  2. CHAPTER 1 Architectural Overview of Oracle Database 11g

    Which SGA structures are required, and which are optional? The database buffer cache, log buffer, an ...

  3. OSX: node中安装zeromq

    1. brew install pkg-config2. brew install zmq3. export PKG_CONFIG_PATH="/usr/local/lib/pkgconfi ...

  4. 条款39: 避免 "向下转换" 继承层次

    基类指针不能调用派生类的独有的成员,即使基类指针指向派生类对象,因为编译器是根据指针的静态类型来确定调用对象在内存中占据的空间的.此时可以使用static_cast来转换,但不要这么做,因为向下转换难 ...

  5. Ubuntu上面安装Redis Python

    Ubuntu上面安装Redis Python 1,下载redis源码https://redis.io/download,下载地址:http://124.205.69.169/files/A092000 ...

  6. zTree初体验(一)——小试牛刀

    zTree 是一个依靠 jQuery 实现的多功能 "树插件".优异的性能.灵活的配置.多种功能的组合是 zTree 最大长处. --zTree官网 zTree v3.0 将核心代 ...

  7. shell 切割文件

    [root@hadoop2 xiaole_chk_url]# cat looh.index.splitfile.sh loop_c=0loop_step=10001loop_tag=0str_head ...

  8. java多线程——饥饿和公平

    一.概念 饥饿:如果一个线程因为 CPU 时间全部被其他线程抢走而得不到 CPU 运行时间,这种状态被称之为“饥饿”: 二.饥饿原因 高优先级线程吞噬所有的低优先级线程的 CPU 时间. 线程被永久堵 ...

  9. c#用webkit内核支持html5

    [实例简介]经过测试可用 [实例截图] [核心代码] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using System; ...

  10. Opencv打开内置摄像头

    Opencv中VideoCapture是专门用来处理视频文件或者摄像头视频流的类,详细的说明和用法可以参考Opencv2.4.13的说明文档:点击打开链接 使用VideoCapture打开内置摄像头的 ...