因为公司要做一个学生在线答题 — 老师监控的功能,并且可以实时同步学生的答题状态 所以配置了swoole,当做websocket服务器。我这里用的扩展是laravel-s 来实现一个类似聊天室的功能。
laravel-s文档:https://github.com/hhxsv5/laravel-s/blob/master/README-CN.md
composer require "hhxsv5/laravel-s:~3.7.0" -vvv
发布laravel-s的配置文件 php artisan laravels publish
生成的文件在config/laravels.php
中,需要关注的就几项:
//第24行监听的ip
'listen_ip' => env('LARAVELS_LISTEN_IP', '127.0.0.1'),
//第35行监听的端口
'listen_port' => env('LARAVELS_LISTEN_PORT', 5200),
//第149行 需要修改如下:
'websocket' => [
'enable' => true,
'handler' => \App\Servers\WebSocketService::class,
],
//第287行 修改 dispatch_mode
'swoole' => [
//...
// dispatch_mode只能设置为2、4、5,https://wiki.swoole.com/#/server/setting?id=dispatch_mode
'dispatch_mode' => env('LARAVELS_DISPATCH_MODE', 2),
//...
],
这里laravels需要一个
handler
类来响应websocket的各种事件,具体的代码 我会放到文末,也可以直接在laravels
文档中获取
不要忘记修改绑定的host
gzip on;
gzip_min_length 1024;
gzip_comp_level 2;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
gzip_vary on;
gzip_disable "msie6";
upstream swoole {
# 通过 IP:Port 连接 这里的ip和端口 应该config中的配置保持一致
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# 通过 UnixSocket Stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能
#server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
#server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
#server 192.168.1.2:5200 backup;
keepalive 16;
}
server {
listen 80;
# 别忘了绑Host
server_name laravels.com;
root /yourpath/laravel-s-test/public;
access_log /yourpath/log/nginx/$server_name.access.log main;
autoindex off;
index index.html index.htm;
# Nginx处理静态资源(建议开启gzip),LaravelS处理动态资源。
location / {
try_files $uri @laravels;
}
# Http and WebSocket are concomitant, Nginx identifies them by "location"
# !!! The location of WebSocket is "/ws"
# Javascript: var ws = new WebSocket("ws://laravels.com/ws");
location =/ws {
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
# proxy_read_timeout 60s;
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://swoole;
}
location @laravels {
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout 120s;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
# “swoole”是指upstream
proxy_pass http://swoole;
}
}
supervisord
配置文件 配置文件目录一般在 /etc/supervisor/conf.d
[program:laravel-s-test]
directory=/www/laravel
command=/usr/bin/php bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
directory
设置为laravel项目根目录即可。
command
中需要正确定义 php
可执行命令的地址。
stdout_logfile
是输出的日志文件,需要保证user
配置项中的用户有可写权限。
supervisord
> sudo supervisorctl update
> sudo supervisorctl start laravel-s-test
laravel-s-test: started
查看一下端口的监听状态
> lsof -i tcp:5200
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
php 10932 www-data 5u IPv4 1174359495 0t0 TCP localhost:5200 (LISTEN)
这样websocket
服务器就算正式启动了
websocketService
监听器类的代码很简单 只有三种事件,
onOpen
建立连接的事件,onMessage
服务器收到推送消息的事件,onClose
连接关闭事件。
代码一定要包裹在try catch
之中,监听器中的异常只会记录到swoole的异常日志,不会抛出
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
/**
* @see https://wiki.swoole.com/#/start/start_ws_server
*/
class WebSocketService implements WebSocketHandlerInterface
{
// 声明没有参数的构造函数
public function __construct()
{
}
// public function onHandShake(Request $request, Response $response)
// {
// 自定义握手:https://wiki.swoole.com/#/websocket_server?id=onhandshake
// 成功握手之后会自动触发onOpen事件
// }
public function onOpen(Server $server, Request $request)
{
// 在触发onOpen事件之前,建立WebSocket的HTTP请求已经经过了Laravel的路由,
// 所以Laravel的Request、Auth等信息是可读的,Session是可读写的,但仅限在onOpen事件中。
// \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
// 此处抛出的异常会被上层捕获并记录到Swoole日志,开发者需要手动try/catch
$server->push($request->fd, 'Welcome to LaravelS');
}
public function onMessage(Server $server, Frame $frame)
{
// \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
// 此处抛出的异常会被上层捕获并记录到Swoole日志,开发者需要手动try/catch
$server->push($frame->fd, date('Y-m-d H:i:s'));
}
public function onClose(Server $server, $fd, $reactorId)
{
// 此处抛出的异常会被上层捕获并记录到Swoole日志,开发者需要手动try/catch
}
}
如果在其他类中想推送数据给前端 可以这样写
//这里获取的是 Swoole\WebSocket\Server 实例
app('swoole')->push($fd, $data);
这篇主要记录一下配置之类的,免得自己忘记,其实可以直接看laravels
的配置文档。
下一篇会记录 WebSocketService
类中对websock
事件的响应的业务代码
- Laravels 中文文档:https://github.com/hhxsv5/laravel-s/blob/master/README-CN.md
- Laravel 文档:https://learnku.com/docs/laravel/8.x