# 基于Swoole的多Room聊天室的程序
[TOC]
## 聊天室Command源碼
~~~
<?php
/**
* Created by PhpStorm.
* Power By Mikkle
* Email:776329498@qq.com
* Date: 2017/11/6
* Time: 11:51
*/
namespace app\base\command;
use app\base\command\webim\WebSocket;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class Server extends Command
{
protected $swoole ;
public function configure(){
$this->setName('socketServer')->setDescription('Here is socket service ');
}
public function execute(Input $input, Output $output)
{
$this->swoole = new WebSocket();
}
}
~~~
## Command源碼文件
詳細教程參見 http://www.hmoore.net/mikkle/thinkphp5_study/376459
~~~
<?php
return [
'app\base\command\Mikkle',
'app\base\command\Server',
];
~~~
## 聊天室后臺源碼
~~~
<?php
/**
* Created by PhpStorm.
* Power By Mikkle
* Email:776329498@qq.com
* Date: 2017/11/7
* Time: 11:04
*/
namespace app\base\command\webim;
use app\base\controller\Redis;
use app\base\controller\Rsa;
use mikkle\tp_swoole\WebsocketServer;
use think\Cache;
use think\Config;
use think\Exception;
use think\Log;
use think\Db;
class WebSocket extends WebsocketServer
{
protected $redis;
protected $userList = "UserOnlineList";
protected $roomName = "defaultRoom";
protected $roomListName = "RoomList";
protected $error;
protected $cache = true;
/**
* 初始化的回調方法
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
*/
protected function initialize(\swoole_websocket_server $server)
{
$redis_config = Config::get("command.redis");
$this->redis = new Redis($redis_config);
$roomList = $this->redis->smembers($this->roomListName);
//主程序啟動 清空所有聊天室在線用戶
if (!empty($roomList) && is_array($roomList)) {
foreach ($roomList as $room) {
$this->redis->delete("{$this->userList}_{$room}");
}
}
//創建內存表
$this->createTable();
}
/**
* 創建鏈接時候的回調方法
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param \swoole_http_request $request
*/
protected function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
{
echo "server: success with fd{$request->fd}\n";
}
/**
* 接收信息回調方法
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param \swoole_websocket_frame $frame
*/
protected function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
{
try {
if (!empty($frame) && $frame->opcode == 1 && $frame->finish == 1) {
$message = $this->checkMessage($frame->data);
if (!$message) {
$this->serverPush($server, $frame->fd, $frame->data, 'message');
}
if (isset($message["type"])) {
switch ($message["type"]) {
case "login":
$this->login($server, $frame->fd, $message["message"], $message["room"]);
break;
case "message":
$this->serverPush($server, $frame->fd, $message["message"], 'message', $message["room"]);
break;
default:
}
$this->redis->sadd($this->roomListName, $message["room"]);
}
} else {
throw new Exception("接收數據不完整");
}
} catch (Exception $e) {
Log::error($e->getMessage());
}
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
}
protected function onRequest(\swoole_websocket_server $server, \swoole_http_request $request, \swoole_http_response $response)
{
// 接收http請求從get獲取message參數的值,給用戶推送
// 通過這個接口 可以對做自定義的管理推送
}
/**
* 退出的回調方法
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param $fd
*/
protected function onClose(\swoole_websocket_server $server, $fd)
{
$this->logout($server, $fd);
$server->close($fd);
echo "client {$fd} closed\n";
}
/**
* 推送同聊天室信息
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param $frame_fd
* @param string $message
* @param string $message_type
*/
protected function serverPush(\swoole_websocket_server $server, $frame_fd, $message = "", $message_type = "message")
{
$push_list = $this->getPushListByFd($frame_fd);
$message = htmlspecialchars($message);
$datetime = date('Y-m-d H:i:s', time());
$user = $this->table->get($frame_fd);
if (isset($user)) {
unset($user["openid"]);
foreach ($push_list as $fd) {
if ($fd == $frame_fd) {
continue;
}
@$server->push($fd, json_encode([
'type' => $message_type,
'message' => $message,
'datetime' => $datetime,
'user' => $user,
])
);
}
}
}
/**
* 獲取同聊天室用戶信息
* Power: Mikkle
* Email:776329498@qq.com
* @param $fd
* @return array
*/
protected function getPushListByFd($fd)
{
$room = $this->table->get($fd, "room");
if (empty($room)) {
return [];
}
return $this->redis->hget("{$this->userList}_{$room}");
}
/**
* 檢測信息并轉換信息
* Power: Mikkle
* Email:776329498@qq.com
* @param $message
* @return array|bool|mixed
*/
protected function checkMessage($message)
{
$message = json_decode($message);
$return_message = [];
if (!is_array($message) && !is_object($message)) {
$this->error = "接收的message數據格式不正確";
return false;
}
if (is_object($message)) {
foreach ($message as $item => $value) {
$return_message[$item] = $value;
}
} else {
$return_message = $message;
}
if (!isset($return_message["type"]) || !isset($return_message["message"])) {
return false;
} else {
if (!isset($return_message["room"])) $return_message["room"] = $this->roomName;
return $return_message;
}
}
/**
* 處理用戶登錄信息
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param $frame_fd
* @param string $message
* @param $room
* @return bool
*/
protected function login(\swoole_websocket_server $server, $frame_fd, $message = "", $room)
{
$open_id = Rsa::instance()->decrypt($message);
if (empty($open_id)) {
return false;
}
$user_info = $this->getUserInfoByOpenId($open_id);
$this->updateFrameFd($frame_fd, $user_info);
$user_info["fd"] = $frame_fd;
$user_info["room"] = $room;
$this->updateFrameFd($frame_fd, $user_info);
$this->createRoomUserList($server, $room, $frame_fd, $open_id);
unset($user_info["openid"]);
$server->push($frame_fd, json_encode(
['user' => $user_info, 'all' => $this->allUserByRoom($room),'type' => 'openSuccess'])
);
$this->serverPush($server, $frame_fd, "歡迎{$user_info['name']}進入聊天室", 'open');
}
/**
* 用戶退出處理
* Power: Mikkle
* Email:776329498@qq.com
* @param \swoole_websocket_server $server
* @param $fd
*/
protected function logout(\swoole_websocket_server $server, $fd)
{
$user = $this->table->get($fd);
if (isset($user)) {
$this->serverPush($server, $fd, $user['name'] . "離開聊天室", 'close');
$this->deleteRoomUserList($user["room"], $user["openid"]);
$this->table->del($fd);
}
}
/**
* 添加至聊天室列表
* Power: Mikkle
* Email:776329498@qq.com
* @param $server
* @param $room
* @param $frame_fd
* @param $openid
*/
protected function createRoomUserList(\swoole_websocket_server $server, $room, $frame_fd, $openid)
{
try {
$fd = $this->redis->hget("{$this->userList}_{$room}", $openid);
if (isset($fd)) {
$user = $this->table->get($fd);
if (isset($user)) {
$this->serverPush($server, $fd, $user['name'] . "離開聊天室", 'close');
$this->table->del($fd);
}
$server->close($fd);
}
$this->redis->hset("{$this->userList}_{$room}", $openid, $frame_fd);
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
/**
* 從在線用戶列表中刪除用戶
* Power: Mikkle
* Email:776329498@qq.com
* @param $room
* @param $openid
*/
protected function deleteRoomUserList($room, $openid)
{
$this->redis->hdel("{$this->userList}_{$room}", $openid);
}
/**
* 獲取所有聊天室在線信息
* Power: Mikkle
* Email:776329498@qq.com
* @param $room
* @return array
*/
protected function allUserByRoom($room)
{
$user_list = $this->redis->hget("{$this->userList}_{$room}");
$users = [];
if (!empty($user_list)) {
foreach ($user_list as $fd) {
$user = $this->table->get($fd);
if (!empty($user)) {
$users[] = $user;
}
}
}
return $users;
}
/**
* 存在在線鏈接信息
* Power: Mikkle
* Email:776329498@qq.com
* @param $frame_fd
* @param $user_info
*/
protected function updateFrameFd($frame_fd, $user_info)
{
$this->table->set($frame_fd, $user_info);
}
/**
* 獲取微信用戶
* Power: Mikkle
* Email:776329498@qq.com
* @param $open_id
* @return array|false|mixed|\PDOStatement|string|\think\Model
*/
protected function getUserInfoByOpenId($open_id)
{
$user_info = Cache::get("WeFans:{$open_id}");
if (empty($user_info) || $this->cache == false) {
$user_info = Db::table("mk_we_fans")->where(["openid" => $open_id, "status" => 1])->field("openid,nickname_json,nickname,headimgurl as avatar")->find();
if (!$user_info) return [];
if (isset($user_info["nickname_json"]) && !empty(json_decode($user_info["nickname_json"]))) {
$user_info["name"] = json_decode($user_info["nickname_json"]);
} else {
$user_info["name"] = $user_info["nickname"];
}
unset($user_info["nickname_json"]);
unset($user_info["nickname"]);
Cache::set("WeFans:{$open_id}", $user_info);
}
return $user_info;
}
/**
* 創建內存表 存在在線用戶
* Power: Mikkle
* Email:776329498@qq.com
*/
protected function createTable()
{
$this->table = new \swoole_table(1024);
$this->table->column('fd', \swoole_table::TYPE_INT);
$this->table->column('openid', \swoole_table::TYPE_STRING, 100);
$this->table->column('name', \swoole_table::TYPE_STRING, 255);
$this->table->column('avatar', \swoole_table::TYPE_STRING, 255);
$this->table->column('room', \swoole_table::TYPE_STRING, 100);
$this->table->create();
}
}
~~~
## 聊天室前端源碼
~~~
<?php
/**
* Created by PhpStorm.
* Power By Mikkle
* Email:776329498@qq.com
* Date: 2017/9/20
* Time: 15:26
*/
namespace app\center\controller;
use app\base\controller\Rsa;
use think\Exception;
class ChatRoom extends WeAuth
{
public function index()
{
$assign_data = [
//你自己服務器的地址
"server"=>"ws://wechat.com:9501",
"open_id"=>Rsa::instance()->encrypt($this->open_id),
];
$this->assign($assign_data);
return $this->fetch('chatRoom/index');
}
}
~~~
## 聊天室模版
~~~
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>在線聊天室</title>
{include file="base_mui/chat_room"/}
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<![endif]-->
</head>
<body id="webim" >
<div class="col-xs-12 " style="padding-left: 0px;padding-right: 0px">
<div class="panel panel-info">
<!--<div class="panel-heading" style="text-align: center">-->
<!--YZF聊天室-->
<!--</div>-->
<div class="panel-body no-padding">
<div class="col-xs-2 user-list">
</div>
<div class="col-xs-10 no-padding">
<div class="chat-list">
</div>
</div>
</div>
<div class="message" >
<div class="text" style="border: 1px solid black">
<textarea></textarea>
</div>
<div class="send">
發送
</div>
</div>
</div>
</div>
<script>
var uuid = "{$open_id}";
$(function() {
webim.init();
$(".message .send").click(function(){
webim.sendMsg();
});
});
var config = {
server : '{$server}'
};
var webim = {
data : {
wsServer : null,
info : {}
},
init : function() {
this.data.wsServer = new WebSocket(config.server);
this.open();
this.close();
this.messages();
this.error();
},
open : function() {
this.data.wsServer.onopen = function(evt) {
webim.notice('連接成功');
webim.data.wsServer.send(webim.toJson("login",uuid));
}
},
close : function() {
this.data.wsServer.onclose = function(evt) {
webim.notice('不妙,鏈接斷開了');
}
},
messages : function() {
this.data.wsServer.onmessage = function (evt) {
var data = jQuery.parseJSON(evt.data);
switch (data.type) {
case 'open':
webim.appendUser(data.user.name, data.user.avatar, data.user.fd);
webim.notice(data.message);
console.log(data);
break;
case 'close':
webim.removeUser(data.user.fd);
webim.notice(data.message);
console.log(data);
break;
case 'openSuccess':
webim.data.info = data.user;
webim.showAllUser(data.all);
console.log(data);
break;
case 'message':
webim.newMessage(data);
console.log(data);
break;
}
};
},
error : function() {
this.data.wsServer.onerror = function (evt, e) {
console.log('Error occured: ' + evt.data);
};
},
removeUser: function(fd) {
$(".fd-"+fd).remove();
},
showAllUser: function(users) {
for (i in users) {
this.appendUser(users[i].name, users[i].avatar, users[i].fd);
}
},
toJson:function (type,msg) {
var data={
"type":type,
"message":msg,
}
console.log(JSON.stringify(data));
return JSON.stringify(data);
},
sendMsg : function() {
var text = $(".message .text textarea");
var msg = text.val();
if ($.trim(msg) == '') {
this.layerErrorMsg('請輸入消息內容');
return false;
}
this.data.wsServer.send(this.toJson("message",msg));
var html = '<div class="col-xs-10 msg-item ">'
+'<div class="col-xs-1 no-padding pull-right">'
+'<div class="avatar">'
+'<img src="'+this.data.info.avatar+'" width="50" height="50" class="img-circle">'
+'</div>'
+'</div>'
+'<div class="col-xs-11" style="padding-right: 0px;padding-left: 0">'
+'<div class="col-xs-12" style="padding-right: 0px;padding-left: 0">'
+'<div class="username pull-right">'+this.data.info.name+'</div>'
+'<div>'
+'<div class="col-xs-12 no-padding" style="padding-left: 50px">'
+'<div class="msg pull-right">'+msg+'</div>'
+'</div>'
+'</div>';
$('.chat-list').append(html);
this.appendUser(this.data.info.name, this.data.info.avatr, this.data.info.fd);
this.scrollBottom();
text.val('');
},
newMessage : function(data) {
this.appendUser(data.user.name, data.user.avatar, data.user.fd);
var html = '<div class="col-xs-10 msg-item ">'
+'<div class="col-xs-1 no-padding">'
+'<div class="avatar">'
+'<img src="'+data.user.avatar+'" width="50" height="50" class="img-circle">'
+'</div>'
+'</div>'
+'<div class="col-xs-11 no-padding">'
+'<div class="col-xs-12">'
+'<div class="username">'+data.user.name+'</div>'
+'</div>'
+'<div class="col-xs-12 no-padding">'
+'<div class="msg">'+data.message+'</div>'
+'</div>'
+'</div>'
+'</div>';
$('.chat-list').append(html);
this.scrollBottom();
},
scrollBottom : function() {
$('.chat-list').scrollTop($('.chat-list')[0].scrollHeight );
},
notice : function(msg) {
var html ='<div class="col-xs-12 notice text-center">'+msg+'</div>';
$('.chat-list').append(html);
this.scrollBottom();
},
appendUser : function(name, avatar, fd) {
if ($(".fd-"+fd).length > 0) {
return true;
}
var html = ' <div class="user-item fd-'+fd+'">'
+'<div class="avatar">'
+'<img src="'+avatar+'" width="50" height="50" class="img-circle">'
+'</div>'
+'<div class="user-name">'+name+'</div>'
+'</div>';
$(".user-list").append(html);
$('.user-list').scrollTop($('.user-list')[0].scrollHeight );
},
layerSuccessMsg : function(msg) {
layer.msg(msg, {time: 1000, icon:6});
},
layerErrorMsg : function(msg) {
layer.msg(msg, {time: 1000, icon:5});
}
};
var html = document.querySelector('html');
var rem = html.offsetWidth / 7.2;
html.style.fontSize = rem + "px";
</script>
</body>
</html>
~~~
- 序言及更新日志
- 前言一 開發PHP必備的環境(你可以不看)
- LinUX系統ThinkPHP5鏈接MsSQL數據庫的pdo_dblib擴展
- centos7.2掛載硬盤攻略
- Centos系統Redis安裝及Redis的PHP擴展安裝
- Centos系統增加Swap(系統交換區)的方法
- 前言二 開發PHP軟件配置和介紹(你依然可以不看)
- 數據庫SQL文件
- 本地Git(版本控制)的搭建
- GIT遠程倉庫的克隆和推送
- Git常用命令
- PHP面向對象思想實戰經驗領悟
- PHP面向對象實戰----命名空間
- PHP面向對象實戰----繼承
- 基類實戰--底層方法封裝
- 基類實戰--構造函數實戰
- 基類實戰--析構函數的使用
- TP5實戰開發前篇---控制器(controller)
- 控制器中Request類的使用
- 控制器中基類的使用
- TP5實戰開發前篇---模型篇(model)
- TP5實戰開發前篇---驗證器篇(Validate)
- TP5實戰課程入門篇---花拳繡腿
- 模塊以及類的文件的建立
- Api開發------單條信息顯示
- Api開發---單條信息復雜關聯顯示
- Api開發---查詢信息緩存Cache的應用
- TP5實戰技巧---開發思路 引路造橋
- TP5實戰技巧---整合基類 化繁為簡
- TP5實戰課程入門篇---數據操作
- Api開發---數據的添加和修改
- API開發---快速開發API通用接口
- TP5專用微信sdk使用教程
- THINKPHP5微信SDK更新記錄及升級指導
- TP5專用SDK 微信參數配置方法
- 微信公眾號推送接口對接教程
- 微信推送接口對接示例含掃描登錄微信端部分
- TP5專用微信支付SDK使用簡介
- TP5專用支付寶支付SDK使用說明
- 使用NW將開發的網站打包成桌面應用
- TP5高階實戰課程 進階篇概述
- 進階篇一 實戰開發之習慣及要求
- 進階篇二 實戰開發之控制器
- 控制器基類之控制器基類使用方法
- 控制器基類之控制器基類常用方法分享
- 控制器基類之構造函數的使用方法
- 進階篇三 實戰開發之權限控制
- TP5實戰源碼 --- 全局用戶信息驗證類Auth
- TP5實戰源碼 --- 微信Auth實戰開發源碼
- 進階篇四 實戰開發之模型
- 模型基類之模型基類的用途
- 模型基類之常用數據處理方法
- 模型邏輯層之實戰代碼(含事務)
- 模型實戰開發之模型常用方法
- 模型實戰源碼 --- 樂觀鎖的應用
- 模型實戰技巧---Model事件功能的使用
- 模型事件實戰應用---數據庫操作日志
- 進階篇五 實戰開發之緩存(Cache)
- TP5實戰源碼---應用緩存獲取城市信息
- TP5實戰源碼---應用緩存獲取分類詳情
- 進階篇六 TP5類庫的封裝和使用
- DataEdit快捷操作類庫
- ShowCode快捷使用類庫
- 阿里大于 短信API接口 TP5專用類庫
- DatabaseUpgrade數據庫對比及更新類庫
- AuthWeb權限類使用說明
- 進階篇七 服務層的應用
- 服務層源碼示例
- 服務層基類源碼
- 進階篇八 應用層Redis數據處理基類
- Redis服務層基類源碼
- 進階篇九 使用Redis類庫處理一般的搶購(秒殺)活動示例
- 進階篇十 某大型項目應用本Redis類源碼示例(含事務 樂觀鎖)
- 進階篇十一 邏輯層的應用
- 邏輯層基類源碼
- 進階篇 服務層代碼示例
- 高階實戰課程 進階篇持續新增中
- 高階篇一 TP5命令行之守護任務源碼
- TP5實戰源碼 --- 命令行
- TP5實戰源碼 --- 通過shell建立PHP守護程序
- 高階篇二 使用Redis隊列發送微信模版消息
- 高階篇二 之 Worker隊列基類源碼
- 高階篇三 TP5實戰之Redis緩存應用
- Redis實戰源碼之Hash專用類庫源碼
- Redis實戰源碼之Model類結合
- Redis實戰源碼之模型Hash基類源碼
- Redis實戰源碼之Hash查詢使用技巧
- Redis實戰源碼之 shell腳本中redis賦值和取值
- 高階篇四 Swoole的實戰應用
- swoole基類代碼
- Swoole擴展WebsocketServer專用類
- 基于Swoole的多Room聊天室的程序
- Swoole守護服務shell源碼
- 高階篇五 命令行異步多進程隊列類的應用
- tp_worker類源碼
- WorkerBase
- WorkerCommand
- WorkerRedis
- Redis類
- CycleWorkBase
- WorkerHookBase異步鉤子
- 隊列日志SQL
- 高階篇六 定時執行隊列類庫以及使用方法
- 定時隊列類庫源碼
- 高階篇七 異步執行循環隊列類庫以及使用教程
- CycleWorkBase源碼
- 高階實戰課程 進階篇持續新增中
- Extend便捷類庫源碼庫
- 阿里相關類庫
- SendSms--驗證碼API接口文件
- 權限相關類庫目錄
- AuthWeb 權限驗證類庫
- Redis便捷操作類庫(20171224更新)
- Redis
- Tools工具類庫集
- Curl類庫
- DataEdit
- Rand類庫
- ShowCode類庫
- Upload類庫
- 附件集合
- 附件一:微信支付 實戰開發源碼
- 微信支付類庫源代碼
- Common_util_pub.php
- DownloadBill_pub.php
- JsApi_pub.php
- NativeCall_pub.php
- NativeLink_pub.php
- OrderQuery_pub.php
- Refund_pub.php
- RefundQuery_pub.php
- SDKRuntimeException.php
- ShortUrl_pub.php
- UnifiedOrder_pub.php
- Wxpay_client_pub.php
- Wxpay_server_pub.php
- WxPayConf_pub.php
- 微信支付回調頁面源碼
- 附件二 順豐快遞BSP接口實戰開發源碼
- 順豐快遞BSP接口實戰開發源碼
- 順豐BSP基類
- 順豐BSP基礎代碼
- 順豐BSP下單接口
- 順豐BSP查單接口
- 順豐BSP確認/取消接口
- 附件三 APP注冊登陸接口源碼(含融云平臺接口)
- 附件四 TP5訂單Model(含事務 獲取器 修改器等方法)
- 附錄五 RSA加密解密
- Rsa文件源碼
- 附件六 阿里大于短信接口
- 附件七 AES加解密類
- AES加解密類源碼
- 附件八 TP5路由設置源碼
- 附件九 TP5 Excel導入導出下載便捷類庫
- Excel類庫TP5源碼
- 附件十 TP5便捷操作Redis類庫源碼
- TP5源碼 Redis操作便捷類庫
- 附件十一 TP5源碼 上傳文件入庫類源碼
- 上傳類Upload源碼
- Upload類上傳配置文件
- 存儲圖像文件的數據庫SQL文件
- 存儲文件的數據庫SQL文件
- 附件十二 TP5 圖片處理增強類 支持縮略圖在線顯示
- 附件十三 微信推送消息接口類庫源碼
- 附件十三 微信推送消息接口類庫源碼 之 基類
- 附件十四 存儲微信昵稱的處理方法