Ratchet库实现WebSocket

Ratchet是一个基于PHP的WebSocket服务器和客户端库,它使得在PHP中构建实时Web应用程序变得相对简单。

以下是Ratchet框架的一些主要优点和缺点:

优点:

  1. 易于上手:Ratchet提供了清晰明了的API文档和示例代码,使得开发者能够快速掌握其基本用法。
  2. 高性能:Ratchet基于ReactPHP异步I/O模型,具有出色的性能表现,能够高效处理大量并发连接。
  3. 跨平台兼容性:Ratchet可以在Linux、Windows和macOS等多种操作系统上运行,为开发者提供了更大的灵活性。
  4. 丰富的功能:支持多种数据类型传输,包括文本、二进制数据及自定义事件等,并提供了可扩展的消息编码机制。
  5. 社区活跃:Ratchet拥有活跃的开发者社区,可以获取及时的技术支持和问题解答。

缺点:

  1. 生态系统相对较小:与Node.js等更流行的WebSocket解决方案相比,Ratchet的生态系统可能较小,可用的第三方库和工具可能较少。
  2. PHP的限制:由于Ratchet是基于PHP的,因此可能会受到PHP本身的一些限制,例如内存管理和并发处理等方面的限制。
  3. 学习曲线:虽然Ratchet的API文档相对清晰明了,但对于初学者来说,理解WebSocket和异步编程的概念可能需要一定的时间。
  4. 文档和社区支持:尽管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

这么一套流程下来,客服和用户就可以愉快的通信了,同时不同客服对应的用户也不会产生冲突。

客服和用户连接成功:

用户进入聊天室但是客服不在线的情况:

我们需要在用户进入聊天室后,给客服发送提示消息。但是这一点我还不知道应该如何实现。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
PHP 和 JavaScript 之间连接 WebSocket 可以使用 PHP 中的 Ratchet 和 JavaScript 中的 WebSocket API。具体实现步骤为: 1. 在 PHP 中安装并导入 Ratchet Ratchet 是一个 PHP 的 WebSocket ,可以使用 Composer 在 PHP 中安装。然后,在 PHP 中通过 use 关键字导入 Ratchet 的相应类。 2. 在 PHP 中创建 WebSocket 服务器。使用 Ratchet 提供的 WsServer 类来创建 WebSocket 服务器。在服务器启动时,需要监听端口。 3. 在 JavaScript 中创建 WebSocket 对象。使用 JavaScript 中的 WebSocket API 中的 WebSocket 构造函数创建 WebSocket 对象。需要将 WebSocket 对象连接到服务器指定的标识符。 4. 使用 JavaScript 中 WebSocket 对象的事件处理程序。在 JavaScript 中,WebSocket 对象拥有多个事件,例如 onopen、onmessage、onerror 和 onclose。通过设置这些事件处理程序,可以控制 WebSocket 连接如何响应不同事件。 5. 在服务器端处理 WebSocket 消息。当客户端通过连接到服务器发送消息时,服务器需要读取消息并根据消息内容进行处理。可以使用 Ratchet 中提供的 MessageComponentInterface 接口来处理 WebSocket 消息。 总的来说,实现在 PHP 中创建 WebSocket 服务器,并在 JavaScript 中创建 WebSocket 对象来连接服务器的方法包括 PHP 的 Ratchet 和 JavaScript 的 WebSocket API,并需要在服务器和客户端之间处理不同的 WebSocket 事件和消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏木子杉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值