上节课(http://blog.youkuaiyun.com/github_26672553/article/details/54932788)我们已经完成了聊天功能的权限(用户名必须)认证。
这节课我们来完成,websocket客户端页面中『所有用户』显示出,连接到我们服务端的所有客户端用户。
1.在服务端验证用户登录后,就把保存的客户端信息,发生给客户端
//一旦有用户登录就把保存的客户端信息发送过去
$connection->send('users:'.json_encode($clients));
我们用users: 作为区分,表示是发送的数据是 所有用户的信息。
2.客户端收到消息,然后展示
if(/^users:/.test(getMsg)){ //显示当前已登录用户
getMsg = getMsg.replace('users:','');
getMsg= eval('('+getMsg+')'); //转json
var listusers = document.getElementById('listusers');
listusers.innerHTML = '';//清空
for(var key in getMsg){
var option = document.createElement('option');
option.value = key; //ip
option.innerHTML = getMsg[key]; //昵称
listusers.appendChild(option); //添加元素进去
}
}
websocket客户端在onmessage 回调函数中,判断接受的消息,是否是『所有用户』的数据,然后展示到页面。
我们这里只有『lily』这一位用户登录了。
至此,服务端server.php全部代码如下:
<?php
//本机IP是10.211.55.13
//需要监听的端口是 9090
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;
require 'workerman/Autoloader.php';
$clients = []; //保存客户端信息
// 创建一个Worker监听9090端口,使用websocket协议通讯
$ws_worker = new Worker("websocket://10.211.55.13:9090");
// 启动4个进程对外提供服务
$ws_worker->count = 4;
// 当收到客户端发来的数据后
$ws_worker->onMessage = function($connection, $data)
{
//这里用global的原因是:php是有作用域的,我们是在onMessage这个回调还是里操作外面的数组
//想要改变作用域外面的数组,就global一下
global $clients;
//验证客户端用户名在3-20个字符
if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ //代表是客户端认证
$ip = $connection->getRemoteIp();
if(!array_key_exists($ip,$clients)){ //必须是之前没有注册过
$clients[$ip] = $result[1]; //把新用户保存起来, 格式为 ip=>昵称
// 向客户端发送数据
$connection->send('notice:success'); //验证成功消息
$connection->send('msg:welcome '.$result[1]); //普通消息
echo $ip .':' .$result[1] .'login' . PHP_EOL; //这是为了演示,控制台打印信息
//一旦有用户登录就把保存的客户端信息发送过去
$connection->send('users:'.json_encode($clients));
}
}elseif(preg_match('/^msg:(.*?)/isU',$data,$msgset)){ //代表是客户端发送的普通消息
if(array_key_exists($connection->getRemoteIp(),$clients)){ //必须是之前验证通过的客户端
echo 'get msg:' . $msgset[1] .PHP_EOL; //这是为了演示,控制台打印信息
if($msgset[1] == 'nihao'){
//如果收到'nihao',就给客户端发送'nihao 用户名'
//给客户端发送普通消息
$connection->send('msg:nihao '.$clients[$connection->getRemoteIp()]);
}
}
}
// 设置连接的onClose回调
$connection->onClose = function($connection) //客户端主动关闭
{
global $clients;
unset($clients[$connection->getRemoteIp()]);
echo "connection closed\n";
};
};
// 运行worker
Worker::runAll();
客户端WebSocket_client.html全部代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket_client</title>
<script>
//创建一个socket实例
var socket = null; //初始为null
var isLogin = false; //是否登录到服务器上
//定义一个连服务的函数
function connectServer(){
var username = document.getElementById('username').value;
if (username == ''){
alert('用户昵称必填');
}
socket = new WebSocket("ws://10.211.55.13:9090");
socket.onopen = function() {
socket.send('login:' + username);
};
socket.onmessage = function(e) {
var getMsg = e.data;
if(/^notice:success$/.test(getMsg)){ //服务器验证通过
isLogin = true;
}else if(/^msg:/.test(getMsg)){ //代表是普通消息
var p = document.createElement('p');
p.innerHTML = '<span>收到消息:</span>' + getMsg.replace('msg:','');
document.getElementById('txtcontent').appendChild(p);
}else if(/^users:/.test(getMsg)){ //显示当前已登录用户
getMsg = getMsg.replace('users:','');
getMsg= eval('('+getMsg+')'); //转json
var listusers = document.getElementById('listusers');
listusers.innerHTML = '';//清空
for(var key in getMsg){
var option = document.createElement('option');
option.value = key; //ip
option.innerHTML = getMsg[key]; //昵称
listusers.appendChild(option); //添加元素进去
}
}
};
socket.onclose = function(){
isLogin = false;
}
}
//发送消息
function send(){
if (!isLogin){
alert('请先通过服务器验证');
}
var msg = document.getElementById('txtmsg').value;
//console.log(msg);
socket.send('msg:' + msg); //发送消息到服务端
//显示我们的消息到div中
var p = document.createElement('p');
p.innerHTML = '<span>回复消息:</span>' + msg;
document.getElementById('txtcontent').appendChild(p);
}
</script>
</head>
<body>
<div id="txtcontent" style="width: 500px;height: 250px;border: 1px solid gray"></div>
<div>所有用户:<select id="listusers"></select></div>
<div>你的昵称:<input type="text" id="username" /></div>
<div>
回复内容:
<textarea style="width: 500px;height: 100px" id="txtmsg"></textarea>
</div>
<div>
<button onclick="connectServer()">连接服务器</button>
<button onclick="send()">发送消息</button>
</div>
</body>
</html>
广播怎么理解
//一旦有用户登录就把保存的客户端信息发送过去
$connection->send('users:'.json_encode($clients));
我们前面通过这个连接发送用户数据给客户端,这个connection是当前和服务端连接的客户端,并没有对所有的客户端进行广播。
广播的原理,就在于对我们所有的connection都遍历,去执行send()。
所以,接下来改造我们服务端代码。
$clients[$ip] = $result[1]; //把新用户保存起来, 格式为 ip=>昵称
我们之前是这样保存客户端信息的,这样还不够,我们需要把connection也保存起来。
//$clients[$ip] = $result[1]; //把新用户保存起来, 格式为 ip=>昵称
$clients[$ip] = ['ip'=>$ip,'name'=>$result[1],'conn'=>$connection];
然后我们循环send就实现了简单的广播消息
//一旦有用户登录就把保存的客户端信息发送过去
//$connection->send('users:'.json_encode($clients));
$users = 'users:'.json_encode(array_column($clients,'name','ip')); //准备要广播的数据
foreach($clients as $ip=>$client){
$client['conn']->send($users);
}
改造之后的服务端代码server.php
<?php
//本机IP是10.211.55.13
//需要监听的端口是 9090
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;
require 'workerman/Autoloader.php';
$clients = []; //保存客户端信息
// 创建一个Worker监听9090端口,使用websocket协议通讯
$ws_worker = new Worker("websocket://10.211.55.13:9090");
// 启动4个进程对外提供服务
$ws_worker->count = 4;
// 当收到客户端发来的数据后
$ws_worker->onMessage = function($connection, $data)
{
//这里用global的原因是:php是有作用域的,我们是在onMessage这个回调还是里操作外面的数组
//想要改变作用域外面的数组,就global一下
global $clients;
//验证客户端用户名在3-20个字符
if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ //代表是客户端认证
$ip = $connection->getRemoteIp();
if(!array_key_exists($ip,$clients)){ //必须是之前没有注册过
//$clients[$ip] = $result[1]; //把新用户保存起来, 格式为 ip=>昵称
$clients[$ip] = ['ip'=>$ip,'name'=>$result[1],'conn'=>$connection];
// 向客户端发送数据
$connection->send('notice:success'); //验证成功消息
$connection->send('msg:welcome '.$result[1]); //普通消息
echo $ip .':' .$result[1] .'login' . PHP_EOL; //这是为了演示,控制台打印信息
//一旦有用户登录就把保存的客户端信息发送过去
//$connection->send('users:'.json_encode($clients));
$users = 'users:'.json_encode(array_column($clients,'name','ip')); //准备要广播的数据
foreach($clients as $ip=>$client){
$client['conn']->send($users);
}
}
}elseif(preg_match('/^msg:(.*?)/isU',$data,$msgset)){ //代表是客户端发送的普通消息
if(array_key_exists($connection->getRemoteIp(),$clients)){ //必须是之前验证通过的客户端
echo 'get msg:' . $msgset[1] .PHP_EOL; //这是为了演示,控制台打印信息
if($msgset[1] == 'nihao'){
//如果收到'nihao',就给客户端发送'nihao 用户名'
//给客户端发送普通消息
$connection->send('msg:nihao '.$clients[$connection->getRemoteIp()]);
}
}
}
// 设置连接的onClose回调
$connection->onClose = function($connection) //客户端主动关闭
{
global $clients;
unset($clients[$connection->getRemoteIp()]);
echo "connection closed\n";
};
};
// 运行worker
Worker::runAll();
本文介绍了如何在WebSocket服务端实现用户列表显示及广播消息功能,包括用户验证、客户端连接管理、用户信息广播等关键步骤。
518

被折叠的 条评论
为什么被折叠?



