Swoole笔记(三)

@date:2017-06-04 17:00:00

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/即可看到输出。

Build by Loppo 0.6.14