oncesk/yii-node-socket

该软件包最新版本(2.0.8)没有可用的许可信息。

通过socket连接实现php和client javascript之间的连接。

2.0.8 2016-04-19 07:34 UTC

This package is not auto-updated.

Last update: 2024-09-24 15:17:21 UTC


README

Join the chat at https://gitter.im/oncesk/yii-node-socket

在单个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

抱歉我的英语不好 :)