一、安装swoole

1. 下载swoole源码,官网:http://pecl.php.net/package/swoole 挑选自己的版本下载,

在这里下载:swoole-4.6.7

解压到/www/server/php/80目录下,

进入:/www/server/php/80/swoole-4.6.7 文件夹下;

执行以下命令安装(三行命令一起复制,粘贴运行):

phpize
./configure --with-php-config=/www/server/php/80/bin/php-config
make && sudo make install

当停到这里时,按回车键就行

二、安装php8-扩展-swoole

参考:宝塔面板php8.0编译安装swoole,初步体验laravel/octane

三、测试连接

(1) 测试TCP 服务器

官方文档:Swoole4 文档

3.1.1、创建网站同时创建文件server.php

官方文档:Swoole4 文档

3.1.2、运行命令server.php

进入网站的根目录再运行

php server.php

出现错误:PHP Fatal error:  Uncaught Error: Class 'Swoole\Server' not found in /www/wwwroot/wss.xxx.com.cn/server.php

参考:

解决Class 'swoole_server' not found - 程序生(Codey) - 博客园

swoole错误“Uncaught Error: Class 'swoole_server' not found”的解决办法_weixin_30248399的博客-CSDN博客

这里因为php.ini配置中没有加入extension=swoole.so

但是我们查看PHP.8 ini的文档中是已经加入的,但是还是出现这个错误,

用命令查看扩展

php -m

发现没有Swoole扩展很奇怪,再怀疑是不是系统运行PHP8.0版本的

查看命令行的ini路径,命令行下运行

发现系统是PHP7.4版本的,所以要变更当前PHP8.0版本

宝塔:

提交再php -m

OK搞定 

3.1.3 再运行命令server.php 没有显示错误,也没有显示任何内容,此时说明是对的

要进入网址根目录后再运行命令

再打开一个Xshell(SSH)运行命令

telnet 127.0.0.1 9501

swoole官方教程:Swoole4 文档

(2) WebSocket 服务端 (异步风格)(主要是这个服务)

只要修改了这个代码,就需要重新启动这个服务(在命令窗口中重启)

官方教程:Swoole4 文档

3.2.1 在网站在创建ws_server.php

(一)、php原生写法

//创建WebSocket Server对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);

//监听WebSocket连接打开事件
$ws->on('Open', function ($ws, $request) {
    $ws->push($request->fd, "hello, welcome\n");
});

//监听WebSocket消息事件
$ws->on('Message', function ($ws, $frame) {
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});

//监听WebSocket连接关闭事件
$ws->on('Close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});

$ws->start();

(二)、面向对象代码 写法

<?php

class WebSocketTest
{
    public $server;

    public function __construct()
    {
        $jsonData=[];$fd='';$content='';$contentjson='';$arr=[];
        $this->server = new Swoole\WebSocket\Server("0.0.0.0", 9502);

        $this->server->on('open', function (Swoole\WebSocket\Server $server, $request) {
            echo "server: handshake success with fd={$request->fd}\n";
        });
        $this->server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
            echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
            if($frame->data){
                $jsonData=json_decode($frame->data,true);
                $fd=$jsonData['fd'];
                $content=$jsonData['content'];
                $contentArr['content']=$content;//发送内容
                $contentArr['id']=$fd;//
                $contentjson=json_encode($contentArr);
            }
            $server->push($fd, $contentjson);
        });
        $this->server->on('close', function ($ser, $fd) {
            echo "client {$fd} closed\n";
        });
        // $this->server->on('request', function ($request, $response) {
        //     // 接收http请求从get获取message参数的值,给用户推送
        //     // $this->server->connections 遍历所有websocket连接用户的fd,给所有用户推送
        //     foreach ($this->server->connections as $fd) {
        //         // 需要先判断是否是正确的websocket连接,否则有可能会push失败
        //         if ($this->server->isEstablished($fd)) {
        //             $this->server->push($fd, $request->get['message']);
        //         }
        //     }
        // });
        $this->server->start();
    }
}

new WebSocketTest();

(三)、WebSocket 服务端  (协程风格)

官方文档:Swoole4 文档

注意一点:

$server = new Server('127.0.0.1', 9502, false);

IP要改成 0.0.0.0

 $server = new Server('0.0.0.0', 9502, false);

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
use Swoole\Coroutine\Http\Server;
use function Swoole\Coroutine\run;

