WebSocket
使用Swoole可以很簡單的搭建異步非阻塞多進程的WebSocket服務器。
WebSocket服務器
<?php
$server = new swoole_websocket_server("0.0.0.0", 9501);
$server->set(array(
'daemonize' => false,
'worker_num' => 2,
));
$server->on('Start', function (swoole_websocket_server $server) {
echo "Server Start... \n";
swoole_set_process_name("swoole_websocket_server");
});
$server->on('ManagerStart', function (swoole_websocket_server $server) {
echo "ManagerStart\n";
});
$server->on('WorkerStart', function (swoole_websocket_server $server, $worker_id) {
echo "WorkerStart \n";
if ($server->worker_id == 0){
swoole_timer_tick(10000,function($id) use ($server) {
echo "test timer\n";
});
}
});
$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) {
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
$server->push($frame->fd, "this is server");
});
$server->on('Close', function ($ser, $fd) {
echo "client {$fd} closed\n";
});
$server->start();
shell里直接運行php swoole_ws_server.php
啟動即可。如果設置了后台運行,可以使用下列命令強殺進程:
kill -9 $(ps aux|grep swoole|grep -v grep|awk '{print $2}')
或者重新啟動worker進程:
kill -10 $(ps aux|grep swoole_websocket_server|grep -v grep|awk '{print $2}')
輸出:
[2017-06-01 22:06:21 $2479.0] NOTICE Server is reloading now.
WorkerStart
WorkerStart
注意:
- onMessage回調函數為必選,當服務器收到來自客戶端的數據幀時會回調此函數。
/**
* @param $server
* @param $frame 包含了客戶端發來的數據幀信息;使用$frame->fd獲取fd;$frame->data獲取數據內容
*/
function onMessage(swoole_server $server, swoole_websocket_frame $frame)
- 使用
$server->push()
向客戶端發送消息。長度最大不得超過2M。發送成功返回true,發送失敗返回false。
function swoole_websocket_server->push(int $fd, string $data, int $opcode = 1, bool $finish = true);
WebSocket客戶端
最簡單的是使用JS編寫:
socket = new WebSocket('ws://192.168.1.107:9501/');
socket.onopen = function(evt) {
// 發送一個初始化消息
socket.send('I am the client and I\'m listening!');
};
// 監聽消息
socket.onmessage = function(event) {
console.log('Client received a message', event);
};
// 監聽Socket的關閉
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};
socket.onerror = function(evt) {
console.log('Client onerror',event);
};
Swoole里沒有直接提供swoole_websocket客戶端(注:自1.8.6后可以直接使用swoole內置的swoole_http_client實現swoole_websocket客戶端),不過通過引入WebSocketClient.php文件可以實現:
<?php
require_once __DIR__ . '/WebSocketClient.php';
$client = new WebSocketClient('192.168.1.107', 9501);
if (!$client->connect())
{
echo "connect failed \n";
return false;
}
$send_data = "I am client.\n";
if (!$client->send($send_data))
{
echo $send_data. " send failed \n";
return false;
}
echo "send succ \n";
return true;
上面代碼實現的是一個同步的swoole_websocket客戶端。發送完消息會自動關閉,可以用來與php-fpm應用協作:將耗時任務使用客戶端發送到swoole_websocket_server。
如何創建一個聊天室
實際項目里,我們可以將用戶uid和fd進行雙向綁定(暫不考慮多台服務器分布式部署情況),例如使用Redis保存:在onMessage進行用戶信息驗證后:
$this->redis->set($fd, $uid);
$this->redis->set($uid, $fd);
后續需要指定給某人發消息,只需要根據uid/fd發送即可。在onClose事件里進行解綁操作。群發的話只需要遍歷一遍$server->connections
即可。
示例(該項目只實現群發):
moell-peng/webim: PHP + Swoole 實現的簡單聊天室
https://github.com/moell-peng/webim
HttpServer
swoole內置Http服務器的支持。swoole版的http server相對於php-fpm,最大優勢在於高性能:代碼一次載入內存,后續無需再解釋執行。缺點是調試沒有nginx+php-fpm方便。
使用swoole,通過幾行代碼即可寫出一個異步非阻塞多進程的Http服務器:
<?php
$serv = new swoole_http_server("0.0.0.0", 9502);
$serv->on('Start', function() {
echo 'Start';
});
$serv->on('Request', function($request, $response) {
var_dump($request->get);
var_dump($request->post);
var_dump($request->cookie);
var_dump($request->files);
var_dump($request->header);
var_dump($request->server);
$response->cookie("User", "Swoole");
$response->header("X-Server", "Swoole");
$response->end("<h1>Hello Swoole!</h1>");
});
$serv->start();
shell里使用php swoole_http_server.php
運行server。瀏覽器打開http://192.168.1.107:9502/即可看到輸出。