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
{
"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 安装
composer require tinywan/validate
消息验证器
自定义消息验证器MessageFormatValidate.php
<?php
/**
* @desc IM消息格式验证器类,用于验证IM消息格式是否正确,以及消息内容是否合法,比如:消息类型、消息长度等等
* @author Tinywan(ShaoBo Wan)
* @email 756684177@qq.com
* @date 2023/12/10 10:50
*/
declare(strict_types=1);
namespace appcommonvalidate;
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()
/**
* @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()
回调函数
/**
* @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()
回调函数
/**
* @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单聊
阿克苏
对 拉姆才让
说话
加入会话
阿克苏
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);
};
拉姆才让
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);
};
发送消息
阿克苏
对拉姆才让
说:
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);
};
拉姆才让
对阿克苏
说:
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群聊
加入会话
阿克苏
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);
};
拉姆才让
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);
};
無尘
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);
};
发送消息
阿克苏
对拉姆才让
说:
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);
};
拉姆才让
对阿克苏
说:
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
原文始发于微信公众号(开源技术小栈):「IM系列」WebSocket教程:私聊和群聊实现,数字化转型!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/247974.html