run(function () {
    $server = new Server('0.0.0.0', 9502, false);
    $server->handle('/websocket', function (Request $request, Response $ws) {
        $ws->upgrade();
        while (true) {
            $frame = $ws->recv();
            if ($frame === '') {
                $ws->close();
                break;
            } else if ($frame === false) {
                echo 'errorCode: ' . swoole_last_error() . "\n";
                $ws->close();
                break;
            } else {
                if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
                    $ws->close();
                    break;
                }
                $ws->push("Hello {$frame->data}!");
                $ws->push("How are you, {$frame->data}?");
            }
        }
    });

    $server->handle('/', function (Request $request, Response $response) {
        $response->end(<<<HTML
    <h1>Swoole WebSocket Server</h1>
    <script>
var wsServer = 'ws://ws.xxx.com.cn/websocket';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) {
    console.log("Connected to WebSocket server.");
    websocket.send('hello');
};

websocket.onclose = function (evt) {
    console.log("Disconnected");
};

websocket.onmessage = function (evt) {
    console.log('Retrieved data from server: ' + evt.data);
};

websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
};
</script>
HTML
        );
    });

    $server->start();
});

3.2.2 运行服务 (进入网站根目录再运行)

php ws_server.php

运行后,什么内容都不会显示

如果出现错误:PHP Fatal error:  Uncaught Swoole\Exception: failed to listen server port[0.0.0.0:9502], Error: Address already in use

说明服务已经启用,用命令查下端口

netstat -anp  | grep  9502

3.2.3 客户端测试 打开GOOGLE浏览器

打开使用 Chrome 浏览器进行测试,JS 代码为:

var wsServer = 'ws://127.xxx.xxx.1:9502';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) {
    console.log("Connected to WebSocket server.");
};

websocket.onclose = function (evt) {
    console.log("Disconnected");
};

websocket.onmessage = function (evt) {
    console.log('Retrieved data from server: ' + evt.data);
};

websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
};

输入上面这段代码,按回车键,几秒后返回错误,此时要检查9502这个端口是开放

1.宝塔中开放9502端口

2.在阿里云ECS安全组中开放9502端口

再测试代码:

返回:hello,welcome 成功 

四、反向代理,启用域名来访问不用IP和端口

4.1、http设置

var wsServer = 'ws://114.xxx.xx.xxx:9502';

换成域名

var wsServer = 'ws://www.xxx.com';

宝塔设置

    # 反向代理 转移到本地的websocket地址,wss://127.0.0.1:2000 ,对应 wss://xxx.xxx.com
    location / {
        proxy_redirect off;
        proxy_pass http://114.xxx.xxx.xxx:9502; 
        proxy_set_header Host $host;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

4.2、https宝塔设置

 先按4.1的设置,然后再给这个域名申请证书就OK;

不是平台那个域名证书,要专门为这个域名申请一个证书

五、实测用户发送、客服回复(测试ws_server.php这个服务)

5.1 优化ws_server.php 代码,能回复

只要修改了这个代码,就需要重新启动这个服务(在命令窗口中重启)

<?php

$server = new Swoole\WebSocket\Server("0.0.0.0", 9502);

$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
    echo "server: handshake success with fd={$request->fd}\n";
});

$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    $json=json_decode($frame->data,true);
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($json['fd'], $json['content']);
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

5.2 前端HTML代码

把代码上传到服务器②(不是上面那个反向代理网站①)或在本地打开

代码下载:在swoole服务中测试发送、回复代码-互联网文档类资源-CSDN下载

修改js/chatbox.js文件中接口网址:修改自己的网址

var wsServer = 'ws://wss.xxx.com.cn?type=websocket&userid=

接受者id(刷新网页后,命令窗口中就会出现)

两边能收到证明成功.

六、如何重启服务

如果服务已经启动,需要重新运行

6.1 查找对应的服务

ps -ef | grep server  //粗略

或

ps -ef | grep ws_server.php

找到进程ID:2449; 然后杀掉这个进程,再运行服务

kill 2449
//如果上面杀不掉,用下面这个命令
kill -9 2449

php ws_server.php

七、swoole常用配置设置

常用到的配置参考:Swoole的常用配置 - 阿埋顶级工程师 - OSCHINA - 中文开源技术交流社区

swoole中swoole_server可以通过$serv->set($array config)来设置Swoole运行的配置.
这样的配置使, Swoole更加的灵活.

如:
    常用配置
    $serv = new swoole_server("0.0.0.0", 9501);
    $serv->set(array(
        'reactor_num' => 8,             # 指定Reactor线程数
        'worker_num' => 8,              # 启动的worker进程数量
        'max_request' => 10000,         # 每个worker进程允许处理的最大任务数量
        'max_conn' => 100000,           # 服务器允许位置的最大TCP连接数量
        'dispatch_mode' => 2,           # 指定数据包的分发策略
        'daemonize' => 1,               # 是否作为守护进程
        'task_worker_num' => 8,         # 服务器开启的task进程数
        'task_max_request' => 10000,    # 每个task进程允许处理的最大任务数。
        'log_file' => '/data/log/swoole.log',   # 指定日志文件路径
        'log_level' => '1',             # 设置 swoole_server 错误日志打印的等级
        'chroot' => '/data/server/',    # 重定向Worker进程的文件系统根目录
        'user' => 'www',                # 设置worker/task子进程的所属用户
        'group' => 'www'                # 设置worker/task子进程的所属用户组
        'heartbeat_check_interval' => 5,# 设置心跳检测间隔
        'heartbeat_idle_time' => 600    # 设置某个连接允许的最大闲置时间
    ));
    

