这两天在学习swoole,作为练手弄了个swoole + 浏览器webSocket 实现的聊天室,效果如下:
环境:php7.3 swoole4.4.3 hredis
参考代码下载地址 https://download.csdn.net/download/shenymce/11565378
WebScketServer.php文件
<?php
namespace App\swoole;
use App\swoole\hander\SockectHander;
use Swoole\Coroutine\Redis;
class WebSocketServer
{
private static $server;
private static $instance;
private function __construct($port = 9517){
self::$server = new \swoole_websocket_server("0.0.0.0",$port);
self::$server->on("open",[$this,"onOpen"]);
self::$server->on("message",[$this,"onMessage"]);
self::$server->on("managerStop",[$this,"onManagerStop"]);
self::$server->on("close",[$this,"onClose"]);
}
public static function getInstance($port = 9517){
if(!self::$instance instanceof self){
self::$instance = new self($port);
}
return self::$instance;
}
public function onOpen($server, $request){
}
public function onMessage($server, $frame){
$frame->data;
$data = json_decode($frame->data,true); // 处理接收到的服务器数据
if(is_array($data)){
// var_dump($data);
// 实现热加载,不需要重启服务器就可以加载 SockectHander 中修改的代码
if(isset($data["debug"]) && $data["debug"]){
self::$server->reload();
// var_dump("set debug !!");
// 服务器重新加载会耗时,因此等待3秒
sleep(3);
}
$sendData = compact("server", "data");
//var_dump($sendData);
if(method_exists(new SockectHander(),$data["cmd"])){
call_user_func([new SockectHander(),$data["cmd"]], $frame->fd, $sendData);
}
}else{
// 用户设置昵称是输出格式错误处理
self::$server->push($frame->fd,json_decode(["msg"=>"参数格式错误!","errCode"=>"-1"]));
}
}
public function onClose($server, $fd){
// 断开连接处理redis
go(function () use ($fd){
echo $fd."closed\n";
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$userlist = json_decode($redis->get("userlist"),true);
$username = isset($userlist[$fd])?$userlist[$fd]:"";
unset($userlist[$fd]);
if($username){
foreach ($userlist as $k => $item) {
if(self::$server->connection_info($k)) {
self::$server->push($k, json_encode(["msg" => "用户 <b style='color: red;padding: 0 5px;'>" . $username . "</b> 离开聊天室!", "userlist" => $userlist]));
}else{
unset($userlist[$k]); // 清空已下线用户
}
}
}
$redis->set("userlist",json_encode($userlist));
});
}
public function onManagerStop($server){
// 清空所有用户信息
$this->clearUserlist();
}
public function clearUserlist(){
// 清空所有用户信息
go(function (){
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$redis->del("userlist");
});
}
public function start(){
self::$server->start();
}
}
SockectHander.php文件
<?php
namespace App\swoole\hander;
use Swoole\Coroutine\Redis;
class SockectHander
{
// 用户设置昵称
public function setName($fd, $data){
if(isset($data["data"]["data"]["name"]) && $username = $data["data"]["data"]["name"]){
go(function () use ($data, $fd, $username){ // 使用协程方便操作redis
$server = $data["server"];
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
// 获得已登录的用户列表
$userlist = json_decode($redis->get("userlist"), true)?:[];
if(in_array($username, $userlist)){
$server->push($fd,json_encode(["msg"=>"用户昵称已经存在,昵称设置失败!","errCode"=>"2"]));
}else{
// 添加用户到已登录列表
$userlist[$fd] = $username;
// 便利发送数据到所有用户
foreach ($userlist as $k => $v) {
// 判断是不是自己发的消息。并获得自己的fd
$isMe = false;
if($fd == $k){
$isMe = $fd;
}
// 判断用户状态,只发送给在线用户
if($server->connection_info($k)){
$server->push($k, json_encode(["msg"=>"欢迎用户 <b style='color: red;padding: 0 5px;'>".$username."</b> 加入聊天室!","userlist"=>$userlist,"isMe"=>$isMe]));
}else{
unset($userlist[$k]); // 清空已下线用户
}
}
$redis->set("userlist", json_encode($userlist));
}
});
}else{
// 用户设置昵称是输出格式错误处理
$data["server"]->push($fd,json_encode(["msg"=>"参数格式错误,用户昵称设置失败!","errCode"=>"1"]));
}
}
// 用户发送数据进行聊天
public function sendMsg($fd, $data){
if(isset($data["data"]["data"]["to"]) && $to = $data["data"]["data"]["to"]){ // 私聊
$server = $data["server"];
$msg = $data["data"]["data"]["msg"];
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
// 获得已登录的用户列表
$userlist = json_decode($redis->get("userlist"), true);
/**
* 参数说明
* msg 消息内容
* from 发送人
**/
$sendData = ["msg" => $msg,"from"=>$userlist[$fd]];
$status = $server->connection_info($to);
if($status){
// 发送消息给指定用户
$server->push($to, json_encode($sendData));
}else{
$sendData1["msg"] = "指定的用户已下线";
$sendData1["errCode"] = 9;
$server->push($fd, json_encode($sendData1));
}
}else{ // 发送给所有人
go(function () use ($data,$fd) { // 使用协程方便操作redis
$server = $data["server"];
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
// 获得已登录的用户列表
$userlist = json_decode($redis->get("userlist"), true);
$msg = $data["data"]["data"]["msg"];
/**
* 参数说明
* msg 消息内容
* from 发送人
**/
$sendData = ["msg" => $msg,"from"=>$userlist[$fd]];
// 便利发送数据到所有用户
foreach ($userlist as $k => $v) {
// 发送给非自己之外的所有人
if($fd != $k){
if($server->connection_info($k)) {
$server->push($k, json_encode($sendData));
}else{
unset($userlist[$k]); // 清空已下线用户
}
}
}
$redis->set("userlist", json_encode($userlist));
});
}
}
}
服务器调用页面主要代码
$obj = \App\swoole\WebSocketServer::getInstance();
$obj->start();
sokect.html文件
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title id="myTitle">swoole + webSocket 实现的在线聊天室</title>
</head>
<style rel="stylesheet" type="text/css">
body {background-color: #eeeeee;}
.master{min-width: 400px;}
h1 {font-size: 25px;color: #750e0e;}
#myname, #text {width: 80%;height: 35px;padding: 5px 10px;box-sizing: border-box;border-radius: 5px;font-weight: bold;margin: 0;border: 0;}
.sendButton {width: 19%;line-height: 35px;box-sizing: border-box;background-color: #c41b1b;margin: 0;color: #fff;border: 0;border-radius: 5px;}
#setName, #sendMsg {background-color: #2d1d2d;line-height: 50px;box-sizing: border-box;padding: 15px;white-space: nowrap;}
.show_send {box-sizing: border-box;margin: 10px auto;}
.flex{display: flex;}
#msg {flex: 8;height: 280px;padding: 5px 10px;box-sizing: border-box;border-radius: 5px;font-weight: bold;background-color: #fff;overflow-y: auto;padding-bottom: 20px;}
.users{flex: 2;background-color: #153f48;}
.userlist {list-style: none;padding:0;line-height: 35px;box-sizing: border-box;margin: 0;color: #fff;border: 0;border-radius: 5px;overflow-y: auto;height: 239px;}
.userlist li{cursor: pointer;box-sizing: border-box;padding: 2px 10px;white-space: nowrap;overflow: hidden;border-bottom: 1px solid;}
.userlist .item:hover, .userlist .item.active{background-color: #333;}
.users .title{background-color: #213162;height: 40px;border-bottom:1px solid;border-bottom: 1px solid; color: #fff;line-height: 40px;padding: 0 10px;}
.footer{position: fixed;width: 100%;bottom: 0;padding: 20px;background-color: #fff;font-size: 14px;font-weight: bold;left: 0;}
</style>
<body>
<div class="master">
<h1>swoole + webSocket 实现的在线聊天室</h1>
<!--发送信息-->
<div id="sendMsg" class="main box-shadow" style="display:none;">
<div class="flex">
<div id="msg"></div>
<div class="users">
<div class="title">在线用户</div>
<ul class="userlist"></ul>
</div>
</div>
<div class="show_send">
<input title="msg" type="text" id="text">
<input class="sendButton sendMsg" value="发送消息" type="submit">
</div>
</div>
<!--设置昵称-->
<div id="setName" class="box-shadow">
<input title="username" type="text" id="myname"/>
<input type="submit" class="setUserName sendButton" value="设置昵称">
</div>
</div>
<div class="footer">
操作说明:<br>
1、聊天室内用户昵称不可重名。<br>
2、用户昵称设置成功后进入聊天界面,右侧将显示所有在线用户。<br>
3、单击右侧用户可进行私聊,发送的消息只私聊用户可见。<br>
4、双击私聊用户退出私聊,发送的消息全局可见。<br>
5、代码提供 <a href="mailto:shenymce@qq.com">shenymce</a>
</div>
</body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
var wsServer = 'ws://192.168.2.100:9517';
var toFd = 0;
var toName = "";
var myfd = 0;
var socket = new WebSocket(wsServer);
// 连接服务器成功
socket.onopen = function (evt) {
console.log(socket.readyState);
};
// 格式化当期日期成 2019-01-01 01:01:01
function nowData() {
var d = new Date();
return d.getFullYear()+"-"+(d.getMonth()+1)+"-"+d.getDate()+" "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds();
}
// 接收到服务器消息
socket.onmessage = function (evt) {
var data = JSON.parse(evt.data);
console.log(data);
var html = "";
if(data.isMe){
myfd = data.isMe;
}
if(data.userlist){
for(var key in data.userlist){
// 右侧不显示用户自己
if(myfd != key){
html +="<li class='item' data-id='"+key+"'>"+data.userlist[key]+"</li>";
}
}
var objUserlist = $(".userlist");
objUserlist.html(html).find(".item").click(function () {
$(".userlist .item").removeClass("active");
$(this).addClass("active");
toFd = $(this).data("id");
toName = $(this).text();
});
objUserlist.find(".item").dblclick(function () {
$(this).removeClass("active");
toFd = 0;
toName = "";
});
}
if(data.from){
formatMsg(data.msg,"r",data.from);
}else{
formatMsg(data.msg,"r");
}
if(data.errCode == 2){
setTimeout(function () {
$("#myname").val("");
$("#setName").show();
$("#sendMsg").hide();
},3000)
}
};
socket.onclose = function (evt) {
alert("连接服务器失败!");
};
socket.onerror = function (evt, e) {
alert('错误:' + evt.data);
};
function formatMsg(msg,flag,from) {
var html = "<div style='font-size: 16px;'>";
var align = "left";
var color = "green";
if(flag == "right" || flag == "r"){
align = "right";
color = "blue";
}
var fromUser = "";
if(from){
fromUser = "<b style='color: red;padding: 0 10px;'> "+from+" </b>"
}
var toStr = "";
if(toName && !from){
toStr = "<b style='color: red;padding: 0 10px;'> "+toName+" </b>"
}
html += "<div style='height:25px;color:"+color+"; text-align: "+align+"'>" + fromUser + nowData() + toStr + "</div>";
html += "<div style='height:25px;color:#000; text-align: "+align+"'>"+msg+"</div>";
html += "</div>";
var obj = $("#msg");
var omsg = obj.html();
obj.html(omsg + html).scrollTop(100000000000000);// 自动滚动到最底部
}
$(function () {
$("#myname").keydown(function (e) {
if(e.keyCode == 13){
$(".setUserName").click();
}
});
$("#text").keydown(function (e) {
if(e.keyCode == 13){
$(".sendMsg").click();
}
});
// 设置用户名称,登录到聊天室
$(".sendMsg").click(function () {
var msg = $("#text").val();
if (!msg) {
return false;
}
/**
* 数据说明
* cmd 服务器要调用的方法名
* debug 是否进行热更新,服务器端hander有更新是设置成 1 可选参数
* data 服务器要处理的数据
* @type {string}
*/
var data = {
cmd: "sendMsg",
debug: 0,
data: {
msg: msg
}
};
if(toFd > 0){
data.data.to = toFd;
}
socket.send(JSON.stringify(data));
formatMsg(msg);
$("#text").val("");
});
// 设置用户名称,登录到聊天室
$(".setUserName").click(function () {
var username = $("#myname").val();
if (!username) {
alert("请先设置名称!");
return false;
}
$("#myTitle").html(username);
/**
* 数据说明
* cmd 服务器要调用的方法名
* debug 是否进行热更新,服务器端hander有更新是设置成 1 可选参数
* data 服务器要处理的数据
* @type {string}
*/
var data = {cmd: "setName",debug: 0,data: {name: username}};
socket.send(JSON.stringify(data));
formatMsg("用户设置昵称!");
formatMsg("等待服务器回应!");
$("#setName").hide();
$("#sendMsg").show();
});
});
</script>
</html>
参考代码下载地址 https://download.csdn.net/download/shenymce/11565378