1.安装基础代码
composer create-project laravel/laravel chatdemo composer require wokerman/wokerman
2.
php artisan make:command WokermanServer
app\Console\Commands\WokermanServer.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use Workerman\Worker;
class WokermanServer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'wokerman {action} {--daemonize}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'workerman 启动停止';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
global $argv;//定义全局变量
$arg = $this->argument('action');
$argv[1] = $arg;
$argv[2] = $this->option('daemonize') ? '-d' : '';//该参数是以daemon(守护进程)方式启动
global $text_worker;
// 创建一个Worker监听2345端口,使用websocket协议通讯
$text_worker = new Worker("websocket://0.0.0.0:2345");
$text_worker->uidConnections = array();//在线用户连接对象
$text_worker->uidInfo = array();//在线用户的用户信息
// 启动4个进程对外提供服务
$text_worker->count = 4;
//引用类文件
$handler = App::make('Handler\WokermanHandler');
$text_worker->onConnect = array($handler,"handle_connection");
$text_worker->onMessage = array($handler,"handle_message");
$text_worker->onClose = array($handler,"handle_close");
$text_worker->onWorkerStart = array($handler,"handle_start");
// 运行worker
Worker::runAll();
return 0;
}
}在app下新建Handler目录,然后新建 WokermanHandler.php
<?php
namespace Handler;
use Workerman\Lib\Timer;
class WokermanHandler
{
private $heartbeat_time = 55;//心跳间隔55秒
//当客户端连上来时分配uid,并保存链接,并通知所有客户端
public function handle_connection($connection)
{
global $text_worker;
//判断是否设置了UID
if (!isset($connection->uid)) {
//给用户分配一个UID
$connection->uid = $this->random_string();
//保存用户的uid
$text_worker->uidConnections[$connection->uid] = $connection;
//向用户返回创建成功的信息
$connection->send("用户:[".$connection->uid."] 创建成功");
}
}
public function handle_start()
{
global $text_worker;
//每秒都判断客户端是否已下线
Timer::add(1, function () use ($text_worker) {
$time_now = time();
foreach ($text_worker->connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > $this->heartbeat_time) {
$connection->close();
}
}
});
//每隔30秒就向客户端发送一条心跳验证
Timer::add(50, function () use ($text_worker) {
foreach ($text_worker->connections as $conn) {
$conn->send('{"type":"ping"}');
}
});
}
//当客户端发送消息过来时,转发给所有人
public function handle_message($connection, $data)
{
global $text_worker;
//debug
//echo "data_info:".$data.PHP_EOL;
$connection->lastMessageTime = time();
$data_info=json_decode($data, true);
if (!$data_info) {
$connection->send("消息格式错误");
return ;
}
//判断业务类型
switch ($data_info['type']) {
case 'login':
//判断用户信息是否存在
if (empty($data_info['user_id'])) {
$connection->send("{'type':'error','msg':'非法请求'}");
return $connection->close();
}
//判断用户是否已经登录了
$user_ids=array_column($text_worker->uidInfo, "user_id");
if (in_array($data_info['user_id'], $user_ids)) {
$connection->send("{'type':'error','msg':'你在其它地方已登录'}");
return $connection->close();
}
//存储用户信息
$text_worker->uidInfo[$connection->uid]=array(
"user_id"=>$data_info['user_id'],
"user_name"=>htmlspecialchars($data_info['user_name']),
"create_time"=>date("Y-m-d H:i"),
);
//返回数据
if (isset($data_info['to_uid']) && $data_info['to_uid'] == "all") {
$return_data=array(
"type"=>"login",
"uid"=>$connection->uid,
"user_name"=>htmlspecialchars($data_info['user_name']),
"send_time"=>date("Y-m-d H:i", time()),
"user_lists"=>$text_worker->uidInfo
);
$curral_data=array(
"type"=>"login_uid",
"uid"=>$connection->uid,
);
$connection->send(json_encode($curral_data));
//给所有用户发送一条数据
foreach ($text_worker->connections as $conn) {
$conn->send(json_encode($return_data));
}
} else {
return ;
}
return;
//用户发消息
case 'say':
if (!isset($text_worker->uidInfo[$connection->uid]) || empty($text_worker->uidInfo[$connection->uid])) {
$connection->send('{"type":"error","msg":"你已经掉线了"}');
}
//获取到当前用户的信息
$user_info=$text_worker->uidInfo[$connection->uid];
//判断是私聊还是群聊
if ($data_info['to_uid'] != "all") {
//私聊
$return_data=array(
"type"=>"say",
"from_uid"=>$connection->uid,
"from_user_name"=>$user_info['user_name'],
"to_uid"=>$data_info['to_uid'],
"content"=>nl2br(htmlspecialchars($data_info['content'])),
"send_time"=>date("Y-m-d H:i")
);
if ($data_info['to_uid'] == $connection->uid) {
$connection->send(json_encode($return_data));
return;
}
//判断用户是否存在,并向对方发送数据
if (isset($text_worker->uidConnections["{$data_info['to_uid']}"])) {
$to_connection=$text_worker->uidConnections["{$data_info['to_uid']}"];
$to_connection->send(json_encode($return_data));
}
//向你自己发送一条数据
$connection->send(json_encode($return_data));
} else {
//群聊
$return_data=array(
"type"=>"say",
"from_uid"=>$connection->uid,
"from_user_name"=>$user_info['user_name'],
"to_uid"=>"all",
"content"=>nl2br(htmlspecialchars($data_info['content'])),
"send_time"=>date("Y-m-d H:i")
);
//向所有用户发送数据
foreach ($text_worker->connections as $conn) {
$conn->send(json_encode($return_data));
}
}
return;
case "pong":
return;
}
}
//当客户端断开时,广播给所有客户端
public function handle_close($connection)
{
global $text_worker;
$user_name=$text_worker->uidInfo[$connection->uid]['user_name'] ?? "";
unset($text_worker->uidConnections[$connection->uid]);
unset($text_worker->uidInfo[$connection->uid]);
if (!empty($user_name)) {
$return_data=array(
"type"=>"logout",
"uid"=>$connection->uid,
"user_name"=>$user_name,
"create_time"=>date("Y-m-d H:i:s"),
);
foreach ($text_worker->connections as $conn) {
$conn->send(json_encode($return_data));
}
}
}
public function random_string()
{
return substr(uniqid('', true), 15).substr(microtime(), 2, 8);
}
}3.修改composer。json 增加handler映射
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Handler\\": "app/Handler/"
}
},启动websocker服务 ,应该不会报错
composer update php artisan wokerman start
成功的截图