在ws_server.php设置

$server->set(array(
    'heartbeat_check_interval' => 180,# 设置心跳检测间隔
    'heartbeat_idle_time' => 600    # 设置某个连接允许的最大闲置时间
));
红色的标注表示比较重要的参数,可以影响到服务器的运行性能,务必注意
 
1.worker_num
 
描述:指定启动的worker进程数。
说明:swoole是master-> n * worker的模式,开启的worker进程数越多,server负载能力越大,但是相应的server占有的内存也会更多,
建议开启的worker进程数为cpu核数的1-4倍。
示例:
'worker_num' => 8
 
2.max_request
描述:每个worker进程允许处理的最大任务数。
说明:设置该值后,每个worker进程在处理完max_request个请求后就会自动重启。设置该值的主要目的是为了防止worker进程处理大量请求后可能引起的内存溢出。
示例:
'max_request' => 10000
 
3.max_conn
描述:服务器允许维持的最大TCP连接数
说明:设置此参数后,当服务器已有的连接数达到该值时,新的连接会被拒绝。另外,该参数的值不能超过操作系统ulimit -n的值,同时此值也不宜设置过大,
因为swoole_server会一次性申请一大块内存用于存放每一个connection的信息。
示例:
'max_conn' => 10000
 
4.ipc_mode
描述:设置进程间的通信方式。
说明:共有三种通信方式,参数如下:
        1 => 使用unix socket通信
        2 => 使用消息队列通信
        3 => 使用消息队列通信,并设置为争抢模式
示例:
'ipc_mode' => 1
 
5.dispatch_mode
描述:指定数据包分发策略。
说明:共有三种模式,参数如下:
        1 => 轮循模式,收到会轮循分配给每一个worker进程
        2 => 固定模式,根据连接的文件描述符分配worker。这样可以保证同一个连接发来的数据只会被同一个worker处理
        3 => 抢占模式,主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker
示例:
'dispatch_mode' => 2
 
6.task_worker_num
描述:服务器开启的task进程数。
说明:设置此参数后,服务器会开启异步task功能。此时可以使用task方法投递异步任务。
      设置此参数后,必须要给swoole_server设置onTask/onFinish两个回调函数,否则启动服务器会报错。
 
示例:
'task_worker_num' => 8
 
7.task_max_request
描述:每个task进程允许处理的最大任务数。
说明:参考max_request task_worker_num
示例:
'task_max_request' => 10000
 
8.task_ipc_mode
描述:设置task进程与worker进程之间通信的方式。
说明:参考ipc_mode
示例:
'task_ipc_mode' => 2
 
9.daemonize(非常重要)
描述:设置程序进入后台作为守护进程运行。
说明:长时间运行的服务器端程序必须启用此项。如果不启用守护进程,当ssh终端退出后,程序将被终止运行。启用守护进程后,标准输入和输出会被重定向到 log_file,
如果 log_file未设置,则所有输出会被丢弃。
示例:
'daemonize' => 0
 
10.log_file
描述:指定日志文件路径
说明:在swoole运行期发生的异常信息会记录到这个文件中。默认会打印到屏幕。注意log_file 不会自动切分文件,所以需要定期清理此文件。
示例:
'log_file' => '/data/log/swoole.log'
 
11.heartbeat_check_interval
描述:设置心跳检测间隔
说明:此选项表示每隔多久轮循一次,单位为秒。每次检测时遍历所有连接,如果某个连接在间隔时间内没有数据发送,则强制关闭连接(会有onClose回调)。
示例:
'heartbeat_check_interval' => 60
 
12.heartbeat_idle_time
描述:设置某个连接允许的最大闲置时间。
说明:该参数配合heartbeat_check_interval使用。每次遍历所有连接时,如果某个连接在heartbeat_idle_time时间内没有数据发送,则强制关闭连接。
默认设置为heartbeat_check_interval * 2。
示例:
'heartbeat_idle_time' => 600
 
13.open_eof_check
描述:打开eof(end  of  file  文件读取到尾部)检测功能
说明:与package_eof 配合使用。此选项将检测客户端连接发来的数据,当数据包结尾是指定的package_eof 字符串时才会将数据包投递至Worker进程,否则会一直拼接
数据包直到缓存溢出或超时才会终止。一旦出错,该连接会被判定为恶意连接,数据包会被丢弃并强制关闭连接。
EOF检测不会从数据中间查找eof字符串,所以Worker进程可能会同时收到多个数据包,需要在应用层代码中自行explode("\r\n", $data) 来拆分数据包
示例:
'open_eof_check' => true
 
