Ⅰ.序
此篇是接着这篇写滴:https://www.jianshu.com/p/6c9ad29c552b;
目的是一个搭小聊天室。
Ⅱ.安装swoole扩展
再上一遍的基础上我先安装了sockets calendar exif pcntl shmop sysvmsg sysvsem sysvshm wddx这些扩展,文件
laravel/docker/Dockerfile
,在上一步安装php扩展的下方添加,感觉只有sockets是需要的,谁知道呢...都给整上:
RUN set -xe \
&& docker-php-ext-configure calendar --enable-calendar \
&& docker-php-ext-configure exif --enable-exif \
&& docker-php-ext-configure pcntl --enable-pcntl \
&& docker-php-ext-configure shmop --enable-shmop \
&& docker-php-ext-configure sysvmsg --enable-sysvmsg \
&& docker-php-ext-configure sysvsem --enable-sysvsem \
&& docker-php-ext-configure sysvshm --enable-sysvshm \
&& docker-php-ext-configure wddx --enable-wddx \
&& docker-php-ext-configure sockets --enable-sockets \
&& docker-php-ext-install sockets calendar exif pcntl shmop sysvmsg sysvsem sysvshm wddx
安装swoole扩展
- 文件
laravel/docker/Dockerfile
添加:
RUN set -xe \
&& pecl install -o -f swoole \
&& docker-php-ext-enable swoole
- 报错,大致是这样说的(忘记截图了):错误:加载公共库libstdc++.so.6 文件找不到或者不存在...swoole.so...找不到或者不存在。但其实swoole.so是有的,只是加载出来要libstdc++库。所以:
RUN set -xe \
&& apk add --no-cache --virtual .persistent-deps \
libstdc++
安装LaravelS
参考
composer require hhxsv5/laravel-s
php artisan laravels publish
配置 .env
LARAVELS_LISTEN_IP=workspace
LARAVELS_DAEMONIZE=true
实现 WebSocket 服务器
参考
agents = array(
self::AGENT_PC_WEB,
self::AGENT_PHONE_WEB,
self::AGENT_PC_CLIENT,
self::AGENT_PHONE_CLIENT,
);
}
// 连接建立时触发
public function onOpen(Server $server, Request $request)
{
// 在触发 WebSocket 连接建立事件之前,Laravel 应用初始化的生命周期已经结束,你可以在这里获取 Laravel 请求和会话数据
// 调用 push 方法向客户端推送数据,fd 是客户端连接标识字段
Log::info('WebSocket 连接建立');
// $server->push($request->fd, 'Welcome to WebSocket Server built on LaravelS');
}
// 收到消息时触发
public function onMessage(Server $server, Frame $frame)
{
// 调用 push 方法向客户端推送数据
// $server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
echo "received message: {$frame->data}\n";
$msg = json_decode($frame->data, true);
if ($msg['msg_type'] == self::MSG_TYPE_JOIN) {
// TODO:创建房间。参考:https://github.com/nineyang/chat/blob/master/app/Console/Commands/Swoole.php
Redis::zadd("room:{$msg['room_id']}", intval($msg['msg_from']), $frame->fd);
Redis::hset('room', $frame->fd, $msg['room_id']);
$memberInfo = [
'online' => Redis::zcard("room:{$msg['room_id']}"),
'all' => Redis::zrange("room:{$msg['room_id']}", 0, -1)
];
$message = [
'member_info' => $memberInfo,
'msg' => $msg['msg_body']
];
$this->sendAll($server, $msg['room_id'], $msg['msg_from'], $message, self::MSG_TYPE_JOIN);
} else {
$this->sendAll($server, $msg['room_id'], $msg['msg_from'], $frame->data, self::MSG_TYPE_TEXT);
// event(new Chat($msg['msg_from'], $frame->data));
}
}
// 关闭连接时触发
public function onClose(Server $server, $fd, $reactorId)
{
echo "connection close: {$fd}\n";
$room_id = Redis::hget('room', $fd);
Redis::hdel('room', $fd);
$user_id = intval(Redis::zscore("room:{$room_id}", $fd));
Redis::zrem("room:{$room_id}", $fd);
$balance = Redis::zcard("room:{$room_id}");
if ($balance == 0) {
Redis::del("room:{$room_id}");
return;
}
$memberInfo = [
'online' => $balance,
'all' => Redis::zrange("room:{$room_id}", 0, -1)
];
$message = [
'member_info' => $memberInfo,
'msg' => $user_id . '离开了群聊'
];
$this->sendAll($server, $room_id, $user_id, $message, self::MSG_TYPE_LEAVE);
}
/**
* @param Server $ws
* @param $room_id
* @param null $user_id
* @param null $message
* @param int $type
*/
private function sendAll($ws, $room_id, $user_id = null, $message = null, $type = self::MSG_TYPE_TEXT)
{
// $user = $this->user->find($user_id, ['id', 'name']);
// if (!$user) {
// return;
// }
if ($type != self::MSG_TYPE_TEXT)
$message = json_encode([
'msg_body' => is_string($message) ? nl2br($message) : $message,
'msg_from' => $user_id,
'msg_type' => $type
]);
$members = Redis::zrange("room:{$room_id}", 0, -1);
foreach ($members as $fd) {
$ws->push($fd, $message);
}
}
}
修改配置文件
- 配置文件
config/laravels.php
'websocket' => [
'enable' => true,
'handler' => \App\Services\WebSocketService::class,
],
...
// 可选
'swoole' => [
...
// 每隔 60s 检测一次所有连接,如果某个连接在 600s 内都没有发送任何数据,则关闭该连接
'heartbeat_idle_time' => 600,
'heartbeat_check_interval' => 60,
...
],
- 配置.env
LARAVELS_LISTEN_IP=app // 这里的 IP 需要和 nginx upstream 中配置的监听 IP 保持一致,即php-fpm的服务名
LARAVELS_DAEMONIZE=true
- 配置Nginx配置文件,注意
map
和upstream
配置与server
是平级的
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream laravels{
# Connect IP:Port
# server workspace:5200 weight=5 max_fails=3 fail_timeout=30s;
# keepalive 16;
server app:5200 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
server {
listen 80;
server_name .purchase.com;
set $root_path '/usr/share/nginx/html';
set $php_path '/var/www/html/public';
client_max_body_size 100m;
charset utf-8;
location ~ (\.html|\.css|\.js|\.jpg|\.jpeg|\.png|\.gif|\.ico|\.ttf|\.woff|\.woff2|\.xls|\.xlsx|\.docx|\.pdf|\.mpga|\.mp3|\.txt) {
root $root_path;
}
location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php?$args;
}
# WebSocket 通信
location =/ws {
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header Server-Protocol $server_protocol;
proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://laravels;
}
location ~ \.php(.*)$ {
root $php_path;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_buffering off;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
include fastcgi_params;
fastcgi_intercept_errors on;
}
location ~ /\.ht {
deny all;
}
}
Websocket的前端实现
直接上代码吧,简陋滴很。(要一个jquery文件)
Laravel
启动
在php源代码容器中,项目根目录下执行:
php bin/laravels start
访问下:http://purchase.com/test/chatRoom/91609929b9699631e84fdef3385f3721(本地的host文件配置好域名)
- 出现问题,这个问题参考的网站下的评论里也出现了,但是没有解决:
php源代码容器中,[ERROR] worker[5] error: exitCode=255, signal=0
解决问题
在nginx容器中查看app:5200是否能连上:
- 安装telnet,参考,下面这样是正常的
/usr/share/nginx/html # apk update
/usr/share/nginx/html # apk add busybox-extras
/usr/share/nginx/html # busybox-extras telnet app:5200
Connected to app:5200
- 如果连不上,可能是5200端口没有给到位,那么,文件
docker/docker-compose.override.yml
,暴露或者映射下端口
app:
build:
context: ../laravel/
dockerfile: docker/Dockerfile
image: "laravel-php:latest"
ports:
- "5200:5200"
environment:
## Application Configuration ##
APP_ENV: "dev"
APP_DEBUG: "true"
volumes:
- ../laravel:/var/www/html
php源代码容器中,[ERROR] worker[5] error: exitCode=255, signal=0
- 参考,composer.json文件加入
"app/Services"
,
"autoload": {
"psr-4": {
"App\": "app/"
},
"classmap": [
"database/seeds",
"app/Services",
"database/factories"
]
},
- 项目根目录下运行命令
composer dump-autoload
至此,模子就差不多了
image.png