一、程序目录结构
二、代码展示
附LayIM开发文档:https://www.layui.com/doc/modules/layim.html
1、前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>websocket连接</title> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> <script src="./layui/jquery.min.js"></script> <script src="./layui/layui.js"></script> </head> <body style="background: url(./layui/images/irongrip.png) repeat"> </body> <script> layui.use('layim', function(layim){ //基础配置 layim.config({ //获取主面板列表信息 init: { url: "./getUserList.php?id=<?php echo $_GET['id']?>" //接口地址(返回的数据格式见下文) ,type: 'get' //默认get,一般可不填 ,data: {} //额外参数 } //获取群员接口 ,members: { url: "./getMembers.php" //接口地址(返回的数据格式见下文) ,type: 'get' //默认get,一般可不填 ,data: {} //额外参数 }, uploadFile: { url: "" } ,uploadImage: { url: "" } ,brief: false //是否简约模式(默认false,如果只用到在线客服,且不想显示主面板,可以设置 true) ,title: '我的LayIM' //主面板最小化后显示的名称 ,maxLength: 3000 //最长发送的字符长度,默认3000 ,isfriend: true //是否开启好友(默认true,即开启) ,isgroup: true //是否开启群组(默认true,即开启) ,right: '0px' //默认0px,用于设定主面板右偏移量。该参数可避免遮盖你页面右下角已经的bar。 ,chatLog: "" //聊天记录地址(如果未填则不显示) ,find: "" //查找好友/群的地址(如果未填则不显示) ,copyright: false //是否授权,如果通过官网捐赠获得LayIM,此处可填true }); var socket = new WebSocket("ws://127.0.0.1:7272"); socket.onopen = function(){ <?php $id = $_GET['id']; $conn = @mysql_connect("127.0.0.1", "root", "root") or die("连接数据库失败"); mysql_select_db("layim"); $rows = mysql_query("select * from snake_chatuser where id = $id"); $info = array(); while ($row = mysql_fetch_array($rows)) { $info = $row; } ?> var id = '<?php echo $info['id'];?>'; var username = '<?php echo $info['username'];?>'; var avatar = '<?php echo $info['avatar'];?>'; var sign = '<?php echo $info['sign'];?>'; //登录 var login_data = '{"type":"init","id":"'+id+'","username":"'+username+'","avatar":"'+avatar+'","sign":"'+sign+'"}'; socket.send( login_data ); console.log("websocket握手成功!"); }; //监听收到的消息 socket.onmessage = function(res){ // console.log(res); var data = eval("("+res.data+")"); switch(data['message_type']){ // 服务端ping客户端 case 'ping': socket.send('{"type":"ping"}'); break; // 登录 更新用户列表 case 'init': //console.log(data['id']+"登录成功"); //layim.getMessage(res.data); //res.data即你发送消息传递的数据(阅读:监听发送的消息) break; //添加 用户 case 'addUser': //console.log(data.data); layim.addList(data.data); break; //删除 用户 case 'delUser': layim.removeList({ type: 'friend' ,id: data.data.id //好友或者群组ID }); break; // 添加 分组信息 case 'addGroup': // console.log(data.data); layim.addList(data.data); break; case 'delGroup': layim.removeList({ type: 'group' ,id: data.data.id //好友或者群组ID }); break; // 检测聊天数据 case 'chatMessage': //console.log(data.data); layim.getMessage(data.data); break; // 离线消息推送 case 'logMessage': console.log(1); setTimeout(function(){layim.getMessage(data.data)}, 1000); break; // 用户退出 更新用户列表 case 'logout': break; //聊天还有不在线 case 'ctUserOutline': console.log('11111'); //layer.msg('好友不在线', {'time' : 1000}); break; } }; //layim建立就绪 layim.on('ready', function(res){ layim.on('sendMessage', function(res){ console.log(res); // 发送消息 var mine = JSON.stringify(res.mine); var to = JSON.stringify(res.to); var login_data = '{"type":"chatMessage","data":{"mine":'+mine+', "to":'+to+'}}'; socket.send( login_data ); }); }); }); </script> </html>
2、后台核心代码
<?php use GatewayWorkerLibGateway; use GatewayWorkerLibDb; /** * 主逻辑 * 主要是处理 onConnect onMessage onClose 三个方法 * onConnect 和 onClose 如果不需要可以不用实现并删除 */ class Events { public static function onConnect($client_id) { } /** * 当客户端发来消息时触发 * @param int $client_id 连接id * @param mixed $message 具体消息 */ public static function onMessage($client_id, $data) { $message = json_decode($data, true); $message_type = $message['type']; switch($message_type) { case 'init': // uid $uid = $message['id']; // 设置session $_SESSION = [ 'username' => $message['username'], 'avatar' => $message['avatar'], 'id' => $uid, 'sign' => $message['sign'] ]; // 将当前链接与uid绑定 Gateway::bindUid($client_id, $uid); // 通知当前客户端初始化 $init_message = array( 'message_type' => 'init', 'id' => $uid, ); Gateway::sendToClient($client_id, json_encode($init_message)); //查询最近1周有无需要推送的离线信息 $db1 = Db::instance('db1'); //数据库链接 $time = time() - 7 * 3600 * 24; $resMsg = $db1->select('id,fromid,fromname,fromavatar,timeline,content')->from('snake_chatlog') ->where("toid= {$uid} and timeline > {$time} and type = 'friend' and needsend = 1" ) ->query(); //var_export($resMsg); if( !empty( $resMsg ) ){ foreach( $resMsg as $key=>$vo ){ $log_message = [ 'message_type' => 'logMessage', 'data' => [ 'username' => $vo['fromname'], 'avatar' => $vo['fromavatar'], 'id' => $vo['fromid'], 'type' => 'friend', 'content' => htmlspecialchars( $vo['content'] ), 'timestamp'=> $vo['timeline'] * 1000, ] ]; Gateway::sendToUid( $uid, json_encode($log_message) ); //设置推送状态为已经推送 $db1->query("UPDATE `snake_chatlog` SET `needsend` = '0' WHERE id=" . $vo['id']); } } //查询当前的用户是在哪个分组中,将当前的链接加入该分组 $ret = $db1->query("select `groupid` from `snake_groupdetail` where `userid` = {$uid} group by `groupid`"); if( !empty( $ret ) ){ foreach( $ret as $key=>$vo ){ Gateway::joinGroup($client_id, $vo['groupid']); //将登录用户加入群组 } } unset( $ret ); return; break; case 'addUser' : //添加用户 $add_message = [ 'message_type' => 'addUser', 'data' => [ 'type' => 'friend', 'avatar' => $message['data']['avatar'], 'username' => $message['data']['username'], 'groupid' => $message['data']['groupid'], 'id' => $message['data']['id'], 'sign' => $message['data']['sign'] ] ]; Gateway::sendToAll( json_encode($add_message), null, $client_id ); return; break; case 'delUser' : //删除用户 $del_message = [ 'message_type' => 'delUser', 'data' => [ 'type' => 'friend', 'id' => $message['data']['id'] ] ]; Gateway::sendToAll( json_encode($del_message), null, $client_id ); return; break; case 'addGroup': //添加群组 $uids = explode( ',', $message['data']['uids'] ); $client_id_array = []; foreach( $uids as $vo ){ $ret = Gateway::getClientIdByUid( $vo ); //当前组中在线的client_id if( !empty( $ret ) ){ $client_id_array[] = $ret['0']; Gateway::joinGroup($ret['0'], $message['data']['id']); //将这些用户加入群组 } } unset( $ret, $uids ); $add_message = [ 'message_type' => 'addGroup', 'data' => [ 'type' => 'group', 'avatar' => $message['data']['avatar'], 'id' => $message['data']['id'], 'groupname' => $message['data']['groupname'] ] ]; Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id ); return; break; case 'joinGroup': //加入群组 $uid = $message['data']['uid']; $ret = Gateway::getClientIdByUid( $uid ); //若在线实时推送 if( !empty( $ret ) ){ Gateway::joinGroup($ret['0'], $message['data']['id']); //将该用户加入群组 $add_message = [ 'message_type' => 'addGroup', 'data' => [ 'type' => 'group', 'avatar' => $message['data']['avatar'], 'id' => $message['data']['id'], 'groupname' => $message['data']['groupname'] ] ]; Gateway::sendToAll( json_encode($add_message), [$ret['0']], $client_id ); //推送群组信息 } return; break; case 'addMember': //添加群组成员 $uids = explode( ',', $message['data']['uid'] ); $client_id_array = []; foreach( $uids as $vo ){ $ret = Gateway::getClientIdByUid( $vo ); //当前组中在线的client_id if( !empty( $ret ) ){ $client_id_array[] = $ret['0']; Gateway::joinGroup($ret['0'], $message['data']['id']); //将这些用户加入群组 } } unset( $ret, $uids ); $add_message = [ 'message_type' => 'addGroup', 'data' => [ 'type' => 'group', 'avatar' => $message['data']['avatar'], 'id' => $message['data']['id'], 'groupname' => $message['data']['groupname'] ] ]; Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id ); //推送群组信息 return; break; case 'removeMember': //将移除群组的成员的群信息移除,并从讨论组移除 $ret = Gateway::getClientIdByUid( $message['data']['uid'] ); if( !empty( $ret ) ){ Gateway::leaveGroup($ret['0'], $message['data']['id']); $del_message = [ 'message_type' => 'delGroup', 'data' => [ 'type' => 'group', 'id' => $message['data']['id'] ] ]; Gateway::sendToAll( json_encode($del_message), [$ret['0']], $client_id ); } return; break; case 'delGroup': //删除群组 $del_message = [ 'message_type' => 'delGroup', 'data' => [ 'type' => 'group', 'id' => $message['data']['id'] ] ]; Gateway::sendToAll( json_encode($del_message), null, $client_id ); return; break; case 'chatMessage': $db1 = Db::instance('db1'); //数据库链接 // 聊天消息 $type = $message['data']['to']['type']; $to_id = $message['data']['to']['id']; $uid = $message['data']['mine']['id']; $chat_message = [ 'message_type' => 'chatMessage', 'data' => [ 'username' => $message['data']['mine']['username'], 'avatar' => $message['data']['mine']['avatar'], 'id' => $type === 'friend' ? $uid : $to_id, 'type' => $type, 'content' => htmlspecialchars($message['data']['mine']['content']), 'timestamp'=> time()*1000, ] ]; //聊天记录数组 $param = [ 'fromid' => $uid, 'toid' => $to_id, 'fromname' => $message['data']['mine']['username'], 'fromavatar' => $message['data']['mine']['avatar'], 'content' => htmlspecialchars($message['data']['mine']['content']), 'timeline' => time(), 'needsend' => 0 ]; switch ($type) { // 私聊 case 'friend': // 插入 $param['type'] = 'friend'; if( empty( Gateway::getClientIdByUid( $to_id ) ) ){ $param['needsend'] = 1; //用户不在线,标记此消息推送 } $db1->insert('snake_chatlog')->cols( $param )->query(); return Gateway::sendToUid($to_id, json_encode($chat_message)); // 群聊 case 'group': $param['type'] = 'group'; $db1->insert('snake_chatlog')->cols( $param )->query(); return Gateway::sendToGroup($to_id, json_encode($chat_message), $client_id); } return; break; case 'hide': case 'online': $status_message = [ 'message_type' => $message_type, 'id' => $_SESSION['id'], ]; $_SESSION['online'] = $message_type; Gateway::sendToAll(json_encode($status_message)); return; break; case 'ping': return; default: echo "unknown message $data" . PHP_EOL; } } /** * 当用户断开连接时触发 * @param int $client_id 连接id */ public static function onClose($client_id) { $logout_message = [ 'message_type' => 'logout', 'id' => $_SESSION['id'] ]; Gateway::sendToAll(json_encode($logout_message)); } }
三、页面效果图
有需要了解的加QQ:2575404985,可以兼容linux与windows
在linux下运行方式,在workerman目录下创建start.sh,代码如下:
#!/bin/bash php ./Applications/start_register.php start & php ./Applications/start_gateway.php start & php ./Applications/start_businessworker.php start &
四、案例