ArcherWong博客
首页
博客
swoole创建websocket服务器
作者:ArcherWong
分类:php
时间:2019-01-04 10:37:36
阅读:18
[TOC] swoole算是nodejs在php中的一种实现,异步响应请求,性能超强 # 1 安装准备 ## 1.1 安装swoole前必须保证系统已经安装了下列软件 php-5.3.10 或更高版本 gcc-4.4 或更高版本 make autoconf pcre (centos系统可以执行命令:yum install pcre-devel) ## 1.2 下载并解压 下载地址 https://github.com/swoole/swoole-src/releases 进入页面后选择download链接下的tar.gz的压缩包 下载源代码包后,解压 ``` tar xzvf xxx.tar.gz ``` 在终端进入源码目录,执行下面的命令进行编译和安装 ``` cd swoole phpize ./configure --enable-swoole-debug make sudo make install ``` 编译参数根据自己的需求选择,详情参看官方文档。 ## 1.3 编译安装成功后,修改php.ini 在php.ini中加入 `extension=swoole.so` 通过在命令行使用 php-m查看,是否安装了swoole 注意:如通重新编译的话需要 make clean # 2 构建Swoole基本实例 ## 2.1 tcp服务器实例 (来自w3cschool教程https://www.w3cschool.cn/swoole/bnte1qcd.html) **服务端代码:Server.php** ``` <?php // Server class Server { private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); } public function onStart( $serv ) { echo "Start\n"; } public function onConnect( $serv, $fd, $from_id ) { $serv->send( $fd, "Hello {$fd}!" ); } public function onReceive( swoole_server $serv, $fd, $from_id, $data ) { echo "Get Message From Client {$fd}:{$data}\n"; } public function onClose( $serv, $fd, $from_id ) { echo "Client {$fd} close connection\n"; } } // 启动服务器 $server = new Server(); ``` 从代码中可以看出,创建一个swoole_server基本分三步: 1. 通过构造函数创建swoole_server对象 2. 调用set函数设置swoole_server的相关配置选项 3. 调用on函数设置相关回调函数 关于set配置选项以及on回调函数的具体说明,请参考我整理的swoole文档( 配置选项) 这里只给出简单介绍。onStart回调在server运行前被调用,onConnect在有新客户端连接过来时被调用,onReceive函数在有数据发送到server时被调用,onClose在有客户端断开连接时被调用。 这里就可以大概看出如何使用swoole:在onConnect处监听新的连接;在onReceive处接收数据并处理,然后可以调用send函数将处理结果发送出去;在onClose处处理客户端下线的事件。 **客户端的代码:Client.php** ``` <?php class Client { private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP); } public function connect() { if( !$this->client->connect("127.0.0.1", 9501 , 1) ) { echo "Error: {$fp->errMsg}[{$fp->errCode}]\n"; } $message = $this->client->recv(); echo "Get Message From Server:{$message}\n"; fwrite(STDOUT, "请输入消息:"); $msg = trim(fgets(STDIN)); $this->client->send( $msg ); } } $client = new Client(); $client->connect(); ``` 这里,通过swoole_client创建一个基于TCP的客户端实例,并调用connect函数向指定的IP及端口发起连接请求。随后即可通过recv()和send()两个函数来接收和发送请求。需要注意的是,这里我使用了默认的同步阻塞客户端,因此recv和send操作都会产生网络阻塞。 **使用方法** 进入到文件目录,在窗口1先启动php Serve.php,然后再开一个窗口(窗口2)启动php Client.php 窗口1内容: ``` # root @ WENGINE in /data/learnSwoole [9:24:57] C:130 $ php Server.php Start Get Message From Client 1:ceshi1 Client 1 close connection ``` 窗口2内容: ``` # root @ WENGINE in /data/learnSwoole [9:23:07] $ php Client.php Get Message From Server:Hello 1! 请输入消息:ceshi1 ``` ## 2.2 web服务器 **服务端代码 http_server.php** ``` $http = new swoole_http_server("0.0.0.0", 9501); $http->on('request', function ($request, $response) { var_dump($request->get, $request->post); $response->header("Content-Type", "text/html; charset=utf-8"); $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>"); }); $http->start(); ``` Http服务器只需要关注请求响应即可,所以只需要监听一个onRequest事件。当有新的Http请求进入就会触发此事件。事件回调函数有2个参数,一个是$request对象,包含了请求的相关信息,如GET/POST请求的数据。 另外一个是response对象,对request的响应可以通过操作response对象来完成。$response->end()方法表示输出一段HTML内容,并结束此请求。 ● 0.0.0.0 表示监听所有IP地址,一台服务器可能同时有多个IP,如127.0.0.1本地回环IP、192.168.1.100局域网IP、210.127.20.2 外网IP,这里也可以单独指定监听一个IP ● 9501 监听的端口,如果被占用程序会抛出致命错误,中断执行。 ## 2.3 WebSocket服务器 **服务端程序代码 ws_server.php** ``` //创建websocket服务器对象,监听0.0.0.0:9502端口 $ws = new swoole_websocket_server("0.0.0.0", 9502); //监听WebSocket连接打开事件 $ws->on('open', function ($ws, $request) { var_dump($request->fd, $request->get, $request->server); $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(); ``` WebSocket服务器是建立在Http服务器之上的长连接服务器,客户端首先会发送一个Http的请求与服务器进行握手。握手成功后会触发onOpen事件,表示连接已就绪,onOpen函数中可以得到$request对象,包含了Http握手的相关信息,如GET参数、Cookie、Http头信息等。 建立连接后客户端与服务器端就可以双向通信了。 ● 客户端向服务器端发送信息时,服务器端触发onMessage事件回调 ● 服务器端可以调用$server->push()向某个客户端(使用$fd标识符)发送消息 ● 服务器端可以设置onHandShake事件回调来手工处理WebSocket握手 运行程序 **客户端的代码** 可以使用Chrome浏览器进行测试,JS代码为: ``` var wsServer = 'ws://127.0.0.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); }; ``` - 不能直接使用swoole_client与websocket服务器通信,swoole_client是TCP客户端 - 必须实现WebSocket协议才能和WebSocket服务器通信,可以使用swoole/framework提供的PHP WebSocket客户端 - WebSocket服务器除了提供WebSocket功能之外,实际上也可以处理Http长连接。只需要增加onRequest事件监听即可实现Comet方案Http长轮询。 *关于onRequest回调* swoole_websocket_server 继承自 swoole_http_server - 设置了onRequest回调,websocket服务器也可以同时作为http服务器 - 未设置onRequest回调,websocket服务器收到http请求后会返回http 400错误页面 - 如果想通过接收http触发所有websocket的推送,需要注意作用域的问题,面向过程请使用“global”对swoole_websocket_server进行引用,面向对象可以把swoole_websocket_server设置成一个成员属性 可以创建更多的服务器 参照官方文档尝试 https://wiki.swoole.com/wiki/page/475.html # 3 使用laravel5.5实现的前后台通信实例 主要思路是使用php artisan 自建命令控制服务端,使用HTML5的websocket实现客户端功能 **服务端:app/Console/Commands/Websocket.php内容** ``` <?php namespace App\Console\Commands; use Illuminate\Console\Command; use swoole_http_request; use swoole_http_response; use swoole_websocket_server; class WebSocket extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'websocket {cmd=start : can use start|stop|status|restart} {--daemon : set to run in daemonize mode} '; /** * The console command description. * * @var string */ protected $description = 'swoole server control'; /** * server * * @var swoole_websocket_server */ private $server; /** * * TYPE_ADMIN * */ const TYPE_ADMIN = 0X00; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * 处理不同command信息 * * @return mixed */ public function handle() { $command = $this->argument('cmd'); $option = $this->option('daemon'); switch ($command) { case 'start': $this->initWs($option); break; case 'stop': $res = $this->sendAdminRequest($command); if ($res){ $this->info('stop the server successfully'); } else { $this->info('the server is not running'); } break; case 'status': $res = $this->sendAdminRequest($command); if ($res){ $this->info($res); } else { $this->info('the server is not running'); } break; case 'restart': $res = $this->sendAdminRequest($command); if ($res){ $this->info('restart the server successfully'); } else { $this->info('the server is not running'); } break; default: $this->info('请按照下面格式输入命令:php artisan websocket {start|stop|status|restart}'); break; } } //初始化服务端 public function initWs($daemonize = false) { if ($daemonize) { $this->info('Starting Websocke server in daemon mode...'); } else { $this->info('Starting Websocke server in interactive mode...'); } $server = new swoole_websocket_server('0.0.0.0', 9501); $server->set([ 'daemonize' => $daemonize, 'log_file' => '/var/log/websocket.log' ]); $server->on('close', function ($server, $fd) { $this->info('close websocket server'); }); $server->on('open', function (swoole_websocket_server $server, $request) { $this->info('websocket open'); }); $server->on('open', [$this, 'onOpen']); $server->on('close', [$this, 'onClose']); $server->on('message', [$this, 'onMessage']); $server->on('request', [$this, 'onRequest']); $this->server = $server; $this->server->start(); } public function onOpen(swoole_websocket_server $server, $request) { $this->info('websocket open'); } public function onClose($server, $fd) { $this->info('close websocket server'); } public function onMessage(swoole_websocket_server $server, $frame) { $this->info($frame->data); $data = json_decode($frame->data, true); //对data进行逻辑处理 $reply = '发送的信息是:' . $data['message']; $response = [ 'status' => true, 'data' => $reply ]; $server->push($frame->fd, json_encode($response)); } //websocket客户端同样支持http协议 public function onRequest(swoole_http_request $request, swoole_http_response $response) { if ($request->post['type'] == self::TYPE_ADMIN) { $ret = json_encode($this->commandHandle($request->post['content'])); return $response->end($ret); } } //操作命名 public function commandHandle($command) { if ($command == 'status') { $this->info('handle status'); return $this->server->stats(); } if ($command == 'restart') { $this->info('handle restart'); return $this->server->reload(); } if ($command == 'stop') { $this->info('handle stop'); return $this->server->stop() && $this->server->shutdown(); } return 'Unknown Command'; } //发送http请求 public function sendAdminRequest($content) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:9501"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'type' => self::TYPE_ADMIN, 'content' => $content ]); $response = curl_exec($ch); curl_close($ch); return $response; } } ``` **客户端内容** ``` <!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>websocket client</title> </head> <body> <div> <input id="message-content" type="text" name="message" /> <button onclick="sendMessage()">发送消息</button> </div> </body> <script> var wsServer = 'ws://115.159.81.46:9501'; 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('从服务器接收到json信息: ' + evt.data); alert('服务器返回信息:' + JSON.parse(evt.data).data); }; websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; function sendMessage(){ var content = document.getElementById('message-content').value; var data = { message : content, } websocket.send(JSON.stringify(data)); }; </script> </html> ``` **启动websocket服务器** 进入系统根目录, php artisan websocket [--daemon] //是否使用daemon模式 php artisan websocket start|stop|status|restart //默认是start
标签:
上一篇:
算法总结
下一篇:
cgi、fastcgi、php-cgi、php-fpm的关系
文章分类
css
elasticsearch
git
golang
guacamole
javascript
letsencrypt
linux
nginx
other
php
python
vue
web
阅读排行
编码总结
详解网络连接
tcpdump使用
JWT
websocket协议
友情链接
node文件
laravel-vue
ArcherWong的博客园