4.写个html调用一下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试websocket</title>
</head>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<body>
<div id="chat_list">
<p></p>
</div>
<input type="text" value="" id="chat_msg">
<button type="button" id="send">发送</button>
<script type="text/javascript">
var wsurl = "ws://127.0.0.1:2345";
var ws = new WebSocket(wsurl);
ws.onopen = function(){
var params = {}
params.type = 'login'
params.user_id = Math.ceil(Math.random()*100)
params.user_name = '测试用户'+params.user_id
params.to_uid = ''
ws.send(JSON.stringify(params))
console.log('注册成功',JSON.stringify(params))
}
ws.onmessage = function(evt){
console.log('返回的信息data',evt.data)
if(msg = parseJSON(evt.data)){
console.log('返回的信息msg',msg)
if(msg.type == 'say'){
$("#chat_list").append("<p>"+msg.content+"</p>")
}
}
}
ws.onclose = function(){
console.log('关闭了')
}
$().ready(function(){
$("#send").click(function(){
var m = $("#chat_msg").val();
if(m == ''){
alert('消息不能为空')
return false;
}
params = {}
params.type = 'say'
params.to_uid = 'all'
params.content = m
ws.send(JSON.stringify(params))
console.log('发送了消息',JSON.stringify(params))
$("#chat_msg").val('')
})
})
function parseJSON(str) {
if (typeof str == 'string') {
try {
return JSON.parse(str);
} catch(e) {
return false;
}
}
console.log('It is not a string!')
}
</script>
</body>
</html>
本文为看恩吧原创文章,转载无需和我联系,但请注明来自knsay.com