Thinkphp5使用workerman、socket、websocket、layui、layim建立即時通訊

在開始之前,有句話想說,曾經我以爲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的簡單使用方法,更多邏輯操作請參照自身的業務。本文只是爲了解決一些簡單的入門問題,重在拋磚引玉,各位看官如有意見及建議請留言,在下必然回覆您的厚愛。

<!--結束,謝謝,再見-->
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章