WebOrb.NET & server to client invocation
Хм.. даже не знаю с чего начать
Ладно, часто возникает задача, когда сервер должен извещать клиента о чем-то важном и полезном. Например - изменение состояния каких-то объектов мониторинга, готовность результатов работы какой-то длительной операции, изменение состояния других клиентов и т д.
Глобально, реализаций этого сценария 2:
- ping (клиент с каким-то интервалом опрашивает сервер)
- сервер сам нотифицирует клиента (клиент при этом должен быть подключенным)
О том как реализовать 2-й сценарий для WebObr.NET + Adobe Flex + RTMP речь пойдет ниже:
Начнем с серверной стороны. Я не буду рассказывать как создавать приложения под WebOrb, как деплоить и т д, об этом прекрасно написано в его документации, поэтому - с корабля на бал:
1. В веб проекте в файле Global.asax в методе Application_Start создаем RTMPServer (будет один на приложение):
var config = new Weborb.Config.ORBConfig();
var server = new Weborb.Messaging.RTMPServer(“MyCallBack”, 2037, 500, config);
server.setApplication(this);
server.start();
Application[ "MyCBServer" ] = server;* This source code was highlighted with Source Code Highlighter.
2. Здесь же, не забываем его тушить:
void Application_End(object sender, EventArgs e)
{
var server = Application["MyCBServer"] as RTMPServer;
if( server != null ) server.shutdown();
}* This source code was highlighted with Source Code Highlighter.
3. В веб сайте, в папке Applications, создаем новую папку: MyCallBack, куда помещаем файл app.config с следующим содержимым:
<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
<application-handler>
MySite.Applications.MyCallBack.MyApplicationAdapter
</application-handler>
</configuration>* This source code was highlighted with Source Code Highlighter.
То что мы только что прописали - ссылка на класс ApplicationAdapter, который будет обслуживать наше приложение MyCallBack. Осталось его написать:
4. Будет он в нашем тесте приблизительно таким:
public class MyApplicationAdapter : ApplicationAdapter
{
public override bool appConnect(IConnection conn, object[] parms)
{
IClient client = conn.getClient();
if(parms != null && parms.Length > 0)
client.setAttribute(“sessionId”, parms[0]);
return base.appConnect(conn, parms);
}public override void appDisconnect(IConnection conn)
{
base.appDisconnect(conn);
}public void AddTask(string sessionId, string taskName)
{
ThreadPool.QueueUserWorkItem(FakeTaskInvoker, new string[] {sessionId, taskName});
}private void FakeTaskInvoker(object param)
{
string[] ps = (string[]) param;
Thread.Sleep(5000);
string taskName = ps[1];
string result = string.Format(“Congradulations! Task ‘{0}’ has been completed”, taskName) ;
string sessionId = ps[0];lock (_locker)
{
IEnumerator<IConnection> connections = scope.getConnections();
// find client and invoke it result function
while (connections.MoveNext())
{
IConnection connection = connections.Current;
IClient client = connection.getClient();
string sid = Convert.ToString(client.getAttribute(“sessionId”));
//
if (connection is IServiceCapableConnection && sessionId == sid)
{
((IServiceCapableConnection)connection).invoke(“serverTaskComplete”, new object[] { taskName, result });
break;
}
}
}}
}* This source code was highlighted with Source Code Highlighter.
Как видим, он наследуется от ApplicationAdapter и позволяет переопределить подключение клиентов, отключение клиентов и т д. Какую логику он здесь выполняет:
при подключении клиента запоминает ID сессии (здесь увы нет HttpContext). При вызове клиентом метода AddTask, ждет 5 секунт, ищет клиента среди подключенных и вызывает удаленный клиентский метод.
5. Клиент. Мого буков не будет, пусть код говорит сам за себя (RTMPBridge.as):
[Event(name="serverTaskComplete", type="mx.rpc.events.ResultEvent")]
public class RTMPBridge extends EventDispatcher
{
private var _connection:NetConnection;
private var _sessionId:String =“someSessionId”;public function RTMPBridge()
{
_connection = new NetConnection();
_connection.client = this;
_connection.objectEncoding = ObjectEncoding.AMF0;
_connection.addEventListener( NetStatusEvent.NET_STATUS, handleNetStatus );
_connection.connect( “rtmp://localhost:2037/MyCallBack”,_sessionId);
}private function handleNetStatus(event:NetStatusEvent):void {
}
public function addTask(taskName:String):void {
var responder:Responder = new Responder(onTaskSend, onTaskFault);
_connection.call(“AddTask”, responder, _clientId, taskName);
}private function onTaskSend(event:ResultEvent):void {
}
private function onTaskFault(event:FaultEvent):void {
}
public function serverTaskComplete( taskName:String, taskResult:Object ) : void
{
dispatchEvent(new ResultEvent(“serverTaskComplete”, false, true, [taskName, taskResult]));
}}
* This source code was highlighted with Source Code Highlighter.
Ну вот и все. Естественно, скопилось много вопросов - откуда взялся sessionId, почему пустые обработчики статусов и ошибок и т д. - ответов на них не будет ![]()
только начал со всем этим разбираться, свключая .net
один небольшой вопрос..
string sid = Convert.ToString(client.getAttribute(“sessionId”));
sid выглядит как “String type. Value - ABCDEF012345″, где ABCDEF012345 - собствено sessionID.
Почему toString() возвращает этот бред ?
getAttribute() метод вернет точно то что было установлено в строке:
client.setAttribute(“sessionId”, parms[0]);
В моем примере этот параметр пришел с флекса:
_connection.connect( “rtmp://localhost:2037/MyCallBack”, _sessionId );
Это сделано для возможности различать клиентов + хранить какие-то их метаданные
для чего это - понятно )
делал по вашему примеру setAttribute, getArrtibute - ошибиться тут вроде не в чем.. но гетАттрибут после конвертации в стринг возвращает совсем не то
WebOrb 3.6, VStudio 2010, NetFramework 4.0
Да, там реально не строка. В дебагере это можно увидеть. Там будет представитель IAdaptingTypes. То есть если надо достать строку -