一、背景:学习+研究
技术栈:Swoole + Linux + jQuery + HTML + CSS
Swoole是高性能的PHP协程框架,适用于高并发、高性能要求的领域,为PHP性能的提升提供了独一无二的可能性。
WebSocket是B/S或C/S网络结构模式下实现双方实时通信的协议,是一种在单个TCP连接上进行全双工通信的协议。Swoole\Websocket\Server 继承自 Swoole\Http\Server,是实现了WebSocket协议的服务器。
只有WebSocket客户端才能与WebSocket服务器通信。
二、类说明
WebScoket 服务器:
Swoole\WebSocket\Server
方法:
onopen() 监听WebSocket连接打开事件
onmessage() 监听WebSocket消息事件
onclose() 监听WebSocket连接关闭事件
push() 推送消息到已连接的客户端
isEstablished() 判断WebSocket连接是否正确
代码片段解析:
1、使用 FILE 作为存储层实现数据存储,当然还可以选择Redis等服务,也可以用Swoole\Table等实现,这里只为学习 WebSocket。
2、发送消息的时候,每个消息都定义一个类型 type,便于客户端解析数据做业务逻辑。
type 类型有:
USER_IN 用户登录
USER_OUT 用户离开
USER_MSG 用户发送的消息
3、$ws->isEstablished 是为了判断连接是否有效,保证推送消息能够成功。
if ($ws->isEstablished($fd)) {
?? ??$ws->push($fd, json_encode($data));
}
4、服务器接收到消息时,服务器会轮询把消息推送给所有用户(包括消息发送者),所以要区分一下消息是否是自己发送的。
foreach ($ws->connections as $item_fd) {
if($item_fd != $frame->fd){
$data = [
//'num' => $num,
'msg' => $frame->data,
'type' => 'USER_MSG',
'from_fd' => $frame->fd
];
// 判断websocket连接是否正确,否则会push失败
if ($ws->isEstablished($item_fd)) {
$ws->push($item_fd, json_encode($data));
}
}else{
$data = [
//'num' => $num,
'msg' => $frame->data,
'type' => 'USER_MSG',
'from_fd' => $frame->fd
];
$ws->push($frame->fd, json_encode($data));
}
}
程序源码:
服务端:PHP代码:ws_chat_server.php
<?php
defined('SWOOLE_SERVER') OR define('SWOOLE_SERVER','127.0.0.1');
// 面向过程编程
// 使用文件缓存 获取用户在线数
function getOnlineUserNum(){
$data = file_get_contents('./chat/user_num.txt');
return $data;
}
// 使用文件缓存 增加用户在线数
function setIncOnlineUserNum($type = null){
if($type == 'init'){
$num = 0;
}else{
$num = getOnlineUserNum() + 1;
}
file_put_contents('./chat/user_num.txt',$num);
return $num;
}
// 使用文件缓存 减少用户在线数
function setDecOnlineUserNum($type = null){
if($type == 'init'){
$num = 0;
}else{
$num = getOnlineUserNum() - 1;
}
file_put_contents('./chat/user_num.txt',$num);
return $num;
}
/**
* WebSocket 服务器
*/
setIncOnlineUserNum('init');
//创建websocket服务器对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server(SWOOLE_SERVER, 9504);
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
echo 'WS-'.$request->fd . ' connected. '.PHP_EOL;
$num = setIncOnlineUserNum();
foreach ($ws->connections as $fd) {
// 判断websocket连接是否正确,否则会push失败
if($request->fd == $fd){
$data = [
'num' => $num,
'msg' => $request->fd.' 进入了聊天室',
'fd' => $request->fd,
'type' => 'USER_IN'
];
$ws->push($request->fd, json_encode($data));
}else{
$data = [
'num' => $num,
'msg' => $request->fd.' 进入了聊天室',
'type' => 'USER_IN'
];
if ($ws->isEstablished($fd)) {
$ws->push($fd, json_encode($data));
}
}
}
});
//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
echo "Receive Message: {$frame->data}\n";
foreach ($ws->connections as $item_fd) {
if($item_fd != $frame->fd){
$data = [
//'num' => $num,
'msg' => $frame->data,
'type' => 'USER_MSG',
'from_fd' => $frame->fd
];
// 判断websocket连接是否正确,否则会push失败
if ($ws->isEstablished($item_fd)) {
$ws->push($item_fd, json_encode($data));
}
}else{
$data = [
//'num' => $num,
'msg' => $frame->data,
'type' => 'USER_MSG',
'from_fd' => $frame->fd
];
$ws->push($frame->fd, json_encode($data));
}
}
});
//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "WS-{$fd} is closed.".PHP_EOL;
$num = setDecOnlineUserNum();
$data = [
'num' => $num,
'msg' => $fd.' 离开了聊天室',
'type' => 'USER_OUT'
];
foreach ($ws->connections as $item_fd) {
// 推送给除自己之外的所有人
if($item_fd != $fd){
// 判断websocket连接是否正确,否则会push失败
if ($ws->isEstablished($item_fd)) {
$ws->push($item_fd, json_encode($data));
}
}
}
echo "当前在线人员有:".$num . PHP_EOL;
});
$ws->start();
客户端:HTML代码:ws_chat_server_test.php
<html>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<body>
<style>
.message-body{
border:1px solid #ccc;
width:300px;
height:450px;
}
.msg-server{
background-color:blue;
border:1px solid #ccc;
height:23px;
margin-top:0px;
text-align: center;
word-wrap:break-word;
}
.msg-user{
color:black;
background-color:#00FFCC;
border:1px solid #ccc;
margin-top:0px;
text-align:left;
word-wrap:break-word;
}
.msg-user-mine{
color:black;
background-color:#33CCFF;
border:1px solid #ccc;
margin-top:0px;
text-align: right;
word-wrap:break-word;
}
.msg-green{
background-color:#DDFF77;
border:1px solid #ccc;
height:23px;
margin-top:0px;
text-align: center;
}
.msg-exit{
background-color:#DDDDDD;
border:1px solid #ccc;
height:23px;
margin-top:0px;
text-align: center;
}
.message-send-input{
width:240px;
height:50px;
}
.message-send-button{
background-color:#0000FF;
color:white;
border:2px solid;
border-radius:25px;
width:60px;
height:50px;
}
</style>
当前在线人数:<span id="onlineUser">0</span>人
客户端FD:<span id="onlineUserFD"></span>
<div id="message-body" class="message-body">
</div>
<div>
<div style="float:left;">
<textarea class="message-send-input" id="msg" name="value">
</textarea>
</div>
<div style="float:left;">
<input class="message-send-button" type="button" id="sendBtn" value="发送">
</div>
</div>
<div id="show-logs">
</div>
</body>
<script lang="javascript">
var wsServer = 'ws://127.0.0.1:9504';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) {// 建立连接
showServerInfoSuccess("连接成功.");
};
websocket.onclose = function (evt) {// 关闭连接
showServerInfoFailured("关闭连接");
};
websocket.onmessage = function (evt) {// 接收服务器推送的消息
//showInfo(evt.data);
// 进入或退出
if(JSON.parse(evt.data).type == 'USER_IN'){
if(typeof JSON.parse(evt.data).fd !== 'undefined'){
$("#onlineUserFD").html(JSON.parse(evt.data).fd);
}
$("#onlineUser").html(JSON.parse(evt.data).num);
showServerInfoSuccess(JSON.parse(evt.data).msg);
}
if(JSON.parse(evt.data).type == 'USER_OUT' ){
$("#onlineUser").html(JSON.parse(evt.data).num);
showServerInfoFailured(JSON.parse(evt.data).msg);
}
if(JSON.parse(evt.data).type == 'USER_MSG'){
if(JSON.parse(evt.data).from_fd == $("#onlineUserFD").html()){
var msg = JSON.parse(evt.data).msg
+ ' 说:我';
showUserMessageMine(msg);
}else{
var msg = JSON.parse(evt.data).from_fd
+ ' 说:'
+ JSON.parse(evt.data).msg;
showUserMessage(msg);
}
}
};
websocket.onerror = function (evt, e) {
showInfo('发生错误: ' + evt.data);
};
var msg;
var sendBtn = document.getElementById('sendBtn');
sendBtn.onclick = function(){
if (websocket.readyState === 1) {
msg = $('#msg').val();
websocket.send(msg);// 发送消息到服务器
} else {
alert('WebSocket 连接失败');
}
};
function showInfo(msg){
$("#show-logs").append("<div class='msg-logs'>"+msg+"</div>");
}
function showUserMessage(msg){
$("#message-body").append("<div class='msg-user'>"+msg+"</div>");
}
function showUserMessageMine(msg){
$("#message-body").append("<div class='msg-user-mine'>"+msg+"</div>");
}
function showServerMsg(msg){
$("#message-body").append("<div class='msg-server'>服务器消息:"+msg+"</div>");
}
function showServerInfoSuccess(msg){
$("#message-body").append("<div class='msg-green'>服务器消息:"+msg+"</div>");
}
function showServerInfoFailured(msg){
$("#message-body").append("<div class='msg-exit'>服务器消息:"+msg+"</div>");
}
</script>
</html>
效果图:
用户登录聊天室,自动通知到其他所有用户。
用户离开聊天室,也会通知剩下还在线的所有用户。
用户发送消息,推送到其他所有在线用户。
|