14.package_eof
描述:设置EOF字符串
说明:package_eof最大只允许传入8个字节的字符串
示例:
'package_eof ' => '/r/n'
 
15.open_length_check
描述:打开包长检测
说明:包长检测提供了固定包头+包体这种格式协议的解析,启用后,可以保证Worker进程onReceive每次都会收到一个完整的数据包。
示例:
'open_length_check' => true
 
16.package_length_offset
描述:包头中第几个字节开始存放了长度字段
说明:配合open_length_check使用,用于指明长度字段的位置。
示例:
'package_length_offset' => 5
 
17.package_body_offset
描述:从第几个字节开始计算长度。
说明:配合open_length_check使用,用于指明包头的长度。
示例:
'package_body_offset' => 10
 
18.package_length_type
描述:指定包长字段的类型
说明:配合open_length_check使用,指定长度字段的类型,参数如下:
        's' => int16_t 机器字节序
        'S' => uint16_t 机器字节序
        'n' => uint16_t 大端字节序
        ’N‘ => uint32_t 大端字节序
        'L' => uint32_t 机器字节序
        'l' => int 机器字节序
示例:
'package_length_type' => 'N'
 
19.package_max_length
描述:设置最大数据包尺寸
说明:该值决定了数据包缓存区的大小。如果缓存的数据超过了该值,则会引发错误。具体错误处理由开启的协议解析的类型决定。
示例:
'package_max_length' => 8192
 
20.open_cpu_affinity
描述:启用CPU亲和性设置
说明:在多核的硬件平台中,启用此特性会将swoole的reactor线程/worker进程绑定到固定的一个核上。可以避免进程/线程的运行时在多个核之间互相切换,提高CPU Cache
的命中率。
示例:
'open_cpu_affinity' => true
 
21.open_tcp_nodelay
描述:启用open_tcp_nodelay
说明:开启后TCP连接发送数据时会无关闭Nagle合并算法,立即发往客户端连接。在某些场景下,如http服务器,可以提升响应速度。
示例:
'open_tcp_nodelay' => true
 
22.tcp_defer_accept
描述:启用tcp_defer_accept特性
说明:启动后,只有一个TCP连接有数据发送时才会触发accept。
示例:
'tcp_defer_accept' => true
 
以下是对这个参数的解释:
建立连接后,立即读数据(读取HTTP请求)。在这种情况下,client发出ACK(ACK (Acknowledgement),即确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。)之后,server被唤醒(accept返回),并立即试图读数据,由于client还没有发数据,server又再度阻塞。这对于调度是一种浪费。
另外,client发的那个ACK也没有实际作用,是不必要的。
假如server端内核忽略client发的ACK,而直接等待数据,数据收到之后再唤醒serve(accept返回),server醒来后就可以直接得到数据并处理。这就是TCP_DEFER_ACCEPT的作用。
 
23.ssl_cert_file和ssl_key_file
描述:设置SSL隧道加密
说明:设置值为一个文件名字符串,指定cert证书和key的路径。
示例:
'ssl_cert_file' => '/config/ssl.crt',
'ssl_key_file' => '/config//ssl.key',
 
24.open_tcp_keepalive
描述:打开TCP的KEEP_ALIVE选项
说明:使用TCP内置的keep_alive属性,用于保证连接不会因为长时闲置而被关闭。
示例:
'open_tcp_keepalive' => true
 
25.tcp_keepidle
描述:指定探测间隔。
说明:配合open_tcp_keepalive使用,如果某个连接在tcp_keepidle内没有任何数据来往,则进行探测。
示例:
'tcp_keepidle' => 600
 
26.tcp_keepinterval
描述:指定探测时的发包间隔
说明:配合open_tcp_keepalive使用
示例:
'tcp_keepinterval' => 60
 
27.tcp_keepcount
描述:指定探测的尝试次数
说明:配合open_tcp_keepalive使用,若tcp_keepcount次尝试后仍无响应,则判定连接已关闭。
示例:
'tcp_keepcount' => 5
 
28.backlog
描述:指定Listen队列长度
说明:此参数将决定最多同时有多少个等待accept的连接。
示例:
'backlog' => 128
 
29.reactor_num
描述:指定Reactor线程数
说明:设置主进程内事件处理线程的数量,默认会启用CPU核数相同的数量, 一般设置为CPU核数的1-4倍,最大不得超过CPU核数*4。
示例:
'reactor_num' => 8
 
30.task_tmpdir
描述:设置task的数据临时目录
说明:在swoole_server中,如果投递的数据超过8192字节,将启用临时文件来保存数据。这里的task_tmpdir就是用来设置临时文件保存的位置。
    需要swoole-1.7.7+
示例:
'task_tmpdir' => '/tmp/task/'

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