oncesk / yii-node-socket
通过socket连接实现php和client javascript之间的连接。
Requires
- oncesk/elephant.io: dev-master
This package is not auto-updated.
Last update: 2024-09-24 15:17:21 UTC
README
在单个Yii应用程序中连接php、javascript和nodejs。
####Yii1
你好,如果你需要使用yii1,你可以从(https://github.com/oncesk/yii-node-socket/tree/2.0.0)操作。
####你能做什么
- 向所有客户端或具体房间或具体频道发送事件
- 现在你可以通过用户id向单个(具体用户)发送事件!
- 在window上下文中调用某些函数或对象方法
- 你可以使用jQuery从php更改DOM模型
- 能够在你的javascript应用程序中设置数据和获取数据
- 从javascript向所有客户端或具体房间或频道发送事件
##更改
- 已更新为支持Yii 2.0
- 添加了命名空间,已更新为支持Yii 2.0
- 更新了Composer设置以与yii扩展更新一起工作
##要求
- linux/unix/windows
- git
- 虚拟私有服务器或专用服务器(用于nodejs进程)
- 必须安装或启用curl
#安装
如果尚未安装,请安装nodejs,请参阅 https://node.org.cn/
安装扩展
- Composer
{ "require" : { "oncesk/yii-node-socket" : "2.0.4" } }
- 使用git clone
$> git clone https://github.com/oncesk/yii-node-socket.git
现在转到你安装扩展的文件夹 application.ext.yii-node-socket 并执行
$> git submodule init $> git submodule update
Yii配置
- 在(console/config/main.php)中配置控制台命令。你可以使用以下配置
'controllerMap' => [ 'node-socket' => '\YiiNodeSocket\NodeSocketCommand', ],
- 注册Yii组件,需要将其添加到你的前端应用程序的 frontend/config/main.php
'nodeSocket' => [ 'class' => '\YiiNodeSocket\NodeSocket', 'host' => 'localhost', 'allowedServerAddresses' => [ "localhost", "127.0.0.1" ], 'origin' => '*:*', 'sessionVarName' => 'PHPSESSID', 'port' => 3001, 'socketLogFile' => '/var/log/node-socket.log', ],
注意: host 应该是类似于你在虚拟主机配置中的域名或如果你使用ip地址请求页面,则为服务器ip地址
- 在(common/config/main.php)的常见配置中配置别名。
- 第一个是为了让Yii找到PHP命名空间,第二个是为了找到JS资产。
- 你可以使用以下配置
'aliases' => [ '@YiiNodeSocket' => '@vendor/oncesk/yii-node-socket/lib/php', '@nodeWeb' => '@vendor/oncesk/yii-node-socket/lib/js' ],
注意:如果你将使用 behaviors 或 node-socket模型,你需要将nodeSocket组件添加到 preload 组件列表中
'bootstrap' => ['log', 'nodeSocket'],
在 application.ext.yii-node-socket.lib.js.server 中安装 nodejs 组件
$> npm install
如果你在执行此命令时遇到错误,请尝试以下操作
$> npm install --no-bin-links
祝贺你,安装完成!
注意:如果你的组件名称不是 nodeSocket,你需要使用特殊键在控制台命令中使用 --componentName=component_name
###控制台命令操作
使用 (./yiic node-socket)
$> ./yiic help node-socket # show help $> ./yiic node-socket/start # start server $> ./yiic node-socket/stop # stop server $> ./yiic node-socket/restart # restart server $> ./yiic node-socket/getPid # show pid of nodejs process
##定义
- 框架 - 包裹在类中的数据包,用于nodejs服务器。针对nodejs服务器的每个请求,你只能发送1个框架。如果要同时发送多个框架,请使用多个框架。
- 房间 - 一个或多个客户端在具体命名空间中:每个客户端都可以创建房间,其他客户端可以加入具体房间,房间中的任何客户端都可以在这个房间中发送事件。
##Javascript
在javascript中使用之前,注册客户端脚本,如这里所示。在Yii 2.0中已弃用 - 资产管理器按需注册文件。
public function actionIndex() { // register node socket scripts // No longer needed - Yii::app()->nodeSocket->registerClientScripts(); }
###事件
###在javascript中工作
使用 YiiNodeSocket
类
####开始工作
// create object var socket = new YiiNodeSocket(); // enable debug mode socket.debug(true); socket.onConnect(function () { // fire when connection established }); socket.onDisconnect(function () { // fire when connection close or lost }); socket.onConnecting(function () { // fire when the socket is attempting to connect with the server }); socket.onReconnect(function () { // fire when successfully reconnected to the server });
####捕获事件
现在事件只能在PHP端创建。所有数据都以json格式传输。在回调函数中,数据作为javascript本地对象(或字符串、整数等)传递
// add event listener socket.on('updateBoard', function (data) { // do any action });
####房间
socket.onConnect(function () { socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { // success - boolean, numberOfRoomSubscribers - number of room members // if error occurred then success = false, and numberOfRoomSubscribers - contains error message if (success) { console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); // do something // bind events this.on('join', function (newMembersCount) { // fire on client join }); this.on('data', function (data) { // fire when server send frame into this room with 'data' event }); } else { // numberOfRoomSubscribers - error message alert(numberOfRoomSubscribers); } }); });
####通道
通道与房间非常相似,但您可以为客户端控制对通道的访问
// join to channel, join needed when you try subscribe to channel from javascript, if you subscribed to channel in php you can bind events without join socket.onConnect(function () { var testChannel = socket.channel('test').join(function (success) { // success - boolean if (success) { // fore getting channel attributes console.log(this.getAttributes()); // bind event listeners this.on('some_event', function (data) { // fire when server send frame into this room with 'data' event }); } else { console.log(this.getError()); } }); // you can bind events handlers for some events without join // in this case you should be subscribed to `test` channel socket.channel('test').on('some_event', function (data) { }); });
####发射事件
您可以向以下对象发射事件
- 所有客户端(包括事件发送者)
- 所有客户端(不包括事件发送者 - 广播。目前仅支持JavaScript广播。PHP广播即将推出)
- 具体房间的客户端
全局事件
socket.emit('global.event', { message : { id : 12, title : 'This is a test message to all including sender' } }); socket.broadcast.emit('global.event', { message : { id : 12, title : 'This is a test message to all excluding sender' } }); socket.on('global.event', function (data) { console.log(data.message.title); // you will see in console `This is a test message` });
房间事件
socket.onConnect(function () { var testRoom = socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { // success - boolean, numberOfRoomSubscribers - number of room members // if error occurred then success = false, and numberOfRoomSubscribers - contains error message if (success) { console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); // do something // bind events this.on('message', function (message) { console.log(message); }); this.on('ping', function () { console.log('Ping!'); }); this.emit('ping'); // emit ping event } else { // numberOfRoomSubscribers - error message alert(numberOfRoomSubscribers); } }); // emit message event testRoom.emit('message', { message : { id : 12, title : 'This is a test message' } }); });
####共享公共数据
您只能使用PublicData Frame(见下面的PHP部分)从PHP设置共享数据。要访问数据,您可以使用getPublicData(string key, callback fn)
方法
socket.getPublicData('error.strings', function (strings) { // you need to check if strings exists, because strings can be not setted or expired, if (strings) { // do something } });
##PHP
####行为
- YiiNodeSocket\Behaviors\ArChannel - 可以用于创建新通道。例如:您可以将此行为附加到User,结果任何用户都将有自己的通道,其他用户可以订阅特定用户的事件。
- YiiNodeSocket\Behaviors\ArSubscriber - 应该附加到可以订阅某些通道的对象。例如:模型User在一段时间内可以是通道和订阅者。
/** * * @method \YiiNodeSocket\Models\Channel getChannel() * @method \YiiNodeSocket\Frames\Event|null createEvent($name) */ class User extends CActiveRecord { ... public function behaviors() { return array( // attach channel behavior 'channel' => array( 'class' => '\YiiNodeSocket\Behaviors\ArChannel', 'updateOnSave' => true ), // attach subscriber behavior 'subscriber' => array( 'class' => '\YiiNodeSocket\Behaviors\ArSubscriber' ) ); } ... } // Example of subscribe $user1 = new User(); $user1->setAttributes($attributes); if ($user1->save()) { // imagine that $user1->id == 122 // user channel was created and sent to nodejs server // subscriber was created and sent to nodejs server // create second user $user2 = User::model()->findByPk(121); // now we can subscribe one user to second user $user1->subscribe($user2); // and $user2 can catch events from $user1 channel like in twitter } // Example of emit event in concrete channel $user = User::model()->findByPk(122); if ($user) { // First method $event = $user->createEvent('test_event'); if ($event) { // set event data $event->setData(array( 'black', 'red', 'white' )); // send event to user channel $event->send(); } // Second method with getting channel $channel = $user->getChannel(); if ($channel) { $event = $channel->createEvent('test_event'); // set event data $event->setData(array( 'black', 'red', 'white' )); // send event to user channel $event->send(); } } // Example of unsubscribe $user1 = User::model()->findByPk(122); $user2 = User::model()->findByPk(121); $user1->unSubscribe($user2); // now $user2 can not catch events in channel of $user1
####客户端授权
要授权客户端,您需要发送特殊的认证帧,您可以在您的user组件的afterLogin事件中这样做
protected function afterLogin($fromCookie) { parent::afterLogin($fromCookie); $frame = Yii::app()->nodeSocket->getFrameFactory()->createAuthenticationFrame(); $frame->setUserId($this->getId()); $frame->send(); }
之后,此用户只能接收为他的事件。见下面的示例,只向单个(具体)$user发送事件
// $user - the user model, which can receive this event $event = Yii::app()->nodeSocket->getFrameFactory()->createUserEventFrame(); $event->setUserId($user->id); $event->setEventName('message'); $event['text'] = 'Hello, how are you?'; $event->send();
和您的javascript
var socket = new YiiNodeSocket(); socket.on('message', function (message) { console.log(message.text); // render message });
####客户端脚本注册
public function actionIndex() { ... Yii::app()->nodeSocket->registerClientScripts(); ... }
####事件帧
... // create event frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); // set event name $frame->setEventName('updateBoard'); // set data using ArrayAccess interface $frame['boardId'] = 25; $frame['boardData'] = $html; // or you can use setData(array $data) method // setData overwrite data setted before $frame->send(); ...
####设置共享数据
您可以使用setLifeTime(integer $lifetime)方法为PublicData类设置过期时间
... // create frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); // set key in storage $frame->setKey('error.strings'); // set data $frame->setData($errorStrings); // you can set data via ArrayAccess interface // $frame['empty_name'] = 'Please enter name'; // set data lifetime $frame->setLifeTime(3600*2); // after two hours data will be deleted from storage // send $frame->send(); ...
####房间事件
... // create frame $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); // set event name $frame->setEventName('updateBoard'); // set room name $frame->setRoom('testRoom'); // set data $frame['key'] = $value; // send $frame->send(); ...
只有testRoom的成员可以捕获此事件
####调用客户端函数或方法
在您的PHP应用程序中,您可以调用window上下文中的javascript函数或对象的方法。
$invokeFrame = Yii::app()->nodeSocket->getFrameFactory()->createInvokeFrame(); $invokeFrame->invokeFunction('alert', array('Hello world')); $invokeFrame->send(); // alert will be showed on all clients
继承自Event frame => 您可以将其发送到特定房间
####使用jQuery进行DOM操作
任务:您需要在每个产品的价格更新后在客户端更新价格
... $product = Product::model()->findByPk($productId); if ($product) { $product->price = $newPrice; if ($product->save()) { $jFrame = Yii::app()->nodeSocket->getFrameFactory()->createJQueryFrame(); $jFrame ->createQuery('#product' . $product->id) ->find('span.price') ->text($product->price); $jFrame->send(); // and all connected clients will can see updated price } } ...
####一次性发送多个帧
示例 1
$multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); $eventFrame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); $eventFrame->setEventName('updateBoard'); $eventFrame['boardId'] = 25; $eventFrame['boardData'] = $html; $dataEvent = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); $dataEvent->setKey('error.strings'); $dataEvent['key'] = $value; $multipleFrame->addFrame($eventFrame); $multipleFrame->addFrame($dataEvent); $multipleFrame->send();
示例 2
$multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); $eventFrame = $multipleFrame->createEventFrame(); $eventFrame->setEventName('updateBoard'); $eventFrame['boardId'] = 25; $eventFrame['boardData'] = $html; $dataEvent = $multipleFrame->createPublicDataFrame(); $dataEvent->setKey('error.strings'); $dataEvent['key'] = $value; $multipleFrame->send();
##PS
抱歉我的英语不好 :)