使用php+swoole+redis 简单实现网页即时聊天,需要浏览器支持html5的websocket,
websocket是不同于http的另外一种网络通信协议,能够进行双向通信,基于此,可开发出各种实时通信产品,简单做了个聊天demo,顺便分享一下
效果图如下:
环境:
- 系统 centos7.5
- php7.2.9
- redis5.0.0
- swoole4.2.2
- nginx 1.8
参考文档:
- redis官网
- 教程
- swoole 官网
- swoole 的webSocket手册:
- php扩展库地址
IP与端口:
- 虚拟机的IP: 192.168.1.100
- webSocket服务端口是 9520
- redis服务端口是 6379
服务器端代码 websocket.php
<?php
class Server
{
private $serv;
private $conn = null;
private static $fd = null;
public function __construct()
{
$this->redis_connect();
$this->serv = new swoole_websocket_server("0.0.0.0", 9502);
$this->serv->set(array(
'worker_num' => 8,
'daemonize' => false,
'max_request' => 10000,
'dispatch_mode' => 2,
'debug_mode' => 1
));
echo "start \n";
$this->serv->on('Open', array($this, 'onOpen'));
$this->serv->on('Message', array($this, 'onMessage'));
$this->serv->on('Close', array($this, 'on close '));
$this->serv->start();
}
function onOpen($server, $req)
{
echo "connection open: {$req->fd} \n";
// $server->push($req->fd, json_encode(33));
}
public function onMessage($server, $frame)
{
//echo "received data $frame->data \n";
//$server->push($frame->fd, json_encode(["hello", "world"]));
$pData = json_decode($frame->data,true);
$fd=$frame->fd;
if(empty($pData)){
echo "received data null \n";
return;
}
echo "received fd=>{$fd} message: {$frame->data}\n";
$data = [];
if (isset($pData['content'])) {
$f_fd = $this->getFd($pData['fid']); //获取绑定的fd
$data = $this->add($pData['uid'], $pData['fid'], $pData['content']); //保存消息
$server->push($f_fd, json_encode($data)); //推送到接收者
$json_data=json_encode($data);
echo "推送到接收者 fd=>{$f_fd} message: {$json_data}\n";
} else {
$this->unBind($pData['uid']); //首次接入,清除绑定数据
if ($this->bind($pData['uid'], $fd)) { //绑定fd
$data = $this->loadHistory($pData['uid'], $pData['fid']); //加载历史记录
} else {
$data = array("content" => "无法绑定fd");
}
}
$json_data=json_encode($data);
echo "推送到发送者 fd=>{$fd} message: {$json_data}\n";
$server->push($fd, json_encode($data)); //推送到发送者
}
public function onClose($server, $fd)
{
//$this->unBind($fd);
echo "connection close: {$fd}\n";
}
/*******************/
/**
* redis
* @param string $host
* @param string $port
* @return bool
*/
function redis_connect($host='127.0.0.1',$port='6379')
{
$this->conn = new Redis();
try{
$this->conn->connect($host, $port);
}catch (\Exception $e){
user_error(print_r($e));
}
return true;
}
/**
* 保存消息
* @param $uid 发送者uid
* @param $fid 接收者uid
* @param $content 内容
* @return array
*/
public function add($uid, $fid, $content)
{
$msg_data=[];
$msg_data['uid']=$uid;
$msg_data['fid']=$fid;
$msg_data['content']=$content;
$msg_data['time']=time();
$key=K::KEY_MSG;
$data=$this->conn->get($key);
if(!empty($data)){
$data=json_decode($data,true);
}else{
$data=[];
}
$data[]=$msg_data;
$this->conn->set($key,json_encode($data));
$return_msg[]=$msg_data;
return $return_msg;
}
/**
* 绑定FD
* @param $uid
* @param $fd
* @return bool
*/
public function bind($uid, $fd)
{
$key=K::KEY_UID."{$uid}";
$ret=$this->conn->set($key,$fd);
if(!$ret){
echo "bind fail \n";
return false;
}
return true;
}
/**
* 获取FD
* @param $uid
* @return mixed
*/
public function getFd($uid)
{
$key=K::KEY_UID."{$uid}";
$fd=$this->conn->get($key);
return $fd;
}
/**
* 清除绑定
* @param $uid
* @return bool
*/
public function unBind($uid)
{
$key=K::KEY_UID."{$uid}";
$ret=$this->conn->delete($key);
if(!$ret){
return false;
}
return true;
}
/**
* 历史记录
* @param $uid
* @param $fid
* @param null $id
* @return array
*/
public function loadHistory($uid, $fid)
{
$msg_data=[];
$key=K::KEY_MSG;
$this->conn->delete($key);
$data=$this->conn->get($key);
if($data){
echo $data;
$json_data=json_decode($data,true);
foreach ($json_data as $k=>$info){
if(($info['uid']==$uid&&$info['fid']==$fid)||($info['uid']==$fid&&$info['fid']==$uid)){
$msg_data[] = $info;
}
}
}
return $msg_data;
}
}
//Key 定义
class K{
const KEY_MSG = 'msg_data';
const KEY_FD = 'fd_data';
const KEY_UID = 'uid';
}
//启动服务器
$server = new Server();
客户端代码 chat. html
<!DOCTYPE html> <html lang="en"> <html> <head> <title>CHAT A</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="jquery.min.js"></script> <script src="jquery.json.min.js"></script> <style type="text/css"> .talk_con{ width:600px; height:500px; border:1px solid #666; margin:50px auto 0; background:#f9f9f9; } .talk_show{ width:580px; height:420px; border:1px solid #666; background:#fff; margin:10px auto 0; overflow:auto; } .talk_input{ width:580px; margin:10px auto 0; } .whotalk{ width:80px; height:30px; float :left; outline:none; } .talk_word{ width:420px; height:26px; padding :0px; float:left; margin-left:10px; outline:none; text-indent:10px; } .talk_sub{ width:56px; height:30px; float:left; margin-left:10px; } .close{ width:56px; height:30px; float:left; margin-left:10px; } .atalk{ margin:10px; } .atalk span{ display:inline-block; background:#0181cc; border-radius:10px; color:#fff; padding:5px 10px; } .btalk{ margin:10px; text-align:right; } .btalk span{ display:inline-block; background:#ef8201; border-radius:10px; color:#fff; padding:5px 10px; } </style> <script type="text/javascript"> var uid = 'A'; //发送者uid var fid = 'B'; //接收者uid var wsUrl = 'ws://192.168.1.100:9502'; var webSocket = new WebSocket(wsUrl); //创建Socket webSocket.onopen = function (event) { console.log('onOpen=' + event.data); //webSocket.send("hello webSocket"); initData(); //初始化数据,加载历史记录 }; //接收数据事件 webSocket.onmessage = function (event) { console.log('onMessage=' + event.data); loadData($.parseJSON(event.data)); //导入消息记录,加载新的消息 } //关闭socket webSocket.onclose = function (event) { console.log('close'); }; //socket连接错误 webSocket.onerror = function (event) { console.log('error-data:' + event.data); } //======================================================== //向服务器发送数据 function sendMsg() { var pData = { content: document.getElementById('content').value, uid: uid, fid: fid, } if (pData.content == '') { alert("消息不能为空"); return; } webSocket.send($.toJSON(pData)); //发送消息 } function initData() { //var Who = document.getElementById("who").value; console.log('initData uid:' + uid + ' fid:'+fid); var pData = { uid: uid, fid: fid, } webSocket.send($.toJSON(pData)); //获取消息记录,绑定fd var html = '<div class="atalk"><span id="asay">' + 'WebSocket连接成功' + '</div>'; $("#words"). append (html); } function loadData(data) { for (var i = 0; i < data.length; i++) { if(data[i].uid=='A'){ var html = '<div class="atalk"><span id="asay">' + data[i].uid + '说: ' + data[i].content + '</div>'; }else{ var html = '<div class="btalk"><span id="asay">' + data[i].uid + '说: ' + data[i].content + '</div>'; } $("#words").append(html); } } //关闭连接 function closeWebSocket() { console.log('close'); webSocket.close(); var html = '<div class="atalk"><span id="asay">' + '已和服务器断开连接' + '</div>'; $("#words").append(html); } </script> </head> <body> <div class="talk_con"> <div class="talk_show" id="words"> <!--<div class="atalk"><span id="asay">A说:吃饭了吗?</span></div>--> <!--<div class="btalk"><span id="bsay">B说:还没呢,你呢?</span></div>--> </div> <div class="talk_input"> <!--<select class="whotalk" id="who">--> <!--<option value="A" selected="selected">A说:</option>--> <!--<option value="B">B说:</option>--> <!--</select>--> <button class="close" onclick="closeWebSocket()">断开</button> <input type="text" class="talk_word" id="content"> <input type="button" onclick="sendMsg()" value="发送" class="talk_sub" id="talksub"> </div> </div> </body> </html>
文件详情 :
- 再复制一份客户端,修改一下发送者与接收者的uid,即可进行模拟实时聊天。
- 此代码已经实现了加载历史记录的功能
使用方法:
安装完php、redis和swoole扩展之后,直接执行:
并可以观察下输出,看看websocket服务器是否正常