在開始之前,有句話想說,曾經我以爲socket會很難入門,所以爲了節省時間,使用了ajax輪詢的方式,最近項目不是很多,想起來優化一下曾經的項目,就準備引入socket代替ajax輪詢,從開始到發出第一句話並接收處理用了大概一天時間,socket並不可怕,可怕的是我當初畏懼它的心。
好了,廢話不多說,開始幹活。
環境:阿里雲ECS(windows) + Thinkphp5.0.24
在開始之前要先確定tp版本,tp5.0.24應該使用topthink/think-worker的 1.0 版本,剛搭好socket環境的時候,這個問題困擾我很久才解決,就是版本問題,tp5是不能使用高版本的think-worker的。
然後以下是我一步一步試驗出來的流程:
一、要在服務器上開放端口,具體方法就是win+r -> 搜索防火牆 -> 高級設置 -> 入站規則 -> 新建規則 -> 端口,例如12138 -> 下 一步…… -> 保存
二、如果是阿里雲ECS,要在ECS中建立安全組,ECS控制檯 -> 具體的某個服務器 -> 安全組設置 -> 將剛剛開放的12138端口加入到安全組中
三、別忘了重啓服務器,不知道這步適不適用於所有情況,反正我的服務器沒重啓端口就一直不生效,也是困擾了很久
端口配置完成之後,就要開始引包了,推薦使用composer引入
composer的具體使用方法請百度
項目引入成功後,可以測試一下端口是否可以成功ping通
端口通過之後就可以開始配置項目了:
在項目的訪問根目錄寫一個開啓服務的文件server.php:
#!/usr/bin/env php
<?php
define('APP_PATH', __DIR__ . '/../application/');
define('BIND_MODULE','admin/Socket');
// 加載框架引導文件
require __DIR__ . '/../thinkphp/start.php';
server.php第四行代表具體workerman初始化文件在application/admin/controller下的socket.php,socket.php示例:
<?php
namespace application\admin\controller;
use think\worker\Server;
use Workerman\Lib\Timer;
error_reporting(E_ERROR | E_PARSE);
class Socket extends Server{
protected $socket = 'websocket://0.0.0.0:12138'; //0.0.0.0表示內部、外部等均可訪問
protected $processes = 1; //因爲業務需要向指定ID發送消息,此處進程數必須爲1,若不需要可設置其他值
protected $uidConnections = [];
/**
* 收到信息
* @param $connection
* @param $data
*/
public function onMessage($connection, $data){
global $worker;
if(!isset($connection -> uid)){
$connection -> uid = $data['u_id'];
/*
* 保存uid到connection的映射,這樣可以方便的通過uid查找connection,
* 實現針對特定uid推送數據
*/
$worker -> uidConnections[$connection -> uid] = $connection;
}
$connection -> lastMessageTime = time();
//此處添加個人的業務代碼,例如對數據的處理、對發送者的反饋或向對方發送消息等操作
}
//向指定ID發送消息
private function sendMessageByUid($uid, $message){
global $worker;
if(isset($worker -> uidConnections[$uid])){
$worker -> uidConnections[$uid] -> send($message);
}
}
/**
* 當連接建立時觸發的回調函數
* @param $connection
*/
public function onConnect($connection){
//此處可以判斷連接數等
}
/**
* 當連接斷開時觸發的回調函數
* @param $connection
*/
public function onClose($connection){
global $worker;
if(isset($connection -> uid)){
// 連接斷開時刪除映射
unset($worker -> uidConnections[$connection -> uid]);
}
}
/**
* 當客戶端的連接上發生錯誤時觸發
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg){
echo "error $code $msg\n";
}
/**
* 每個進程啓動
* @param $worker
*/
public function onWorkerStart($worker){
//進程啓動時加入對每個連接的心跳判斷,若55秒內沒有對服務器發起過心跳或其他任何請求,則斷開連接
Timer::add(1, function()use($worker){
$time_now = time();
foreach($worker -> connections as $connection) {
// 有可能該connection還沒收到過消息,則lastMessageTime設置爲當前時間
if (empty($connection -> lastMessageTime)) {
$connection -> lastMessageTime = $time_now;
$u_ids[] = $connection -> uid;
continue;
}
// 上次通訊時間間隔大於心跳間隔,則認爲客戶端已經下線,關閉連接
if ($time_now - $connection->lastMessageTime > 55) {
$connection -> close();
}
}
});
}
}
每次使用時,需要用命令行手動運行server.php來開啓服務:
1.在cmd命令行中使用cd來進入到server.php所在目錄
2.運行 php server.php 來開啓服務,服務開啓成功後,如下圖提示:
需要注意的是,每次socket.php的代碼更改後,需要重新開啓一次服務,否則新代碼將不會執行
後端配置完成後,開始配置前端,此處給出的是websocket,基於layui的示例。layui是一款優秀的前端框架,layui的即時通訊layim極爲方便的解決了聊天界面的複雜實現且擴展容易。
layim的使用及引入請參考官方網站:layui
//首先判斷瀏覽器是否支持websocket
if(typeof(WebSocket) == 'undefined'){
layer.msg('你的瀏覽器不支持 WebSocket,無法進行聊天功能,推薦使用Google Chrome、Mozilla Firefox、360瀏覽器及QQ瀏覽器等');
}
//layui使用
layui.use('layim', function(layim){
//layim的初始化配置
layim.config({
brief: false,//是否簡約模式(如果true則不顯示主面板)
title: '客服會話', //主面板最小化後顯示的名稱
min: false, //用於設定主面板是否在頁面打開時,始終最小化展現
isAudio: false, //是否開啓聊天工具欄音頻
isVideo: false, //是否開啓開啓聊天工具欄視頻
notice: false, //是否開啓桌面消息提醒,即在瀏覽器之外的提醒
voice: false, //不開啓聲音提示
isfriend: true, //是否開啓好友
isgroup: false, //是否開啓羣組
maxLength: 3000, //可允許的消息最大字符長度
chatLog: layui.cache.dir + 'css/modules/layim/html/chatlog.html', //歷史記錄模板
init: { //獲取主面板列表信息
url: '', //獲取好友列表的接口地址
type: 'get', //默認get,一般可不填
data: {
//額外參數
}
},
uploadImage: {
url: '', //圖片上傳地址
}
});
var so = new WebSocket('ws://域名或IP:12138');
//websocket心跳
var heartCheck = {
timeout: 55000, //55秒心跳一次,告訴服務器,這個連接還接着用
timeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
this.start();
},
start: function(){
this.timeoutObj = setTimeout(function(){
console.log('我心跳了');
}, this.timeout);
},
remove: function(){
clearTimeout(this.timeoutObj);
}
}
//連接成功時觸發
so.onopen = function(){
console.log('我上線了');
//開啓心跳
heartCheck.start();
};
//so.readyState屬性值
// 0 :對應常量CONNECTING (numeric value 0),
// 正在建立連接連接,還沒有完成。The connection has not yet been established.
// 1 :對應常量OPEN (numeric value 1),
// 連接成功建立,可以進行通信。The WebSocket connection is established and communication is possible.
// 2 :對應常量CLOSING (numeric value 2)
// 連接正在進行關閉握手,即將關閉。The connection is going through the closing handshake.
// 3 : 對應常量CLOSED (numeric value 3)
// 連接已經關閉或者根本沒有建立。The connection has been closed or could not be opened.
so.onclose = function(){
console.log('會話已關閉');
heartCheck.remove();
}
//發送消息時執行
layim.on('sendMessage',function(res) {
var mine = res.mine; //包含我發送的消息及我的信息
var to = res.to; //對方的信息
so.send(JSON.stringify({
//可加其他參數
type: 'chatMessage',//隨便寫,需要時對應上即可
data: res
}));
});
//監聽收到的聊天消息
so.onmessage = function(res) {
//重置心跳
heartCheck.reset();
//官方示例是JSON.parse(),我在使用的時候不生效,試了不少方法,隨後eval()可以解析
var msg_res = eval('('+ res.data +')');
//以下爲我寫的業務代碼,收到消息時有code,並對參數進行了處理
if(msg_res.code == 200){
if(msg_res.addfriend == 1){
//添加好友
layim.addList(msg_res);
}else if(msg_res.send == 1){
//系統消息
if(msg_res.system == 1){
layim.getMessage({
system: true
,id: msg_res.to_id
,type: "friend"
,content: msg_res.msg
});
}else{
layim.getMessage(msg_res);
}
}
}else{
//彈出框報錯
layer.msg(msg_res.msg);
}
};
});
以上就是socket的簡單使用方法,更多邏輯操作請參照自身的業務。本文只是爲了解決一些簡單的入門問題,重在拋磚引玉,各位看官如有意見及建議請留言,在下必然回覆您的厚愛。
<!--結束,謝謝,再見-->