github地址:github.com/buckychen/larachat
首先引入workerman/gateway-worker
擴充套件包
$ composer require workerman/gateway-worker
在 app/Console/Commands 目錄下建立命令列檔案。
<?php
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;
class WorkermanCommand extends Command
{
protected $signature = 'workman {action} {--d}';
protected $description = 'Start a Workerman server.';
public function handle()
{
global $argv;
$action = $this->argument('action');
$argv[0] = 'wk';
$argv[1] = $action;
$argv[2] = $this->option('d') ? '-d' : '';
$this->start();
}
private function start()
{
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
$worker->name = 'BusinessWorker';
$worker->count = 1;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = \App\Workerman\Events::class;
}
private function startGateWay()
{
$gateway = new Gateway("websocket://0.0.0.0:2346");
$gateway->name = 'Gateway';
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0;
$gateway->pingData = '{"type":"ping"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister()
{
new Register('text://0.0.0.0:1236');
}
}
如果專案環境是在win中執行的話需要對啟動檔案進行修改,並且在專案根目錄中建立一個start_for_win.bat檔案一次性執行多個協議
<?php
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;
class WorkermanCommand extends Command{
//相容win
protected $signature = 'workerman
{action : action}
{--start=all : start}
{--d : daemon mode}';
protected $description = 'Start a Workerman server.';
public function handle()
{
global $argv;
$action = $this->argument('action');
//針對 Windows 一次執行,無法註冊多個協議的特殊處理
if ($action === 'single') {
$start = $this->option('start');
if ($start === 'register') {
$this->startRegister();
} elseif ($start === 'gateway') {
$this->startGateWay();
} elseif ($start === 'worker') {
$this->startBusinessWorker();
}
Worker::runAll();
return;
}
$argv[1] = $action;
$argv[2] = $this->option('d') ? '-d' : '';
$this->start();
}
private function start()
{
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
$worker->name = 'BusinessWorker';
$worker->count = 1;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = \App\Workerman\Events::class;
}
private function startGateWay()
{
$gateway = new Gateway("websocket://0.0.0.0:2346");
$gateway->name = 'Gateway';
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0;
$gateway->pingData = '{"type":"ping"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister()
{
new Register('text://0.0.0.0:1236');
}
}
start-for_win.bat 檔案中寫入
start /b php artisan workerman single --start=register
start /b php artisan workerman single --start=gateway
start /b php artisan workerman single --start=worker
pause
建立一個 app/Workerman/Events.php 檔案來監聽處理 workman 的各種事件。
<?php
namespace App\Workerman;
use GatewayWorker\Lib\Gateway;
class Events
{
public static function onWorkerStart($businessWorker)
{
echo "BusinessWorker Start\n";
}
public static function onConnect($client_id)
{
Gateway::sendToClient($client_id,json_encode(['type' => 'init','client_id' => $client_id]));
}
public static function onWebSocketConnect($client_id, $data)
{
}
public static function onMessage($client_id, $message)
{
// debug
echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n";
// 客戶端傳遞的是json資料
$message_data = json_decode($message, true);
if(!$message_data)
{
return ;
}
switch ($message_data['type']){
// 客戶端回應服務端的心跳
case 'pong':
return;
// 客戶端登入 message格式: {type:login, name:xx, room_id:1} ,新增到客戶端,廣播給所有客戶端xx進入聊天室
case 'login':
if(!isset($message_data['room_id'])){
throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
}
// 把房間號暱稱放到session中
$room_id = $message_data['room_id'];
$client_name = htmlspecialchars($message_data['client_name']);
$_SESSION['room_id'] = $room_id;
$_SESSION['client_name'] = $client_name;
//獲取房間內使用者
$clients_list = Gateway::getClientSessionsByGroup($room_id);
foreach ($clients_list as $tmp_client_id => $item){
$clients_list[$tmp_client_id] = $item['client_name'];
}
$clients_list[$client_id] = $client_name;
$new_message = array('type' => $message_data['type'],'client_id' => $client_id,'client_name' => htmlspecialchars($client_name),'time' => date('Y-m-d H:i:s'));
//給房間內使用者傳送資訊
Gateway::sendToGroup($room_id, json_encode($new_message));
//當前使用者加入到房間內
Gateway::joinGroup($client_id, $room_id);
// 給當前使用者傳送使用者列表
$new_message['client_list'] = $clients_list;
Gateway::sendToCurrentClient(json_encode($new_message));
return;
case 'say':
if(!isset($_SESSION['room_id']))
{
throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");
}
$room_id = $_SESSION['room_id'];
$client_name = $_SESSION['client_name'];
if($message_data['to_client_id'] != 'all'){
$new_message = array(
'type' => 'say',
'from_client_id' => $client_id,
'from_client_name' => $client_name,
'to_client_id' => $message_data['to_client_id'],
'content' => "<b>對你說: </b>".nl2br(htmlspecialchars($message_data['content'])),
'time' => date('Y-m-d H:i:s'),
);
Gateway::sendToClient($message_data['to_client_id'],json_encode($new_message));
$new_message['content'] = "<b>你對".htmlspecialchars($message_data['to_client_name'])."說: </b>".nl2br(htmlspecialchars($message_data['content']));
return Gateway::sendToCurrentClient(json_encode($new_message));
}
$new_message = array(
'type'=>'say',
'from_client_id'=>$client_id,
'from_client_name' =>$client_name,
'to_client_id'=>'all',
'content'=>nl2br(htmlspecialchars($message_data['content'])),
'time'=>date('Y-m-d H:i:s'),
);
return Gateway::sendToGroup($room_id ,json_encode($new_message));
}
}
public static function onClose($client_id)
{
// debug
echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n";
if(isset($_SESSION['room_id'])){
$room_id = $_SESSION['room_id'];
$new_message = array(
'type' => 'logout',
'from_client_id' => $client_id,
'from_client_name' => $_SESSION['client_name'],
'time' => date('Y-m-d H:i:s'),
);
Gateway::sendToGroup($room_id,json_encode($new_message));
}
}
}
在resources/views目錄中中建立chatList.blade.php檔案
<html>
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css'>
<link rel="stylesheet" href="{{ asset('css/styleChat.css') }}">
<link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/jquery-sinaEmotion-2.1.0.min.css') }}" rel="stylesheet">
<script type="text/javascript" src="{{ asset('js/swfobject.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/web_socket.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/jquery-sinaEmotion-2.1.0.min.js') }}"></script>
<script type="text/javascript">
if (typeof console == "undefined") { this.console = { log: function (msg) { } };}
// 如果瀏覽器不支援websocket,會使用這個flash自動模擬websocket協議,此過程對開發者透明
WEB_SOCKET_SWF_LOCATION = "{{ asset('swf/WebSocketMain.swf') }}";
// 開啟flash的websocket debug
WEB_SOCKET_DEBUG = true;
var ws, name, client_list={}, client_id, send_to_user_client_id='all',send_to_username='all';
function connect(){
ws = new WebSocket("ws://"+document.domain+":2346");
ws.onopen = onopen;
// 當有訊息時根據訊息型別顯示不同資訊
ws.onmessage = onmessage;
ws.onclose = function() {
console.log("連線關閉,定時重連");
connect();
};
ws.onerror = function() {
console.log("出現錯誤");
};
}
function onopen(){
if(!name){
show_prompt();
}
// 登入
var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"<?php echo isset($_GET['room_id']) ? $_GET['room_id'] : 1?>"}';
console.log("websocket握手成功,傳送登入資料:"+login_data);
ws.send(login_data);
$("#user_name").text(name);
}
function onmessage(e){
console.log(e.data);
var data = JSON.parse(e.data);
switch (data['type']){
//服務端ping客戶端
case 'ping':
ws.send('{"type":"pong"}');
break;
case 'login':
say(data['client_id'], data['client_name'], data['client_name']+' 加入了聊天室', data['time']);
if(data['client_list']){
client_list = data['client_list'];
}else{
client_list[data['client_id']] = data['client_name'];
}
flush_client_list();
console.log(data['client_name']+"登入成功");
break;
case 'say':
say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);
break;
case 'logout':
say(data['from_client_id'],data['from_client_name'],data['from_client_name']+' 退出了',data['time']);
delete client_list[data['from_client_id']];
flush_client_list();
break;
case 'init':
client_id = data['client_id'];
break;
}
}
// 輸入姓名
function show_prompt(){
name = prompt('輸入你的名字:', '');
if(!name || name=='null'){
name = '遊客';
}
}
function say(from_client_id,from_client_name,content,time){
// $("#msg_list_ul").append(
// '<li class="clearfix"><div class="message-data align-right"><span class="message-data-time" >'+time+'</span> <span class="message-data-name" >'+from_client_name+'</span> <i class="fa fa-circle me"></i></div><div class="message other-message float-right">'+content+'</div></li>'
// )
if(from_client_id == client_id){
$("#msg_list_ul").append(
'<li class="clearfix"><div class="message-data align-right"><span class="message-data-time" >'+time+'</span> <span class="message-data-name" >'+from_client_name+'</span> <i class="fa fa-circle me"></i></div><div class="message other-message float-right">'+content+'</div></li>'
)
}else{
$("#msg_list_ul").append(
'<li><div class="message-data"><span class="message-data-name"><i class="fa fa-circle online"></i>'+from_client_name+'</span><span class="message-data-time">'+time+'</span></div><div class="message my-message">'+content+'</div></li>'
)
}
document.getElementById("chat-history-div").scrollTop=document.getElementById("chat-history-div").scrollHeight;
}
function flush_client_list(){
var client_list_ul = $("#client_list_ul");
var img = '{{ asset("img/t1.png") }}';
client_list_ul.empty();
client_list_ul.append(
'<li class="clearfix" onclick="click_user(this)" id="all"><img src="'+img+'" alt="avatar" /><div class="about"><div class="name">all</div><div class="status"><i class="fa fa-circle online"></i> online</div></div></li>'
);
for(var p in client_list){
//client_list_ul.append('<li id="'+p+'">'+client_list[p]+'</li>')
client_list_ul.append(
'<li class="clearfix" onclick="click_user(this)" id="'+p+'"><img src="'+img+'" alt="avatar" /><div class="about"><div class="name">'+client_list[p]+'</div><div class="status"><i class="fa fa-circle online"></i> online </div></div></li>'
);
}
}
function submit(){
var input = $("#message-to-send");
var input_text = input.val();
var to_client_id = send_to_user_client_id;
var to_client_name = send_to_username;
ws.send('{"type":"say","to_client_id":"'+to_client_id+'","to_client_name":"'+to_client_name+'","content":"'+input_text.replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r')+'"}');
input.val('');
input.focus();
}
function click_user(e){
var user_client_id = $(e).attr('id');
var user_name = $(e).children('div').children('.name').text();
if(client_id == user_client_id){
return;
}
send_to_user_client_id = user_client_id;
send_to_username = user_name;
$("#message-to-send").attr('placeholder','send to '+user_name);
}
</script>
</head>
<body onload="connect()">
<!-- partial:index.partial.html -->
<div class="container clearfix">
<div class="people-list" id="people-list">
<div class="search">
<input type="text" placeholder="search" />
<i class="fa fa-search"></i>
</div>
{{-- 線上使用者列表--}}
<ul class="list" id="client_list_ul">
{{-- <li class="clearfix" onclick="click_user(this)" id="all">--}}
{{-- <img src="{{ asset('img/t1.png') }}" alt="avatar" />--}}
{{-- <div class="about">--}}
{{-- <div class="name">all</div>--}}
{{-- <div class="status">--}}
{{-- <i class="fa fa-circle online"></i> online--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- </li>--}}
</ul>
</div>
<div class="chat">
{{-- 使用者資訊--}}
<div class="chat-header clearfix">
<img src="{{ asset('img/t1.png') }}" alt="avatar" />
<div class="chat-about">
<div class="chat-with" id="user_name"></div>
<div class="chat-num-messages">already 1 902 messages</div>
</div>
<i class="fa fa-star"></i>
</div> <!-- end chat-header -->
{{-- 聊天列表--}}
<div class="chat-history" id="chat-history-div">
<ul id="msg_list_ul">
{{-- <li class="clearfix">--}}
{{-- <div class="message-data align-right">--}}
{{-- <span class="message-data-time" >10:10 AM, Today</span> --}}
{{-- <span class="message-data-name" >Olia</span> <i class="fa fa-circle me"></i>--}}
{{-- </div>--}}
{{-- <div class="message other-message float-right">--}}
{{-- Hi Vincent, how are you? How is the project coming along?--}}
{{-- </div>--}}
{{-- </li>--}}
{{-- 正在輸入--}}
{{-- <li>--}}
{{-- <div class="message-data">--}}
{{-- <span class="message-data-name"><i class="fa fa-circle online"></i> Vincent</span>--}}
{{-- <span class="message-data-time">10:31 AM, Today</span>--}}
{{-- </div>--}}
{{-- <i class="fa fa-circle online"></i>--}}
{{-- <i class="fa fa-circle online" style="color: #AED2A6"></i>--}}
{{-- <i class="fa fa-circle online" style="color:#DAE9DA"></i>--}}
{{-- </li>--}}
</ul>
</div> <!-- end chat-history -->
<div class="chat-message clearfix" id="msg_text">
<a href="{{url('test/chat?room_id=1')}}">房間1</a> <a href="{{url('test/chat?room_id=2')}}">房間2</a> <a href="{{url('test/chat?room_id=3')}}">房間3</a>
<textarea name="message-to-send" id="message-to-send" placeholder ="" rows="3"></textarea>
<i class="fa fa-file-o"></i>
<i class="fa fa-file-image-o"></i>
<button onclick="submit()">傳送</button>
</div> <!-- end chat-message -->
</div> <!-- end chat -->
</div> <!-- end container -->
<!-- partial -->
<script src='{{ asset('js/list.min.js') }}'></script>
<script type="text/javascript">
// 動態自適應螢幕
document.write('<meta name="viewport" content="width=device-width,initial-scale=1">');
$("#message-to-send").on("keydown", function(e) {
// 按enter鍵自動提交
if(e.keyCode === 13 && !e.ctrlKey) {
e.preventDefault();
submit();
return false;
}
// 按ctrl+enter組合鍵換行
if(e.keyCode === 13 && e.ctrlKey) {
$(this).val(function(i,val){
return val + "\n";
});
}
});
</script>
</body>
</html>
並在routes/web.php路由檔案中註冊路由,訪問該前端檔案
Route::get('/test/chat',function (){
return view('chatList');
});
在命令列中執行
php artisan workman start -d
如在win環境中,雙擊建立的start_for_win.bat檔案啟動服務
如果顯示下面的結果,那麼workerman就已經啟動成功了
----------------------- WORKERMAN -----------------------------
Workerman version:4.0.19 PHP version:7.4.3
------------------------ WORKERS -------------------------------
worker listen processes status
Register text://0.0.0.0:1236 1 [ok]
----------------------- WORKERMAN -----------------------------
Workerman version:4.0.19 PHP version:7.4.3
------------------------ WORKERS -------------------------------
worker listen processes status
Gateway websocket://0.0.0.0:2346 1 [ok]
----------------------- WORKERMAN -----------------------------
Workerman version:4.0.19 PHP version:7.4.3
------------------------ WORKERS -------------------------------
worker listen processes status
BusinessWorker none 1 [ok]
BusinessWorker Start
參考文章:部落格:在 Laravel 中使用 Workerman 進行 socket 通訊
本作品採用《CC 協議》,轉載必須註明作者和本文連結