Ratchet是一个基于PHP的WebSocket服务器和客户端库,它使得在PHP中构建实时Web应用程序变得相对简单。
以下是Ratchet框架的一些主要优点和缺点:
优点:
- 易于上手:Ratchet提供了清晰明了的API文档和示例代码,使得开发者能够快速掌握其基本用法。
- 高性能:Ratchet基于ReactPHP异步I/O模型,具有出色的性能表现,能够高效处理大量并发连接。
- 跨平台兼容性:Ratchet可以在Linux、Windows和macOS等多种操作系统上运行,为开发者提供了更大的灵活性。
- 丰富的功能:支持多种数据类型传输,包括文本、二进制数据及自定义事件等,并提供了可扩展的消息编码机制。
- 社区活跃:Ratchet拥有活跃的开发者社区,可以获取及时的技术支持和问题解答。
缺点:
- 生态系统相对较小:与Node.js等更流行的WebSocket解决方案相比,Ratchet的生态系统可能较小,可用的第三方库和工具可能较少。
- PHP的限制:由于Ratchet是基于PHP的,因此可能会受到PHP本身的一些限制,例如内存管理和并发处理等方面的限制。
- 学习曲线:虽然Ratchet的API文档相对清晰明了,但对于初学者来说,理解WebSocket和异步编程的概念可能需要一定的时间。
- 文档和社区支持:尽管Ratchet有文档和活跃的社区支持,但与一些更成熟的框架相比,这些资源可能相对较少或不够详细。
我在vscode中使用Ratchet库,首先通过Composer这个PHP依赖管理工具来安装,网上有很多教程,这里不再过多赘述。
先说明功能需求:
我这里实现的是一个客服与用户聊天的功能,一个客服可以对应多个用户,一个用户只能对应一个客服;当前用户与客服之间的沟通不会被其他用户和客服看见。
前端代码很简单:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线咨询</title>
<link rel="stylesheet" href="{THEME_PATH}assets/layui/css/layui.css">
<link href="{HOME_THEME_PATH}web/css/DigitalPlatform.css" rel="stylesheet" type="text/css" />
<script src="{THEME_PATH}assets/global/plugins/jquery.min.js" type="text/javascript"></script>
<script src="{THEME_PATH}assets/js/cms.js" type="text/javascript"></script>
<script src="{THEME_PATH}assets/layui/layui.js"></script>
</head>
<body>
<div class="platform_all">
<div class="platform_top">
<div class="platform_logoDiv">
<img src="/static/envtools/web/images/home/logo.png" alt="">
</div>
<span class="platform_text1">在线咨询</span>
<div class="platform_home gohome">
<img src="/static/envtools/web/images/digitalPlatform/homelogo.png" alt="">
</div>
<span class="platform_text2 gohome">首页</span>
</div>
<div class="chat_div">
<div class="chat_box">
<div class="chat_left">
<div class="chat_left_1">
<img src="" alt="" class="user_photo">
<div class="username_div">
<span class="text1"></span>
<span>VIP用户</span>
</div>
<div class="searchDiv">
<form action="" style="position: relative;">
<input type="text" class="searchInput" placeholder="搜索最近联系人">
</form>
</div>
<div class="chat_partner_div" id="chat_partner_list"></div>
</div>
</div><div class="chat_right">
<div class="chat_right_top">
<div class="chat_partner_div">
<div class="chat_partner" id="chat_partner_top"></div>
</div>
</div>
<div class="chat_container"></div>
<div class="chat_input">
<div class="emoji-tip">
<div class="emoji-selector"></div>
<img src="https://gw.alicdn.com/imgextra/i2/O1CN01LOhM1r1rwMX3LWpGK_!!6000000005695-55-tps-20-20.svg"
aria-haspopup="true" aria-expanded="false" id="emoji">
</div>
<div class="placeholder">点击输入内容...</div>
<textarea class="chat_edit" onkeydown="confirm(event)"></textarea>
</div>
<button class="sendButton" onclick="send()"><span class="sendText">发送</span></button>
</div>
</div>
</div>
</div>
</body>
<script>
$('.chat_edit').on('focus', function () {
$('.placeholder').hide();
})
$('.chat_edit').on('blur', function () {
if ($('.chat_edit').val()) {
$('.placeholder').hide();
} else {
$('.placeholder').show();
}
})
$('.placeholder').on('click', function () {
$('.placeholder').hide();
$('.chat_edit').focus();
})
$(document).click(function (event) {
if (!$(event.target).closest('.emoji-selector').length) {
$('.emoji-selector').fadeOut(300);
}
})
$('#emoji').click(function () {
event.stopPropagation();
$('.chat_edit').focus();
if ($('.emoji-selector').is(':visible')) {
$('.emoji-selector').fadeOut(300);
} else {
$('.emoji-selector').fadeIn(300);
}
});
</script>
<script src="{THEME_PATH}assets/global/plugins/emoji.js" type="text/javascript"></script>
<script src="{THEME_PATH}assets/global/plugins/chat.js" type="text/javascript"></script>
<script>
$('.gohome').click(function () {
window.location.href = "index.php?s=Httpapi&c=show&m=index";
});
</script>
</html>
JS代码有些长,但不难理解:
//登录用户信息
var res = JSON.parse(sessionStorage.getItem('UserStatus'));
if (res.code == 1) {
var serviceArray = [];//记录用户信息
var userid = res.id;
var status = res.status;
var username = res.username;
var userphoto = res.photo;
$(".user_photo").attr('src', userphoto);
$('.text1').html(username);
var ws = null;
// 创建websocket连接
connect();
function connect() {
// 创建一个 websocket 连接 ws://ip:端口号
ws = new WebSocket("ws://127.0.0.1:8091");
// 连接建立时触发
ws.onopen = onopen;
// 客户端接收服务端数据时触发
ws.onmessage = onmessage;
// 连接关闭时触发
ws.onclose = onclose;
// 通信发生错误时触发
ws.onerror = onerror;
}
// 通信建立成功
function onopen() {
console.log("系统消息:建立连接成功,自动发送一次通信")
sendMsg({ type: 'handShake', userid: userid, status: status });
}
// 接收服务端的数据
function onmessage(e) {
var data = JSON.parse(e.data);
console.log("data['type']:" + data['type'])
switch (data['type']) {
case 'handShake':
//首次登录,发送登陆数据
var user_info = { 'type': 'login', 'id': userid, 'username': username, 'userphoto': userphoto, 'status': status };
sendMsg(user_info);
break;
case 'login':
if (status == "USER" && userid == data.info.user.id) {
userList(data.info.service);
} else if (status == "SERVICE" && userid == data.info.service.id) {
console.log("客户" + data.info.user.id + "上线")
serviceArray.push(data.info.user);
userList(serviceArray);
}
sendMsg(user_info);
break;
case 'logout':
userList(data);
break;
case 'chat':
getMessage(data);
break;
case 'success':
sendSuccess(data);
break;
case 'error':
sendError(data);
break;
}
}
function onclose() {
console.log("连接关闭,定时重连");
// sendMsg({ type: 'logout', id: userid, status: status });
connect();
}
// websocket 错误事件
function onerror() {
console.log("系统消息 : 出错了,请退出重试.");
}
function confirm(event) {
var key_num = event.keyCode;
if (13 == key_num) {
send();
} else {
return false;
}
}
// 发送数据
function send() {
var msg = $('.chat_edit').val();
var reg = new RegExp("\r\n", "g");
msg = msg.replace(reg, "");
var whoSEND = {
id: userid,
status: status,
username: username,
userphoto: userphoto,
msg: msg
}
//取出聊天对象的索引,并清空会话
if (status == 'SERVICE') {
var sendWHO = sessionStorage.getItem('sendWHO'+userid);
sendMsg({ type: 'chat', sendWHO: JSON.parse(sendWHO), whoSEND: whoSEND });
} else if (status == 'USER') {
var sendWHO = sessionStorage.getItem('sendWHO'+userid);
sendMsg({ type: 'chat', sendWHO: JSON.parse(sendWHO), whoSEND: whoSEND });
}
$('.chat_edit').blur();
$('.chat_edit').val("");
$('.emoji-selector').fadeOut(300);
$('.placeholder').show();
}
// 发送数据
function sendMsg(msg) {
var data = JSON.stringify(msg);
ws.send(data);
}
// 追加从服务端返回的数据 左侧在线人数列表
function userList(user) {
var html = '';
var htmlTop = '';
console.log("user===:"+user)
if (user.type == "logout") {
console.log("客户" + user.id + "选择了离线")
$('.class' + user.id).remove();
if (user.status == "USER") {
serviceArray = serviceArray.filter(function (service) {
return service.id !== user.id;//移除serviceArray指定元素
});
} else if (user.status == "SERVICE") {
// $('.chat_partner_div').hide();
console.log(user.id + "客服退出")
}
} else {
if (status == "USER" && user != null) {
html = `<div class="chat_partner">
<img src="` + user.userphoto + `" alt="" class="partner_photo">
<div class="partner_name">` + user.username + `</div>
</div>`;
$('.chat_partner_div').html(html);
$('#chat_partner_list').children('.chat_partner').addClass('chat_partner_div_color');
//将聊天对象的信息存入会话中
var sendWHO = sessionStorage.getItem('sendWHO'+userid);
if (sendWHO != null || sendWHO != "") {
sessionStorage.removeItem('sendWHO'+userid);
}
var sendWHO = {
id: user.id,
username: user.username,
userphoto: user.userphoto,
status: user.status
}
sessionStorage.setItem('sendWHO'+userid, JSON.stringify(sendWHO));
} else if (status == "SERVICE" && user != null) {
for (var i = 0; i < user.length; i++) {
htmlTop += `<div class="chat_partner htmlTop " id="htmlTop${i}" hidden>
<img src="` + user[i].userphoto + `" alt="" class="partner_photo">
<div class="partner_name">` + user[i].username + `</div>
</div>`;
html += `<div class="chat_partner" id="chat_partner${i}">
<img src="` + user[i].userphoto + `" alt="" class="partner_photo">
<div class="partner_name">` + user[i].username + `</div>
</div>`;
}
$('.chat_container').html(`<div class="select_people"><span class="select_word">您还没有选择联系人</span></div>`);
$('#chat_partner_top').html(htmlTop);
$('#chat_partner_list').html(html);
$('.chat_partner').on('click', function () {
var id = $(this).attr('id').replace('chat_partner', '');
$('.select_people').hide();
$('.chat_message').remove();
$('.chat_partner').removeClass('chat_partner_div_color');
$('#chat_partner' + id).addClass('chat_partner_div_color');
$('.htmlTop').hide();
$('#htmlTop' + id).show();
//点击左侧在线用户后,通过查询数据库展现右侧不同页面和聊天记录
$.post('index.php?s=Httpapi&c=Chat&m=records', {
send_id: userid,
receive_id: user[id]['id']
}, function (result) {
for (var i = 0; i < result.data.length; i++) {
console.log("聊天记录id" + i + ":" + result.data[i]['id'])
console.log("聊天记录msg" + i + ":" + result.data[i]['msg'])
if (result.data[i]['tag'] == (userid + "-" + user[id]['id'])) {
console.log("他发的")
var html = `<div class="chat_message my-bubble1">
<div class="chat_message_top">
<div class="chat_time">` + result.data[i]['sendtime'] + `</div>
<div class="partner_name">` + result.data[i]['username'] + `</div>
<img src="` + result.data[i]['photo'] + `" alt="" class="partner_photo" style="margin-right: 20px;">
</div>
<div class="my-bubble2">
<span class="bubble_text">` + result.data[i]['msg'] + `</span>
</div>
</div>`;
var active_chat = $('.chat_container');
var oldHtml = active_chat.html();
active_chat.html(oldHtml + html);
active_chat.scrollTop(active_chat[0].scrollHeight);
} else if (result.data[i]['tag'] == (user[id]['id'] + "-" + userid)) {
console.log("我发的")
var html = `<div class="chat_message other-bubble1">
<div class="chat_message_top">
<img src="` + result.data[i]['photo'] + `" alt="" class="partner_photo" style="margin-right: 20px;">
<div class="partner_name">` + result.data[i]['username'] + `</div>
<div class="chat_time">` + result.data[i]['sendtime'] + `</div>
</div>
<div class="other-bubble2">
<span class="bubble_text">` + result.data[i]['msg'] + `</span>
</div>
</div>`;
var active_chat = $('.chat_container');
var oldHtml = active_chat.html();
active_chat.html(oldHtml + html);
active_chat.scrollTop(active_chat[0].scrollHeight);
}
}
});
//将聊天对象的信息存入会话中
var sendWHO = sessionStorage.getItem('sendWHO'+userid);
if (sendWHO != null || sendWHO != "") {
sessionStorage.removeItem('sendWHO'+userid);
}
var sendWHO = {
id: user[id]['id'],
username: user[id]['username'],
userphoto: user[id]['userphoto'],
status: user[id]['status']
}
sessionStorage.setItem('sendWHO'+userid, JSON.stringify(sendWHO));
});
}
}
}
//信息发送成功后对页面的渲染
function sendSuccess(data) {
//获取时间
var year = new Date().getFullYear();
var monthDay = ('0' + (new Date().getMonth() + 1)).slice(-2) + '-' + ('0' + new Date().getDate()).slice(-2);
var time = ('0' + (new Date().getHours())).slice(-2) + ':' + ('0' + new Date().getMinutes()).slice(-2) + ':' + ('0' + new Date().getSeconds()).slice(-2);
var date = year + '-' + monthDay + ' ' + time;
if (data.length != 0) {
//首先判断服务器是否传来的发送成功消息,根据该消息来渲染前端页面的自己发送信息div
if (data['type'] == 'success' && data['sendername'] == username && data['senderstatus'] == status) {
//该条件满足,意味着自己发送了一条消息,在此渲染自己发送消息的div
var html = `<div class="chat_message my-bubble1">
<div class="chat_message_top">
<div class="chat_time">` + date + `</div>
<div class="partner_name">` + data['sendername'] + `</div>
<img src="` + data['senderphoto'] + `" alt="" class="partner_photo" style="margin-right: 20px;">
</div>
<div class="my-bubble2">
<span class="bubble_text">` + data['sendermsg'] + `</span>
</div>
</div>`;
//将自己发送的消息存入数据库
var sendWHO = JSON.parse(sessionStorage.getItem('sendWHO'+userid));
$.post('index.php?s=Httpapi&c=Chat&m=chat', {
tag: data['senderid'] + "-" + sendWHO.id,
send_id: data['senderid'],
receive_id: sendWHO.id,
sendtime: date,
msg: data['sendermsg']
});
}
var active_chat = $('.chat_container');
var oldHtml = active_chat.html();
active_chat.html(oldHtml + html);
active_chat.scrollTop(active_chat[0].scrollHeight);
}
}
// 右侧聊天记录列表
function getMessage(data) {
//获取时间
var year = new Date().getFullYear();
var monthDay = ('0' + (new Date().getMonth() + 1)).slice(-2) + '-' + ('0' + new Date().getDate()).slice(-2);
var time = ('0' + (new Date().getHours())).slice(-2) + ':' + ('0' + new Date().getMinutes()).slice(-2) + ':' + ('0' + new Date().getSeconds()).slice(-2);
var date = year + '-' + monthDay + ' ' + time;
if (data.length != 0) {
if (data['whoSEND']['username'] != username && data['sendWHO']['username'] == username) {
//我是信息的接收者,我需要在左边展示whoSEND里面的信息
var html = `<div class="chat_message other-bubble1">
<div class="chat_message_top">
<img src="` + data['whoSEND']['userphoto'] + `" alt="" class="partner_photo">
<div class="partner_name">` + data['whoSEND']['username'] + `</div>
<div class="chat_time">` + date + `</div>
</div>
<div class="other-bubble2">
<span class="bubble_text">` + data['whoSEND']['msg'] + `</span>
</div>
</div>`;
}
}
var active_chat = $('.chat_container');
var oldHtml = active_chat.html();
active_chat.html(oldHtml + html);
active_chat.scrollTop(active_chat[0].scrollHeight);
}
//错误信息的处理与反馈
function sendError(data){
console.log(data)
}
} else {
window.location.href = "index.php?s=Httpapi&c=show&m=login";
}
附上在聊天页面,点击表情后会弹出选择表情包的div的代码:
var emojis = [
"😀", "😁", "😂", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎",
"😍", "😘", "😗", "😙", "😚", "😇", "😐", "😑", "😶", "😏", "😣",
"😥", "😮", "😯", "😪", "😫", "😴", "😌", "😛", "😜", "😝", "😒",
"😓", "😔", "😕", "😲", "😷", "😖", "😞", "😟", "😤", "😢", "😭",
"😦", "😧", "😨", "😬", "😰", "😱", "😳", "😵", "😡", "😠", "😈",
"👿", "👹", "👺", "💀", "☠", "👻", "👽", "👾", "💣", "💋", "💌",
"💘", "❤", "💓", "💔", "💕", "💖", "💗", "💙", "💚", "💛", "💜",
"💝", "💞", "💟", "💏", "🧑🤝🧑", "💪", "👈", "👉", "☝", "👆", "👇",
"✌", "✋", "👌", "👍", "👎", "✊", "👊", "👋", "👏", "👐", "✍"
]
// emoji容器框
var emojiSelector = document.getElementsByClassName('emoji-selector')[0]
// 输入框
var chatInput = document.getElementsByClassName('chat_edit')[0]
// 单个表情
var emojiBtn = document.getElementsByClassName('emoji-item')
// 显示表情
for (let index = 0; index < emojis.length; index++) {
emojiSelector.innerHTML += "<span class='emoji-item'>" + emojis[index] + "</span>"
}
// emoji点击响应
for (let index = 0; index < emojiBtn.length; index++) {
emojiBtn[index].addEventListener('click', function (e) {
// 点击时,焦点放入输入框中
chatInput.focus();
// 获取当前光标位置
var start = chatInput.selectionStart;
var end = chatInput.selectionEnd;
// 插入文本
var textBefore = chatInput.value.substring(0, start);
var textAfter = chatInput.value.substring(end, chatInput.value.length);
chatInput.value = textBefore + emojis[index] + textAfter;
// 设置新的光标位置
chatInput.selectionStart = start + emojis.length;
chatInput.selectionEnd = start + emojis.length;
});
}
根据前端的代码,简单来讲:当websocket连接成功后,前端向后端发送一个type为shack(这里可以自定义,叫什么都可以)的数据,后端收到后也向前端传递一个type为shack的数据。
前端通过switch来检查后端传来的数据的type,后端同理,也是用switch检查前端传来的数据的type。就像两个人认识,从初见(type:shakc),到彼此打量(type:login),到彼此交流(type:chat)。
接着服务器需要判断这段信息的发送人是谁,接收人是谁,信息内容是什么,信息发送成功后的响应,信息发送失败后的响应。
前端就需要依据服务器发送的相应进行相应的逻辑处理。
根据这个逻辑,我们不但能理解前端代码,也能更加透彻的理解服务器代码了。
下面是PHP服务器脚本代码:
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
require __DIR__ . '/autoload.php';
class WebSocket implements MessageComponentInterface
{
protected $userClients;//用户连接数组
protected $serviceClients;//客服连接数组
protected $_userToService; // 用户到客服的映射
public $_response = array();//返回用户信息数组
public $_userlist = array();//存入用户信息
public $_servicelist = array();//存入客服信息
public function __construct()
{
$this->userClients = array();
$this->serviceClients = array();
$this->_userToService = []; // 初始化用户到客服的映射
}
public function onOpen(ConnectionInterface $conn)
{
echo "[客户端与服务器连接成功]";
}
public function onMessage(ConnectionInterface $conn, $data)
{
if (!empty($data)) {
$data = json_decode($data, true);
//将用户与客服的连接分类存储
if (!empty($data['status']) && !empty($data['type']) && $data['type'] == 'handShake') {
$this->_response = array(
'type' => 'handShake'
);
$conn->send(json_encode($this->_response));
if ($data['status'] == 'USER') {
$this->userClients[$data['userid']] = array(
'conn' => $conn,
'id' => (int) $data['userid'],
'status' => $data['status'],
);
} elseif ($data['status'] == 'SERVICE') {
$this->serviceClients[$data['userid']] = array(
'conn' => $conn,
'id' => (int) $data['userid'],
'status' => $data['status']
);
}
}
if (!empty($data['type']) && $data['type'] != 'handShake') {
switch ($data['type']) {
case "login":
if (isset($data['id']) && $data['status'] == 'USER') {
$this->_userlist[$data['id']] = array(
'id' => (int) $data['id'],
'username' => (string) $data['username'],
'userphoto' => (string) $data['userphoto'],
'status' => (string) $data['status']
);
} elseif (isset($data['id']) && $data['status'] == 'SERVICE') {
$this->_servicelist[$data['id']] = array(
'id' => (int) $data['id'],
'username' => (string) $data['username'],
'userphoto' => (string) $data['userphoto'],
'status' => (string) $data['status']
);
}
if ($data['status'] == 'USER') {
if (!empty($this->_servicelist)) {
foreach ($this->_servicelist as $key => $value) {}
//连接的客服ID作为用户客服连接数组的索引
$this->_userToService[$data['id']] = array(
'user' => $this->_userlist[$data['id']],
'service' => $this->_servicelist[$key],
);
} else {
//在这里提醒客服进入聊天室
echo "[客服不存在]";
$this->_userToService[$data['id']] = array(
'user' => $this->_userlist[$data['id']],
);
print_r($this->_userToService);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-5",
"msg" => "客服不存在"
]));
}
//用户与客服已经绑定
if (!empty($this->_userToService)) {
echo '[绑定用户和客服的连接]';
print_r($this->_userToService);
if (!empty($this->_userlist)) {
//通知用户已经建立连接
$this->userClients[$this->_userToService[$data['id']]['user']['id']]['conn']->send(json_encode([
'type' => 'login',
'info' => $this->_userToService[$data['id']],
]));
}
if (!empty($this->_servicelist)) {
//通知客服已经建立连接
$this->serviceClients[$this->_userToService[$data['id']]['service']['id']]['conn']->send(json_encode([
'type' => 'login',
'info' => $this->_userToService[$data['id']]
]));
}
} else {
echo "[用户与客服连接失败]";
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-4",
"msg" => "用户与客服连接失败"
]));
}
} elseif ($data['status'] == 'SERVICE') {
echo "[客服打开了聊天窗口]";
print_r($this->_userToService);
//当用户存在时,判断用户是否已经与客服连接,防止多个客服连接同一个用户
if (empty($this->_userToService['service'])) {
if (!empty($this->_userlist)) {
//连接的用户ID作为用户客服连接数组的索引
foreach ($this->_userToService as $key => $userToService) {
if (empty($userToService['service'])) {
$this->_userToService[$key] = array(
'service' => $this->_servicelist[$data['id']],
'user' => $userToService['user'],
);
if (!empty($this->_userlist)) {
//通知用户已经建立连接
$this->userClients[$key]['conn']->send(json_encode([
'type' => 'login',
'info' => $this->_userToService[$key],
]));
}
if (!empty($this->_servicelist)) {
//通知客服已经建立连接
$this->serviceClients[$data['id']]['conn']->send(json_encode([
'type' => 'login',
'info' => $this->_userToService[$key]
]));
}
echo "======客服眼里的连接:";
print_r($this->_userToService);
}
}
}
}
}
break;
case "chat":
if (!empty($data) && !empty($data['sendWHO']) && !empty($data['whoSEND'])) {
echo "客户端发来的data:";
print_r($data);
$data['whoSEND']['msg'] = htmlentities($data['whoSEND']['msg']);//转义发送者输入的内容,防止XXS攻击
$data['whoSEND']['username'] = htmlentities($data['whoSEND']['username']);//转义发送者用户名
$data['sendWHO']['username'] = htmlentities($data['sendWHO']['username']);//转义接收者用户名
//给发送者一个返回值,告诉他服务器已经收到了他 发来的消息
$whoSENDID = $data['whoSEND']['id'];
if ($data['whoSEND']['status'] == "USER") {
//用户发送的消息
echo "用户发送的消息";
$this->userClients[$whoSENDID]['conn']->send(json_encode([
"type" => "success",
"sendername" => $data['whoSEND']['username'],
"senderphoto" => $data['whoSEND']['userphoto'],
"senderid" => $data['whoSEND']['id'],
"senderstatus" => $data['whoSEND']['status'],
'sendermsg' => $data['whoSEND']['msg'],
]));
} elseif ($data['whoSEND']['status'] == "SERVICE") {
//客服发送的消息
$this->serviceClients[$whoSENDID]['conn']->send(json_encode([
"type" => "success",
"sendername" => $data['whoSEND']['username'],
"senderphoto" => $data['whoSEND']['userphoto'],
"senderid" => $data['whoSEND']['id'],
"senderstatus" => $data['whoSEND']['status'],
'sendermsg' => $data['whoSEND']['msg'],
]));
}
//把消息转发给接收者
if ($data['sendWHO']['status'] == "SERVICE") {
// $this->serviceClients[$data['sendWHO']['id']]['conn']->send(json_encode($data));
// 检查 $data['sendWHO']['id'] 是否存在于 $this->userClients 中
if (isset($this->serviceClients[$data['sendWHO']['id']])) {
// 检查 'conn' 键是否存在,并且它的值不是 null
if (is_array($this->serviceClients[$data['sendWHO']['id']]) && isset($this->serviceClients[$data['sendWHO']['id']]['conn']) && $this->serviceClients[$data['sendWHO']['id']]['conn'] !== null) {
// 现在可以安全地调用 send 方法
$this->serviceClients[$data['sendWHO']['id']]['conn']->send(json_encode($data));
} else {
// 处理 'conn' 不存在或为 null 的情况
error_log("尝试发送消息时连接不存在或为 null: " . $data['sendWHO']['id']);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-3",
"msg" => "尝试发送消息时连接不存在或为 null"
]));
}
} else {
// 处理 $data['sendWHO']['id'] 不存在于 $this->userClients 的情况
error_log("尝试发送消息时找不到客服连接: " . $data['sendWHO']['id']);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-2",
"msg" => "尝试发送消息时找不到客服连接"
]));
}
} elseif ($data['sendWHO']['status'] == "USER") {
// $this->userClients[$data['sendWHO']['id']]['conn']->send(json_encode($data));
// 检查 $data['sendWHO']['id'] 是否存在于 $this->userClients 中
if (isset($this->userClients[$data['sendWHO']['id']])) {
// 检查 'conn' 键是否存在,并且它的值不是 null
if (is_array($this->userClients[$data['sendWHO']['id']]) && isset($this->userClients[$data['sendWHO']['id']]['conn']) && $this->userClients[$data['sendWHO']['id']]['conn'] !== null) {
// 现在可以安全地调用 send 方法
$this->userClients[$data['sendWHO']['id']]['conn']->send(json_encode($data));
} else {
// 处理 'conn' 不存在或为 null 的情况
error_log("尝试发送消息时连接不存在或为 null: " . $data['sendWHO']['id']);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-3",
"msg" => "尝试发送消息时连接不存在或为 null"
]));
}
} else {
// 处理 $data['sendWHO']['id'] 不存在于 $this->userClients 的情况
error_log("尝试发送消息时找不到用户连接: " . $data['sendWHO']['id']);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-2",
"msg" => "尝试发送消息时找不到用户连接"
]));
}
}
} else {
echo "[警告!聊天数据不存在]";
print_r($data);
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-1",
"msg" => "警告!聊天数据不存在"
]));
}
break;
case "logout":
echo "退出聊天的数据:";
print_r($data);
break;
}
}
}
}
public function onClose(ConnectionInterface $conn)
{
echo "[链接关闭]";
foreach ($this->userClients as $key => $userClient) {
// 找到了当前用户对应的连接socket
if ($userClient['conn'] === $conn) {
$id = $userClient['id'];
$status = $userClient['status'];
echo $id . "用户已经离线";
$this->logout($id, $status);
//清空数组里的相关信息
unset($this->_userlist[$id]);
unset($this->_userToService[$id]);
unset($this->userClients[$key]);
break;
}
}
/* 只要客服连接过,就不再关闭客服的socket连接 */
foreach ($this->serviceClients as $key => $serviceClient) {
// 找到了当前客服对应的连接socket
if ($serviceClient['conn'] === $conn) {
$id = $serviceClient['id'];
$status = $serviceClient['status'];
echo $id . "客服已经离线";
$this->logout($id, $status);
//清空数组里的相关信息
unset($this->_servicelist[$id]);
unset($this->serviceClients[$key]);
break;
}
}
}
public function onError(ConnectionInterface $conn, \Throwable $e)
{
error_log("连接出错: {$e->getMessage()}");
$conn->send(json_encode([
"type" => "error",
"data" => "ERROR/-6",
"msg" => $e->getMessage()
]));
$conn->close();
}
public function logout($id, $status)
{
if ($status == "USER") {
foreach ($this->serviceClients as $serviceClient) {
$serviceClient['conn']->send(json_encode([
"type" => "logout",
'id' => $id,
"msg" => '连接已经断开',
'status' => $status
]));
}
} elseif ($status == "SERVICE") {
foreach ($this->userClients as $userClient) {
$userClient['conn']->send(json_encode([
"type" => "logout",
'id' => $id,
"msg" => '连接已经断开',
'status' => $status
]));
}
}
}
}
// 创建WebSocket服务器
$server = \Ratchet\Server\IoServer::factory(
new \Ratchet\Http\HttpServer(
new \Ratchet\WebSocket\WsServer(
new WebSocket()
)
),
8091,
);
echo "服务器脚本启动";
$server->run();
当我们写好服务器脚本后,需要使用php命令来启动该脚本,假如服务器脚本的文件名是:SocketServer.php
启动服务器脚本的命令:
php SocketServer.php
这么一套流程下来,客服和用户就可以愉快的通信了,同时不同客服对应的用户也不会产生冲突。
客服和用户连接成功:
用户进入聊天室但是客服不在线的情况:
我们需要在用户进入聊天室后,给客服发送提示消息。但是这一点我还不知道应该如何实现。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/109188.html