前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「IM系列」WebSocket教程:私聊和群聊实现,数字化转型!

「IM系列」WebSocket教程:私聊和群聊实现,数字化转型!

作者头像
Tinywan
发布2023-12-13 11:56:41
3360
发布2023-12-13 11:56:41
举报
文章被收录于专栏:开源技术小栈开源技术小栈

1群聊和私聊

群聊: 群聊是指在一个群组中,多个成员可以互相交流和分享信息,多人参与的聊天对话。您可以创建或加入不同的群组,与团队成员、同事或其他人进行群组讨论和协作。可以容纳多个成员,适合用于团队讨论和群体交流。

私聊: 是指一对一的私密对话。在单聊中,您可以与其他用户进行私密交流,分享文件、图片、语音消息等。单聊适合私人对话、个别咨询和私密信息的传递。仅限两个成员参与,提供了私密的交流空间,私聊消息只有发送者和接收者可见,适合私人交流和个人话题讨论。

2约定

约定大于配置原则 这里先约定好客户端和服务端请求数据结构和字段。

字段约定

字段

描述

示例值

event

事件(join:加入连接,speak:发送消息)

join

mode

消息模式(1:私聊,2:群聊)

1

group_id

群组ID(私聊:0)

0

from_user_id

来自用户id

10086

from_username

来自用户昵称

开源技术小栈

to_user_id

接受用户id

10000

content

消息内容

你好! Tinywan

请求JOSN

代码语言:javascript
复制
{
    "event": "join",
    "mode": 1,
    "group_id": 0,
    "from_user_id": "10086",
    "from_username": "开源技术小栈",
    "to_user_id": "10000",
    "content": "Hi, 开源技术小栈"
}

用户和群组约定

用户名称

用户id(群组ID)

阿克苏

10086

拉姆才让

10000

無尘

2023

开源技术小栈(群)

100

3验证

这里直接复用webman官方验证器插件Validate 验证器插件

composer 安装

代码语言:javascript
复制
composer require tinywan/validate

消息验证器

自定义消息验证器MessageFormatValidate.php

代码语言:javascript
复制
<?php
/**
 * @desc IM消息格式验证器类,用于验证IM消息格式是否正确,以及消息内容是否合法,比如:消息类型、消息长度等等
 * @author Tinywan(ShaoBo Wan)
 * @email 756684177@qq.com
 * @date 2023/12/10 10:50
 */

declare(strict_types=1);

namespace app\common\validate;

class MessageFormatValidate extends BaseValidate
{
    /**
     * @var string[]
     */
    protected $rule = [
        'mode' => 'require|in:1,2',
        'group_id' => 'require|number',
        'from_user_id' => 'require',
        'from_username' => 'require',
        'from_avatar' => 'require',
        'content' => 'require|max:128',
    ];

    /**
     * @var string[]
     */
    protected $message = [
        'mode.require' => '消息模式是必须的',
        'mode.in' => '消息模式只能是1或2',
        'group_id.require' => '群组group_id是必须的',
        'from_user_id.require' => '用户id是必须的',
        'from_username.require' => '用户名必须填写',
        'from_avatar.require' => '用户头像是必须的',
        'content.require' => '消息内容是必须的',
        'content.max' => '消息内容最大支持128个字符',
    ];
}

Part1业务Events

回调函数

  • onWorkerStart():当businessWorker进程启动时触发。每个进程生命周期内都只会触发一次。
  • onWebSocketConnect():当客户端连接上gateway完成websocket握手时触发的回调函数。注意:此回调只有gateway为websocket协议并且gateway没有设置onWebSocketConnect时才有效。
  • onMessage():当客户端发来数据(Gateway进程收到数据)后触发的回调函数
  • onClose():客户端与Gateway进程的连接断开时触发。不管是客户端主动断开还是服务端主动断开,都会触发这个回调。一般在这里做一些数据清理工作。

onWebSocketConnect()

代码语言:javascript
复制
/**
 * @desc: 当客户端连接上gateway完成websocket握手时触发
 * @param string $clientId
 * @param array $data
 * @return bool
 * @author Tinywan(ShaoBo Wan)
 */
public static function onWebSocketConnect(string $clientId, array $data): bool
{
    try {
        $_SESSION['client_ip'] = get_client_real_ip($data['server']['HTTP_X_FORWARDED_FOR'] ?? $data['server']['HTTP_REMOTEIP'] ?? '127.0.0.1');
        $_SESSION['browser'] = isset($data['server']['HTTP_USER_AGENT']) ? get_client_browser($data['server']['HTTP_USER_AGENT']) : '未知';
    } catch (\Throwable $e) {
        Log::error('[onWebSocketConnect] ' . $e->getMessage() . '|' . $e->getFile() . '|' . $e->getLine());
        return Gateway::sendToCurrentClient(broadcast_json(500, $e->getMessage()));
    }
    return true;
}

onMessage()回调函数

代码语言:javascript
复制
/**
 * @desc 当客户端发来数据后触发的回调函数
 * @param string $clientId
 * @param string $message
 * @return false
 * @author Tinywan(ShaoBo Wan)
 */
public static function onMessage(string $clientId, string $message): bool
{
    try {
        $originMessage = json_decode($message, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            Gateway::closeClient($clientId, broadcast_json(400, '无效的json数据'));
            return false;
        }
        if (!is_array($originMessage)) {
            Gateway::closeClient($clientId, broadcast_json(400, '请求数据结构无法被解析'));
            return false;
        }

        $validate = new MessageFormatValidate();
        if (false === $validate->check($originMessage)) {
            Gateway::closeClient($clientId, broadcast_json(400, $validate->getError()));
            return false;
        }
        $groupId = $originMessage['group_id'];
        switch ($originMessage['event']) {
            case 'join':
                /** 群聊 */
                if ($originMessage['mode'] === 2) {
                    $_SESSION['group_id'] = $groupId;
                    Gateway::joinGroup($clientId, $groupId);
                /** 私聊 */
                } else {
                    Gateway::bindUid($clientId, $originMessage['from_user_id']);
                }
                $_SESSION['mode'] = $originMessage['mode'];
                $_SESSION['event'] = $originMessage['event'];
                $_SESSION['group_id'] = $groupId;
                $_SESSION['from_user_id'] = $originMessage['from_user_id'];
                $_SESSION['from_username'] = $originMessage['from_username'];
                Gateway::sendToCurrentClient(broadcast_json(0, 'success', $originMessage));
                break;
            case 'speak':
                /** 私聊 */
                if ($originMessage['mode'] == 1) {
                    $msg = $originMessage['from_username'] . '[单聊对]'.$originMessage['to_user_id'].'[说]:' . $originMessage['content'];
                    Gateway::sendToUid($originMessage['to_user_id'], broadcast_json(0, $msg, $originMessage));
                /** 群聊 */
                } else {
                    $msg = $originMessage['from_username'] . '[群聊说]:' . $originMessage['content'];
                    Gateway::sendToGroup($groupId, broadcast_json(0, $msg, $originMessage));
                }
                break;
            default:
                Gateway::sendToCurrentClient(broadcast_json(400, 'default invalid', $originMessage));
        }
    } catch (\Throwable $throwable) {
        return Gateway::sendToClient($clientId, broadcast_json(500, $throwable->getMessage()));
    }
    return true;
}

onClose()回调函数

代码语言:javascript
复制
/**
 * @desc: 客户端与Gateway进程的连接断开时触发
 * @param $clientId
 * @return bool
 * @author Tinywan(ShaoBo Wan)
 */
public static function onClose($clientId): bool
{
    try {
        $data = [
            'event' => 'leave',
            'group_id' => $_SESSION['group_id'] ?? '',
            'client_id' => $clientId,
            'content' => 'leaving group',
            'from_user_id' => $_SESSION['from_user_id'] ?? '',
            'from_username' => $_SESSION['from_username'] ?? ''
        ];
        if (isset($data['group_id']) && !empty($data['group_id'])) {
            GateWay::sendToGroup($data['group_id'], broadcast_json(0, 'close', $data));
            return true;
        }
        return Gateway::sendToCurrentClient(broadcast_json(500, 'error', $data));
    } catch (\Throwable $e) {
        $data = ['client_id' => $clientId, 'content' => $e->getMessage()];
        Log::error('[onClose] ' . $e->getMessage() . '|' . $e->getFile() . '|' .
            $e->getLine() . ': clientId = ' . $clientId);
        return Gateway::sendToCurrentClient(broadcast_json(500, 'error', $data));
    }
}  

Part2单聊

阿克苏拉姆才让 说话

加入会话

阿克苏

代码语言:javascript
复制
var ws = new WebSocket("ws://127.0.0.1:8783");
ws.onopen = function(evt) { 
   let $_content = {
      "event": "join",
      "mode": 1,
      "group_id": 0,
      "from_user_id": "10086",
      "from_username": "阿克苏",
      "to_user_id": "10000",
      "content": "加入会话",
   };
   ws.send(JSON.stringify($_content));
};

ws.onmessage = function(evt) {
  console.log( "【阿克苏】接受消息: " + evt.data);
};

拉姆才让

代码语言:javascript
复制
var ws = new WebSocket("ws://127.0.0.1:8783");

ws.onopen = function(evt) { 
   let $_content = {
      "event": "join",
      "mode": 1,
      "group_id": 0,
      "from_user_id": "10000",
      "from_username": "拉姆才让",
      "to_user_id": "10086",
      "content": "加入会话"
   };
   ws.send(JSON.stringify($_content));
};

ws.onmessage = function(evt) {
  console.log( "【拉姆才让】接受消息: " + evt.data);
};

发送消息

阿克苏拉姆才让说:

代码语言:javascript
复制
let $_content = {
  "event": "speak",
  "mode": 1,
  "group_id": 0,
  "from_user_id": "10086",
  "from_username": "阿克苏",
  "to_user_id": "10000",
  "content": "Hi, 我是阿克苏",
};
ws.send(JSON.stringify($_content));
ws.onmessage = function(evt) {
  console.log( "【阿克苏】接受消息: " + evt.data);
};

拉姆才让阿克苏说:

代码语言:javascript
复制
let $_content = {
  "event": "speak",
  "mode": 1,
  "group_id": 0,
  "from_user_id": "10000",
  "from_username": "拉姆才让",
  "to_user_id": "10086",
  "content": "Hi, 我是拉姆才让",
};
ws.send(JSON.stringify($_content));
ws.onmessage = function(evt) {
  console.log( "【拉姆才让】接受消息: " + evt.data);
};

阿克苏 console

拉姆才让 console

Part3群聊

加入会话

阿克苏

代码语言:javascript
复制
var ws = new WebSocket("ws://127.0.0.1:8783");
ws.onopen = function(evt) { 
   let $_content = {
      "event": "join",
      "mode": 2,
      "group_id": 100,
      "from_user_id": "10086",
      "from_username": "阿克苏",
      "to_user_id": "10000",
      "content": "加入会话",
   };
   ws.send(JSON.stringify($_content));
};

ws.onmessage = function(evt) {
  console.log( "【阿克苏】接受消息: " + evt.data);
};

拉姆才让

代码语言:javascript
复制
var ws = new WebSocket("ws://127.0.0.1:8783");

ws.onopen = function(evt) { 
   let $_content = {
      "event": "join",
      "mode": 2,
      "group_id": 100,
      "from_user_id": "10000",
      "from_username": "拉姆才让",
      "to_user_id": "10086",
      "content": "加入会话"
   };
   ws.send(JSON.stringify($_content));
};

ws.onmessage = function(evt) {
  console.log( "【拉姆才让】接受消息: " + evt.data);
};

無尘

代码语言:javascript
复制
var ws = new WebSocket("ws://127.0.0.1:8783");
ws.onopen = function(evt) { 
   let $_content = {
      "event": "join",
      "mode": 2,
      "group_id": 100,
      "from_user_id": "2023",
      "from_username": "無尘",
      "to_user_id": "10000",
      "content": "加入会话",
   };
   ws.send(JSON.stringify($_content));
};

ws.onmessage = function(evt) {
  console.log( "【無尘】接受消息: " + evt.data);
};

发送消息

阿克苏拉姆才让说:

代码语言:javascript
复制
let $_content = {
  "event": "speak",
  "mode": 2,
  "group_id": 100,
  "from_user_id": "10086",
  "from_username": "阿克苏",
  "to_user_id": "10000",
  "content": "【群组消息】:我是阿克苏",
};
ws.send(JSON.stringify($_content));
ws.onmessage = function(evt) {
  console.log( "【阿克苏】接受消息: " + evt.data);
};

拉姆才让阿克苏说:

代码语言:javascript
复制
let $_content = {
  "event": "speak",
  "mode": 2,
  "group_id": 100,
  "from_user_id": "10000",
  "from_username": "拉姆才让",
  "to_user_id": "10086",
  "content": "【群组消息】:拉姆才让",
};
ws.send(JSON.stringify($_content));
ws.onmessage = function(evt) {
  console.log( "【拉姆才让】接受消息: " + evt.data);
};

拉姆才让 收到群组100的消息

上一章节:「IM系列」WebSocket教程:安全授权认证详解和简单实现思路

Part4源码

文章相关源码地址:https://github.com/Tinywan/webman-admin

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-12-09,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1群聊和私聊
  • 2约定
    • 字段约定
      • 请求JOSN
        • 用户和群组约定
        • 3验证
          • composer 安装
            • 消息验证器
            • Part1业务Events
              • 回调函数
                • onWebSocketConnect()
                  • onMessage()回调函数
                    • onClose()回调函数
                    • Part2单聊
                      • 加入会话
                        • 发送消息
                        • Part3群聊
                          • 加入会话
                          • Part4源码
                          相关产品与服务
                          语音消息
                          语音消息(Voice Message Service,VMS)通过腾讯云提供的语音专线,为客户提供语音告警、语音通知、语音验证码等服务。语音消息具有高到达率、超低延时、秒级触达的优势,致力于提供优质的语音消息服务。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                          http://www.vxiaotou.com