[Flex]Flex与.NET互操作(十二):基于FluorineFx.Net的及时通信应用(Rem

mikel阅读(1123)

      远程共享对象(Remote Shared Objects) 可以用来跟踪、存储、共享以及做多客户端的数据同步操作。只要共享对象上的数据发生了改变,将会把最新数据同步到所有连接到该共享对象 的应用程序客户端。FluorineFx所提供的远程共享对象(Remote Shared Objects)和FMS的共享对象的功能是一样,对于熟悉FMS开发的朋友来说,学习FluorineFx的远程共享对象是非常简单的。

      共享对象可以在服务器端创建,也可以在客户端创建。在客户端创建共享对象的方法和使用FMS开发是一样的,创建一个 NetConnection对象,通过该对象的connect()方法连接到服务器,然后通过SharedObject.getRemote()方法就可 以在客户端创建一个远程共享对象。如下实例代码:

private function connectionServer():void
{
    var nc:NetConnection 
= new NetConnection();
    nc.connect(
"rtmp://localhost:1617/SOAPP","username","password")
    nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
    nc.client 
= this;
}
private function onStatusHandler(event:NetStatusEvent):void
{
    
if(event.info.code == "NetConnectin.Connect.Success")
    {
        createSharedObject();
    }
}
private function createSharedObject():void
{
    var so:SharedObject 
= SharedObject.getRemote("OnLineUsers",nc.uri,false);
    so.addEventListener(SyncEvent.SYNC,onSyncHandler);
    so.connect(
this.nc);
    so.client 
= this;
}
private function onSyncHandler(event:SyncEvent):void
{
  
//..do other
}

 

      在FluorineFx的服务器端创建远程共享对象和FMS有很大的区别,FluorineFx的 ISharedObjectService接口提供了专门用于创建远程共享对象的方法 CreateSharedObject(),ApplicationAdapter实现了此接口方法。定义如下:

 

public bool CreateSharedObject(IScope scope, string name, bool persistent)
{
      ISharedObjectService service 
= (ISharedObjectService)ScopeUtils.GetScopeService(scope, typeof(ISharedObjectService));
      
return service.CreateSharedObject(scope, name, persistent);
}

 

      如果要在服务器端创建远程共享对象,直接调用ApplicationAdapter类中的CreateSharedObject()方法就可以。如下在FluorineFx服务器端创建远程共享对象的代码块:

ISharedObject users_so = GetSharedObject(connection.Scope, "OnLineUsers");
if (users_so == null)
{
       
//创建共享对象
        CreateSharedObject(connection.Scope, "OnLineUsers"false);
        users_so 
= GetSharedObject(connection.Scope, "OnLineUsers");
}

 

      要想更新共享对象里的数据客户端还是使用setProperty()方法,而FluorineFx的服务器更新共享对象的方法则与 FMS不一样,使用的是FluorineFx.Messaging.Api.IAttributeStore接口提供的SetAttribute()和 RemoveAttribute()方法来更新共享对象里的数据。

      陆续介绍了这么多,下面通过一个案例来看看该这么去应用远程共享对象。比如做IM、视频聊天、视频会议等及时通信类型的应用中,用户上线下线的频率非常高,这时候我们就可以使用远程共享对象去做在线用户的数据同步。 

      首先建立FluorineFx服务库,并建立一个应用类继承于ApplicationAdapter,通过重写ApplicationAdapter的相关方法来实现应用程序的不同需求,详细如下代码块:

using System;
using System.Collections.Generic;
using System.Text;
using FluorineFx.Messaging.Adapter;
using FluorineFx;
using FluorineFx.Messaging.Api;
using System.Diagnostics;
using FluorineFx.Messaging.Api.SO;
using FluorineFx.Exceptions;
using FluorineFx.Context;
using FluorineFx.Messaging.Api.Service;
using System.Collections;
using Fx.Adapter.DTO;
namespace Fx.Adapter
{
    
/// <summary>
    
/// 自定义ApplicationAdapter
    
/// </summary>
    [RemotingService]
    
public class MyApp : ApplicationAdapter
    {
        
/// <summary>
        
/// 应用程序启动
        
/// </summary>
        
/// <param name="application"></param>
        
/// <returns></returns>
        public override bool AppStart(IScope application)
        {
            Trace.WriteLine(
"应用程序启动");
            
return true;
        }
        
/// <summary>
        
/// 房间启动
        
/// </summary>
        
/// <param name="room"></param>
        
/// <returns></returns>
        public override bool RoomStart(IScope room)
        {
            Trace.WriteLine(
"房间启动");
            
if (!base.RoomStart(room))
                
return false;
            
return true;
        }
        
/// <summary>
        
/// 接收客户端的连接
        
/// </summary>
        
/// <param name="connection"></param>
        
/// <param name="parameters"></param>
        
/// <returns></returns>
        public override bool AppConnect(IConnection connection, object[] parameters)
        {
            
string userName = parameters[0as string;
            
string password = parameters[1as string;
            
if (password == null || password == string.Empty)
                
throw new ClientRejectedException(null);
            connection.Client.SetAttribute(
"userName", userName);
            
//获取共享对象(OnLineUsers)
            ISharedObject users_so = GetSharedObject(connection.Scope, "OnLineUsers");
            
if (users_so == null)
            {
                
//创建共享对象
                CreateSharedObject(connection.Scope, "OnLineUsers"false);
                users_so 
= GetSharedObject(connection.Scope, "OnLineUsers");
            }
            
//更新共享对象
            users_so.SetAttribute(userName, userName);
            
return true;
        }
        
/// <summary>
        
/// 加入房间
        
/// </summary>
        
/// <param name="client"></param>
        
/// <param name="room"></param>
        
/// <returns></returns>
        public override bool RoomJoin(IClient client, IScope room)
        {
            Trace.WriteLine(
"加入房间 " + room.Name);
            
return true;
        }
        
/// <summary>
        
/// 离开房间
        
/// </summary>
        
/// <param name="client"></param>
        
/// <param name="room"></param>
        public override void RoomLeave(IClient client, IScope room)
        {
            Trace.WriteLine(
"离开房间 " + room.Name);
            
base.RoomLeave(client, room);
        }
        
/// <summary>
        
/// 用户退出
        
/// </summary>
        
/// <param name="connection"></param>
        public override void AppDisconnect(IConnection connection)
        {
            
string userName = connection.Client.GetAttribute("userName"as string;
            ISharedObject users_so 
= GetSharedObject(connection.Scope, "OnLineUsers");
            
if (users_so != null)
            {
                
//从共享对象中移除当前退出系统用户
                users_so.RemoveAttribute(userName);
            }
            
base.AppDisconnect(connection);
        }
    }
}

 

      开发好了ApplicationAdapter,还需要对此ApplicationAdapter进行通信配置,在FluorineFx的应用程序目录中添加app.config并进行如下配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    
<application-handler type="Fx.Adapter.MyApp"/>
</configuration>

 

      另外还需要配置一个客户端方法的通信通道,通过FluorineFx网站下的WEB-INF/flex/service-config.xml配置:

<?xml version="1.0" encoding="utf-8" ?>
<services-config>

      <channels>

          <channel-definition id="my-rtmp" class="mx.messaging.channels.RTMPChannel">
               
<endpoint uri="rtmp://{server.name}:1617" class="flex.messaging.endpoints.RTMPEndpoint"/>
          </channel-definition>

      </channels>

</services-config>

 

      如上便完成了服务器端的开发,在flash/felx客户端通过NetConnection去连接应用,并根据当前的连接去连接服务器端的远程共享对象,最后通过异步事件来实现数据同步更新。如下程序运行截图:

             

 

      此时开多个浏览器窗口测试,不同窗口使用不同的用户名登录,可以很清楚的看到,我们已经实现了在线用户的数据同步功能,可以及时的反映用户上线离线,可以及时的同步在线用户列表的数据。

 

      另外远程共享对象还有一个功能非常强大的特性方法,就是连接到共享对象的客户端之间可以直接广播消息(客户端调用客户端的方法)。就以 上面在线用户的案例为例,用户成功登陆服务器我需要广播一条消息,用户退出了我也需要广播一条消息,要实现这个功能就需要通过远程共享的客户端呼叫 (send()方法)来实现,如下代码块:

private function onCallClient(message:String):void
{
    so.send(
"onSayMessage",message);
}

      

      远程共享对象的send()方法调用了onSayMessage这个客户端方法来实现对连接到共享对象上的所有客户端广播消息,那么我们的在定义一个onSayMessage方法,如下:

/**
 * 接受客户端呼叫—此方法必须是public修饰
 
*/
public function onSayMessage(message:Object):void
{
    traceWriteln(message.toString());
}

private function traceWriteln(param:String):void
{
   txtTraceArea.htmlText += param + "\n";
   txtTraceArea.validateNow();
   txtTraceArea.verticalScrollPosition = txtTraceArea.maxVerticalScrollPosition;
}

 

            

      如果想实现用户退出广播,可以通过服务器端RPC的方法调用客户端的方法来实现,关于RPC请查看《Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Remote Procedure Call)(二) 》有详细介绍。下面是Flex客户端的完整代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"  
    width
="530" height="378" backgroundGradientAlphas="[1.0, 1.0]" 
    backgroundGradientColors
="[#000000, #686868]" fontSize="12">
    
<mx:Script>
        
<![CDATA[
            import mx.controls.Alert;
            import dotnet.fluorinefx.VO.UserInfo;
            
            
private var nc:NetConnection;
            
private var so:SharedObject;
            
private var info:UserInfo;
            
            
private function connectionServer(event:MouseEvent):void
            {
                info 
= new UserInfo();
                info.UserName 
= this.txtUserName.text;
                info.Password 
= this.txtPassword.text;
                
                nc 
= new NetConnection();
                nc.connect(
"rtmp://localhost:1617/SOAPP",info.UserName,info.Password);
                nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
                nc.client 
= this;
                
                
this.txtUserName.text="";
                
this.txtPassword.text="";
                
this.txtUserName.setFocus();
            }
            
            
private function onStatusHandler(event:NetStatusEvent):void
            {
                
this.connStatus.text = "连接状态:" + event.info.code;
                
if(event.info.code == "NetConnection.Connect.Success")
                {
                    
//连接远程共享对象
                    so = SharedObject.getRemote("OnLineUsers",nc.uri,false);
                    
if(so)
                    {
                        so.addEventListener(SyncEvent.SYNC,onSyncHandler);
                        so.connect(nc);
                        so.client 
= this;
                    }
                    onCallClient(
"用户【 <font color=\"#4100b9\">"+info.UserName+"</font>】登陆了系统!");
                }
            }
            
            
private function onSyncHandler(event:SyncEvent):void
            {
                var temp:Array 
= new Array();
                
for(var u:String in so.data)
                {
                    
//traceWriteln("异步事件->共享对象:" + u + ":" + so.data[u]);
                    temp.push(so.data[u]);
                }
                
this.userList.dataProvider = temp;
            }
            
            
private function traceWriteln(param:String):void
            {
                txtTraceArea.htmlText 
+= param + "\n";
                txtTraceArea.validateNow();
                txtTraceArea.verticalScrollPosition 
= txtTraceArea.maxVerticalScrollPosition;
            }
            
            
private function onCallClient(message:String):void
            {
                so.send(
"onSayMessage",message);
            }
            
/**
             * 接受客户端呼叫
             
*/
            
public function onSayMessage(message:Object):void
            {
                traceWriteln(message.toString());
            }
        ]]
>
    
</mx:Script>
    
<mx:Label x="24" y="134" id="connStatus" width="288" color="#FFFFFF"/>
    
<mx:List x="342" y="10" height="347" width="160" id="userList" >
    
</mx:List>
    
<mx:Form x="24" y="10" width="236">
        
<mx:FormItem label="用户名:" color="#FFFFFF">
            
<mx:TextInput id="txtUserName" width="130" color="#000000"/>
        
</mx:FormItem>
        
<mx:FormItem label="密  码:" color="#FFFFFF">
            
<mx:TextInput id="txtPassword" width="130" 
                color
="#000000" displayAsPassword="true"/>
        
</mx:FormItem>
        
<mx:FormItem label="">
            
<mx:Button label="登陆服务器" click="connectionServer(event)" 
                enabled
="{this.txtUserName.text.length&gt;0?true:false}" color="#FFFFFF"/>
        
</mx:FormItem>
    
</mx:Form>
    
<mx:TextArea x="24" y="174" width="288" height="153" alpha="1.0" 
        backgroundColor
="#F2D2D2" backgroundAlpha="0.26" color="#FFFFFF" 
        id
="txtTraceArea" borderColor="#FFFFFF"/>
</mx:Application>

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[手机开发]Windows Mobile 6.5 开发者工具包

mikel阅读(776)

    在一段时间的等待之后,Microsoft终于在其下载中心开放了《Windows Mobile 6.5 开发者工具包》的下载。这对于广大Windows Mobile爱好者来说,无疑是一个好消息,就当是一个迟到的儿童节礼物吧。

      Windows Mobile 6.5 Developer Tool Kit包括的组件:

说明文档

示例代码

头文件和库文件

模拟器镜像

Visual Studio中创建Windows Mobile 6.5应用的工具

      Windows Mobile 6.5 Developer Tool Kit包含Professional版本和Standard版本。目前,模拟器镜像的语言有6种,包括CHS(简体中文)、USA(英语)、GER(德语)、FRA(法语)、ITA(意大利语)和ESN(西班牙语)。Windows Mobile 6.5 Developer Tool Kit包括的模拟器镜像种类(主要是平台和分辨率的区别,和安装的Professional版本和Standard版本相关):

Windows Mobile 6.5 Professional Square Emulator

Windows Mobile 6.5 Professional QVGA Emulator

Windows Mobile 6.5 Professional WQVGA Emulator

Windows Mobile 6.5 Professional VGA Emulator

Windows Mobile 6.5 Professional WVGA Emulator

Windows Mobile 6.5 Standard Square Emulator

Windows Mobile 6.5 Standard QVGA Emulator

    对于广大开发人员来说,最具吸引力的应该就是其中的Windows Mobile 6.5 Gesture API,这可以在开发包中找到相关的例程,在”%"Windows Mobile 6.5 Developer Tool Kit"Samples" folder”目录下。当然,这个Gesture API也只是支持Windows Mobile Classic Professional平台,因为大家知道,Classic是不支持触摸的。

    我下载了一个中文的Windows Mobile 6.5 Developer Professional Tool Kit,尝试使用了一下Windows Mobile 6.5 Professional Emulator,截了一些图片上来。

     第一次启动模拟器的时候,占用的资源还不小,如下图1所示:

1:启动模拟器占用的资源

    开始菜单,的确和我现在在使用的6.0有比较大的区别:

2:开始菜单

      MyphoneMarketplace应用:

3:MyphoneMarketplace应用

    Home界面

4:Home界面

    设备信息:

5:设备信息界面

相关链接:

下载地址:Windows Mobile 6.5 开发者工具包

Windows Mobile Team Blog

Windows Mobile Developer Center

What's New for Developers in Windows Mobile 6

[Lucene]多个TermQuery或一个MultiFieldQueryParser多条件查询

mikel阅读(1496)

 import java.io.IOException;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;

/**
 * 与或非布尔查询——Lucene中的BooleanQuery
 * @author USER
 *
 */
public class TestBooleanQuery {
   /**
    * 主函数,运行测试程序
    * @param args
    * @throws Exception
    */
 public static void main(String[] args) throws Exception {   
     //建索引
  createIndex();   
      
  //多个TermQuery构建BooleanQuery检索
      searchIndex4TermQuery();
    
     //一个MultiFieldQueryParser构建BooleanQuery多个域的检索
     searchIndex4MultiFieldQueryParser();
   }   
    
 /**
  * 建索引
  * @throws Exception
  */
   public static void createIndex() throws Exception {   
     Document doc1 = new Document();   
     Field field = null;   
     field = new Field("name", "word1 word2 word3", Field.Store.YES,   
         Field.Index.TOKENIZED);   
     doc1.add(field);   
     field = new Field("title", "doc1", Field.Store.YES, Field.Index.TOKENIZED);   
     doc1.add(field);   
   
     Document doc2 = new Document();   
     field = new Field("name", "word4 word5", Field.Store.YES,   
         Field.Index.TOKENIZED);   
     doc2.add(field);   
     field = new Field("title", "doc2", Field.Store.YES, Field.Index.TOKENIZED);   
     doc2.add(field);   
   
     Document doc3 = new Document();   
     field = new Field("name", "word1 word2 word6", Field.Store.YES,   
         Field.Index.TOKENIZED);   
     doc3.add(field);   
     field = new Field("title", "doc3", Field.Store.YES, Field.Index.TOKENIZED);   
     doc3.add(field);   
   
     /**
      * 为测试MultiFieldQueryParser而添加的文档
      */
     Document doc4 = new Document();   
     field = new Field("name", "word1 word2 word3", Field.Store.YES,   
          Field.Index.TOKENIZED);   
     doc4.add(field);   
     field = new Field("title", "doc1 word1", Field.Store.YES, Field.Index.TOKENIZED);   
     doc4.add(field);
    
     /**
      * 对MultiFieldQueryParser更深理解
      */
     Document doc5 = new Document();   
     field = new Field("title", "北京2008年奥运会", Field.Store.YES,   
          Field.Index.TOKENIZED);   
     doc5.add(field);   
     field = new Field("name", "这是一届创造奇迹、超越梦想的…….", Field.Store.YES, Field.Index.TOKENIZED);   
     doc5.add(field);
    
     Document doc6 = new Document();   
     field = new Field("title", "北京2008年奥运会", Field.Store.YES,   
          Field.Index.TOKENIZED);   
     doc6.add(field);   
     field = new Field("name", "这是一届创造奇迹、超越梦想的奥运会…….", Field.Store.YES, Field.Index.TOKENIZED);   
     doc6.add(field);
    
     IndexWriter writer = new IndexWriter("e:\\java\\index",   
         new StandardAnalyzer(), true);   
     writer.addDocument(doc1);   
     writer.addDocument(doc2);   
     writer.addDocument(doc3);
    
     writer.addDocument(doc4);
    
     writer.addDocument(doc5);
     writer.addDocument(doc6);
    
     writer.close();   
   } 
  
   /**
    * 由TermQuery和BooleanQuery构建的多个域检索
    * @throws Exception
    */
   public static void searchIndex4TermQuery() throws Exception{
    TermQuery query1 = null;   
      TermQuery query2 = null;   
      TermQuery query3 = null;   
      TermQuery query4 = null;   
      TermQuery query5 = null; 
      TermQuery query6 = null; 
      BooleanQuery bquerymain = null;   
      BooleanQuery bquery1 = null;   
      BooleanQuery bquery2 = null;   
      BooleanQuery bquery3 = null;   
      Hits hits = null;   
    
      IndexSearcher searcher = new IndexSearcher("e:\\java\\index");   
    
      query1 = new TermQuery(new Term("name", "word1"));   
      query2 = new TermQuery(new Term("name", "word2"));   
      
      query3 = new TermQuery(new Term("name", "word3"));
     
      query4 = new TermQuery(new Term("name", "word4"));   
      query5 = new TermQuery(new Term("name", "word5"));   
         
      query6 = new TermQuery(new Term("name", "word6"));   
         
         
    
      // 构造布尔查询(可根据你的要求随意组合)   
      bquerymain = new BooleanQuery();   
      bquery1 = new BooleanQuery();   
      bquery2 = new BooleanQuery();   
      bquery3 = new BooleanQuery();   
    
      bquery1.add(query1, BooleanClause.Occur.MUST);   
      bquery1.add(query3, BooleanClause.Occur.MUST);   
         
      bquery2.add(query3, BooleanClause.Occur.MUST);   
      bquery2.add(query4, BooleanClause.Occur.MUST);   
         
      bquery3.add(query5, BooleanClause.Occur.MUST);
      bquery3.add(query6, BooleanClause.Occur.MUST_NOT);
         
      bquerymain.add(bquery1, BooleanClause.Occur.SHOULD);   
      bquerymain.add(bquery2, BooleanClause.Occur.SHOULD);   
      bquerymain.add(bquery3, BooleanClause.Occur.MUST); 
     
      /**
       * 根据你的要求建一个BooleanQuery对象,然后来查询
       */
      hits = searcher.search(bquery3);   
      printResult(hits, bquery1.toString());   
    
   }
   
   /**
    * 由MultiFieldQueryParser和BooleanQuery构建的多个域检索
    * @throws Exception
    */
   public static void searchIndex4MultiFieldQueryParser() throws Exception{
      Hits hits = null;   
    
      IndexSearcher searcher = new IndexSearcher("e:\\java\\index");   
    
      // 构造布尔查询(可根据你的要求随意组合) 
      BooleanClause.Occur[] flags = new BooleanClause.Occur[] {
     BooleanClause.Occur.MUST, BooleanClause.Occur.MUST};
    
      Query query = MultiFieldQueryParser.parse("word1", new String[] {
     "name", "title"}, flags, new StandardAnalyzer());
    
    /*  //加深对MultiFieldQueryParser的理解(注意看建索引的文档doc5,doc6与检索后的结果)
      Query query = MultiFieldQueryParser.parse("北京 奥运会", new String[] {
     "name", "title"}, flags, new StandardAnalyzer());    */
   
      hits = searcher.search(query);   
      printResult(hits, query.toString());   
    
   }
  
   /**
    * 打印输出检索出的文档,并输出检索的布尔语句
    * @param hits
    * @param key
    * @throws Exception
    */
   public static void printResult(Hits hits, String key) throws Exception {   
     System.out.println("查询 " + key);   
     if (hits != null) {   
       if (hits.length() == 0) {   
         System.out.println("没有找到任何结果");   
       } else {   
         System.out.println("找到" + hits.length() + "个结果");   
         for (int i = 0; i < hits.length(); i++) {   
           Document d = hits.doc(i);   
           String dname = d.get("title");   
           System.out.print(dname + "   ");   
         }   
         System.out.println();   
         System.out.println();   
       }   
     }   
   }   
}   

[Lucene]Lucene 学习笔记(二)——搜索方式(一)

mikel阅读(749)

Lucene有多种搜索方式,可以根据需要选择不同的方式。

1、词条搜索(单个关键字查找)

     主要对象是TermQuery

     调用方式如下:


Term term=new Term(字段名,搜索关键字);
Query query
=new TermQuery(term);
Hits hits
=searcher.search(query);

 

2、组合搜索(允许多个关键字组合搜索)

     主要对象是BooleanQuery

     调用方式如下:

 


Term term1=new Term(字段名,搜索关键字);
TermQuery query1
=new TermQuery(term1);
Term term2
=new Term(字段名,搜索关键字);
TermQuery query2
=new TermQuery(term2);
BooleanQuery booleanquery
=new BooleanQuery();
booleanquery.add(query1,参数)
booleanquery.add(query2,参数)
Hits hits
=searcher.search(booleanquery);

此方法中的核心在BooleanQuery的add方法上,其第二个参数有三个可选值,对应着逻辑上的与或非关系

参数如下:

BooleanClause.Occur.MUST                必须包含,类似于逻辑运算的与

BooleanClause.Occur.MUST _NOT       必须不包含,类似于逻辑运算的非

BooleanClause.Occur.SHOULD           可以包含,类似于逻辑运算的或

这三者组合,妙用无穷。

 

3、范围搜索(允许搜索指定范围内的关键字结果)

     主要对象是RangeQuery

     调用方式如下: 


Term term1=new Term(字段名,起始值);
Term term2
=new Term(字段名,终止值);
RangeQuery rangequery
=new RangeQuery(term1,term2,参数);
Hits hits
=searcher.search(rangequery);

此方法中的参数是Boolean类型的,表示是否包含边界 。

true 包含边界

false不包含边界

 

4、前缀搜索(搜索起始位置符合要求的结果)

     主要对象是PrefixQuery

     调用方式如下:


Term term=new Term(字段名,搜索关键字);
PrefixQuery prefixquery
=new PrefixQuery (term);
Hits hits
=searcher.search(prefixquery);

 

5、短语搜索(根据零碎的短语组合成新的词组进行搜索)

     主要对象是PhraseQuery

     调用方式如下:


Term term1=new Term(字段名,搜索关键字);
Term term2
=new Term(字段名,搜索关键字);
PhraseQuery phrasequery
=new PhraseQuery();
phrasequery.setSlop(参数);
phrasequery.add(term1);
phrasequery.add(term2);
Hits hits
=searcher.search(phrasequery);

其中setSlop的参数是设置两个关键字之间允许间隔的最大值。

[ASP.NET]Web开发学习心得7——MasterPage的实现原理

mikel阅读(872)

      MasterPage是ASP.NET2.0引入的一个非常实用的特性,怎么用,我想不用我说,基本上大家都会,这里要讲的是,它是如何实现的。

      在深入源代码去探索MasterPage之前,我以为MasterPage的实现应该是比较复杂的,也一直纳闷为什么 MasterPage类会继承于UserControl类,感觉这两者好像差得很远。昨天晚上,我专门抽出时间,阅读了部分与MasterPage有关的 源代码,终于明白了是怎么回事,在那突然明白的那一刻,真有如醍醐灌顶,拍案叫绝,不得不佩服微软的那些guys。

      下面就是我的探索之旅的过程(大家也可以跳过该部分,直接看后面的真相大白部分):

      1、我首先查看的是Page.ProcessRequestMain方法,我们知道,Page类大部分特性,包括LifeCycle、 PostBack、ProcessPostData等都是在该方法中实现,所以,我想,MasterPage的实现肯定在该方法中有不少的体现。然而,令 我惊讶的是,我居然没有在该方法中找到任何有关MasterPage的线索,而仅仅在this.PerformPreInit()中,找到唯一一个 ApplyMasterPage()方法。而该方法也出奇的简单,感觉仅仅是将递归的各级MasterPage的._masterPageApplied 字段设为true而已。当时,我忽略了一个重要的东西,就是代码中对this.Master这个属性的访问,实际上,奥秘就在对这个属性的访问上(下文将 叙述)。

private void PerformPreInit()
{
    
this.OnPreInit(EventArgs.Empty);
    
this.InitializeThemes();
    
this.ApplyMasterPage();
    
this._preInitWorkComplete = true;
}

 


private void ApplyMasterPage()
{
    
if (this.Master != null)
    
{
        ArrayList appliedMasterFilePaths 
= new ArrayList();
        appliedMasterFilePaths.Add(
this._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture));
        MasterPage.ApplyMasterRecursive(
this.Master, appliedMasterFilePaths);
    }

}

 

      2、我查看了MasterPage的源代码,出奇的是,竟也如此简单,以至于我也没有从该源代码中找到多少有价值的信息。  


[ControlBuilder(typeof(MasterPageControlBuilder)), Designer("Microsoft.VisualStudio.Web.WebForms.MasterPageWebFormDesigner, Microsoft.VisualStudio.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"typeof(IRootDesigner)), ParseChildren(false), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
public class MasterPage : UserControl
{
    
private IList _contentPlaceHolders;
    
private IDictionary _contentTemplateCollection;
    
private IDictionary _contentTemplates;
    
private MasterPage _master;
    
private bool _masterPageApplied;
    
private VirtualPath _masterPageFile;
    
internal TemplateControl _ownerControl;

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal void AddContentTemplate(string templateName, ITemplate template)
    
{
        
if (this._contentTemplateCollection == null)
        
{
            
this._contentTemplateCollection = new Hashtable(10, StringComparer.OrdinalIgnoreCase);
        }

        
try
        
{
            
this._contentTemplateCollection.Add(templateName, template);
        }

        
catch (ArgumentException)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Multiple_content"new object[] { templateName }));
        }

    }


    
internal static void ApplyMasterRecursive(MasterPage master, IList appliedMasterFilePaths)
    
{
        
if (master.Master != null)
        
{
            
string str = master._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture);
            
if (appliedMasterFilePaths.Contains(str))
            
{
                
throw new InvalidOperationException(SR.GetString("MasterPage_Circular_Master_Not_Allowed"new object[] { master._masterPageFile }));
            }

            appliedMasterFilePaths.Add(str);
            ApplyMasterRecursive(master.Master, appliedMasterFilePaths);
        }

        master._masterPageApplied 
= true;
    }


    
internal static MasterPage CreateMaster(TemplateControl owner, HttpContext context, VirtualPath masterPageFile, IDictionary contentTemplateCollection)
    
{
        MasterPage child 
= null;
        
if (masterPageFile == null)
        
{
            
if ((contentTemplateCollection != null&& (contentTemplateCollection.Count > 0))
            
{
                
throw new HttpException(SR.GetString("Content_only_allowed_in_content_page"));
            }

            
return null;
        }

        VirtualPath virtualPath 
= VirtualPathProvider.CombineVirtualPathsInternal(owner.TemplateControlVirtualPath, masterPageFile);
        ITypedWebObjectFactory vPathBuildResult 
= (ITypedWebObjectFactory) BuildManager.GetVPathBuildResult(context, virtualPath);
        
if (!typeof(MasterPage).IsAssignableFrom(vPathBuildResult.InstantiatedType))
        
{
            
throw new HttpException(SR.GetString("Invalid_master_base"new object[] { masterPageFile }));
        }

        child 
= (MasterPage) vPathBuildResult.CreateInstance();
        child.TemplateControlVirtualPath 
= virtualPath;
        
if (owner.HasControls())
        
{
            
foreach (Control control in owner.Controls)
            
{
                LiteralControl control2 
= control as LiteralControl;
                
if ((control2 == null|| (Util.FirstNonWhiteSpaceIndex(control2.Text) >= 0))
                
{
                    
throw new HttpException(SR.GetString("Content_allowed_in_top_level_only"));
                }

            }

            owner.Controls.Clear();
        }

        
if (owner.Controls.IsReadOnly)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Cannot_ApplyTo_ReadOnly_Collection"));
        }

        
if (contentTemplateCollection != null)
        
{
            
foreach (string str in contentTemplateCollection.Keys)
            
{
                
if (!child.ContentPlaceHolders.Contains(str.ToLower(CultureInfo.InvariantCulture)))
                
{
                    
throw new HttpException(SR.GetString("MasterPage_doesnt_have_contentplaceholder"new object[] { str, masterPageFile }));
                }

            }

            child._contentTemplates 
= contentTemplateCollection;
        }

        child._ownerControl 
= owner;
        child.InitializeAsUserControl(owner.Page);
        owner.Controls.Add(child);
        
return child;
    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IList ContentPlaceHolders
    
{
        
get
        
{
            
if (this._contentPlaceHolders == null)
            
{
                
this._contentPlaceHolders = new ArrayList();
            }

            
return this._contentPlaceHolders;
        }

    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IDictionary ContentTemplates
    
{
        
get
        
{
            
return this._contentTemplates;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPage"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
    
public MasterPage Master
    
{
        
get
        
{
            
if ((this._master == null&& !this._masterPageApplied)
            
{
                
this._master = CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
            }

            
return this._master;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPageFile"), WebCategory("Behavior"), DefaultValue("")]
    
public string MasterPageFile
    
{
        
get
        
{
            
return VirtualPath.GetVirtualPathString(this._masterPageFile);
        }

        
set
        
{
            
if (this._masterPageApplied)
            
{
                
throw new InvalidOperationException(SR.GetString("PropertySetBeforePageEvent"new object[] "MasterPageFile""Page_PreInit" }));
            }

            
if (value != VirtualPath.GetVirtualPathString(this._masterPageFile))
            
{
                
this._masterPageFile = VirtualPath.CreateAllowNull(value);
                
if ((this._master != null&& this.Controls.Contains(this._master))
                
{
                    
this.Controls.Remove(this._master);
                }

                
this._master = null;
            }

        }

    }

}

      

      3、我又查看了ContentPlaceHolder类、Content类,心想,难道奥秘在这两个控件上?打开源代码一看,彻底晕 倒,这两个类简单得简直不能让人相信,ContentPlaceHolder居然是一个空类,仅仅起到一个标识的作用,而Content居然也仅仅只有 ContentPlaceHolderID唯一一个string属性。

public class ContentPlaceHolder : Control, INonBindingContainer, INamingContainer
{
}

 

Content

      

      4、此时,我几乎已经没法再从ASP.NET源代码中找到其他有关MasterPage的有价值的信息了。于是,我决定写一个简单的 Web应用程序,该应用程序仅仅只有一个MasterPage页与Default页,并将其编译,查看编译后的代码,看看是否能找到有价值的信息。

      源代码如下:


<%@ Master Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    
<title></title>
</head>
<body>
    
<form id="form1" runat="server">
    
<div>
        
<asp:ContentPlaceHolder id="ContentPlaceHolder" runat="server">
            
<asp:Button runat="server" ID="master" Text="master"/>
        
</asp:ContentPlaceHolder>
    
</div>
    
</form>
</body>
</html>

 


<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>

<asp:Content ID="Content" ContentPlaceHolderID="ContentPlaceHolder" Runat="Server">
    
<asp:Button runat="server" ID="default" Text="default" />
</asp:Content>

 

      编译后代码如下:


[CompilerGlobalScope]
public class masterpage_master : MasterPage
{
    
private static bool __initialized;
    
private ITemplate __Template_ContentPlaceHolder;
    
protected ContentPlaceHolder ContentPlaceHolder;
    
protected HtmlForm form1;
    
protected Button master;

    [DebuggerNonUserCode]
    
public masterpage_master()
    
{
        
base.AppRelativeVirtualPath = "~/MasterPage.master";
        
if (!__initialized)
        
{
            __initialized 
= true;
        }

        
base.ContentPlaceHolders.Add("contentplaceholder");
    }


    [DebuggerNonUserCode]
    
private ContentPlaceHolder __BuildControlContentPlaceHolder()
    
{
        ContentPlaceHolder container 
= new ContentPlaceHolder();
        
this.ContentPlaceHolder = container;
        container.ID 
= "ContentPlaceHolder";
        
if (base.ContentTemplates != null)
        
{
            
this.__Template_ContentPlaceHolder = (ITemplate) base.ContentTemplates["ContentPlaceHolder"];
        }

        
if (this.__Template_ContentPlaceHolder != null)
        
{
            
this.__Template_ContentPlaceHolder.InstantiateIn(container);
            
return container;
        }

        IParserAccessor accessor 
= container;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n            "));
        Button button 
= this.__BuildControlmaster();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n        "));
        
return container;
    }


    [DebuggerNonUserCode]
    
private HtmlForm __BuildControlform1()
    
{
        HtmlForm form 
= new HtmlForm();
        
this.form1 = form;
        form.ID 
= "form1";
        IParserAccessor accessor 
= form;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    <div>\r\n        "));
        ContentPlaceHolder holder 
= this.__BuildControlContentPlaceHolder();
        accessor.AddParsedSubObject(holder);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    </div>\r\n    "));
        
return form;
    }


    [DebuggerNonUserCode]
    
private Button __BuildControlmaster()
    
{
        Button button 
= new Button();
        
this.master = button;
        button.ApplyStyleSheetSkin(
this.Page);
        button.ID 
= "master";
        button.Text 
= "master";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(masterpage_master __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n<!DOCTYPE html PUBLIC \"//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <title></title>\r\n</head>\r\n<body>\r\n    "));
        HtmlForm form = this.__BuildControlform1();
        accessor.AddParsedSubObject(form);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n</body>\r\n</html>\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }


    [TemplateContainer(
typeof(MasterPage)), TemplateInstance(TemplateInstance.Single)]
    
public virtual ITemplate Template_ContentPlaceHolder
    
{
        
get
        
{
            
return this.__Template_ContentPlaceHolder;
        }

        
set
        
{
            
this.__Template_ContentPlaceHolder = value;
        }

    }

}

 


[CompilerGlobalScope]
public class default_aspx : Page, IRequiresSessionState, IHttpHandler
{
    
private static object __fileDependencies;
    
private static bool __initialized;
    
protected Button @default;

    [DebuggerNonUserCode]
    
public default_aspx()
    
{
        
base.AppRelativeVirtualPath = "~/Default.aspx";
        
if (!__initialized)
        
{
            
string[] virtualFileDependencies = new string[] "~/Default.aspx""~/MasterPage.master" };
            __fileDependencies 
= base.GetWrappedFileDependencies(virtualFileDependencies);
            __initialized 
= true;
        }

        
base.Server.ScriptTimeout = 0x1c9c380;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlContent(Control __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    "));
        Button button 
= this.__BuildControldefault();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n"));
    }


    [DebuggerNonUserCode]
    
private Button __BuildControldefault()
    
{
        Button button 
= new Button();
        
this.@default = button;
        button.TemplateControl 
= this;
        button.ApplyStyleSheetSkin(
this);
        button.ID 
= "default";
        button.Text 
= "default";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(default_aspx __ctrl)
    
{
        __ctrl.Title 
= "";
        __ctrl.MasterPageFile 
= "~/MasterPage.master";
        
this.InitializeCulture();
        
base.AddContentTemplate("ContentPlaceHolder"new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
        
base.AddWrappedFileDependencies(__fileDependencies);
        
base.Request.ValidateInput();
    }


    [DebuggerNonUserCode]
    
public override int GetTypeHashCode()
    
{
        
return 2002306427;
    }


    [DebuggerNonUserCode]
    
public override void ProcessRequest(HttpContext context)
    
{
        
base.ProcessRequest(context);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
public masterpage_master Master
    
{
        
get
        
{
            
return (masterpage_master) base.Master;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }

}

      

      我们首先观察default_aspx的BuildControlTree方法,该方法调用了 base.AddContentTemplate("ContentPlaceHolder", new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));而该方法实质上只有一行代码,即 this._contentTemplateCollection.Add(templateName, template);其中,_contentTemplateCollection是IDictionary类型。因 此,base.AddContentTemplate只有一个功能,即在Page的_contentTemplateCollection中添加一个 CompliedTemplateBuilder类型的对象。那么该对象有啥作用呢?为了不岔开话题,这里不对该对象做详细描述,只给出结论:该对象实现 ITemplate接口,其接口方法InstantiateIn(Control container)具体实现为为container添加BuildTemplateMethod委托所创建的控件。这话有点拗口,简单地就刚才那个例子 来说,就是如果你调用该对象的InstantiateIn(Control container)方法,就为该container添加this.__BuildControlContent()方法所创建的控件做为子控件。

 

      5、我们继续来看看masterpage_master的__BuildControlContentPlaceHolder()方 法,我们发现,该方法即调用了刚才讨论的那个InstantiateIn方法。哈,原来在这里,终于明白了,原来Page里Content控件中的所有内 容最终都将变成其对应的MasterPage中的ContentPlaceHolder的子控件。等一下,这个结论下得有点早,难道这里的 base.ContentTemplates属性就等于Page的_contentTemplateCollection字段吗?如果是,那么上面的结论 就是正确的。

 

      6、我们重新回到1中的代码,查看Page.Master属性的实现,我们发现,它调用了MasterPage的CreateMaster静态方法,而该方法传送的其中一个参数就是._contentTemplateCollection字段。


public MasterPage Master
{
    
get
    
{
        
if ((this._master == null&& !this._preInitWorkComplete)
        
{
            
this._master = MasterPage.CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
        }

        
return this._master;
    }

}

 

      我们再回头来看看MasterPage.CreateMaster方法,在倒数第6行,我们发现,该方法果然将 contentTemplateCollection赋给了MasterPage实例(child)的_contentTemplates字段。再往下 看,我们还看到了owner.Controls.Add(child),什么意思呢?意思就是将该MasterPage实例作为普通控件加入到owner 实例的子控件集合中。而往上,我们又可以找到owner.Controls.Clear()语句,因此,该MasterPage将作为owner的唯一子 控件而存在。而该owner,常常就是Page实例。(在存在嵌套MasterPage的时候,该owner还可能是下一层次的MasterPage实 例)。

      至此,真相大白,原来,MasterPage最终将作为Page的唯一子控件而存在,难怪它要继承自 UserControl,而Page中Content控件定义的各个子控件,又将作为该MasterPage的ContentPlaceHolder的子控件而存在,难 怪ContentPlaceHolder无需实现任何代码,因为它仅仅是一个容器。正因为MasterPage最终成为Page的唯一子控件,那么后来的 处理就与普通的控件没什么两样了,难怪ProcessRequestMain方法里无需为MasterPage单独编码,哈哈,一切都真相大白了。这里, 我们还发现一个比较有趣的现象,即Content控件本身却消失不见了,这应该是ASP.NET解析器所做的优化,因为ContentPlaceHolder完全没必要先装上Content控件,然后再装上Content中的那些控件。

      另外,从Page.Master属性与Page.MasterPageFile属性的实现上,我们也不难明白为什么MasterPageFile属性只能在 PreInit 事件中进行设置的原因。 

      如何证明以上所说都是正确的呢?呵呵,其实很简单,我们可以观察最终页面的控件树,就可证明上面分析是正确的。(写这篇blog时才想起看控件树,要是早想起,就能让我少走不少歪路了,唉,幸好打开控件树发现结果与预期完全一致。)

ControlTree

      

      那么嵌套MasterPage是如何实现的呢?呵呵,其实也一样,即Top1MasterPage成为Top2MasterPage的 唯一子控件,Top2MasterPage成为Top3MasterPage的唯一子控件,……,直到TopNMasterPage成为Page的唯一子 控件。

      

      最后我用两幅图来做总结。

      下图为初始的控件树结构:

 

      下图为最终的控件树结构:

 

[优化]Memcahed分布式缓存服务替换Session解决方案

mikel阅读(886)

需求:

       有个ASP.NET网站系统,有一级域名,二级域名,三级域名,系统的各个功能模块分布在不同的域名,同一域名的也有可能分布在不同省份的服务器或者同一同一省份的不同的服务器中,同一省份的的服务器可以通过内部局域网访问。在系统中,现在需要所有功能模块共享用户会话信私有数据。

常用的方式是启用Session的数据库持久会模式可以达到上面的需求(没尝试过),现在需要使用Memcached分布式缓存服务来达到保存用户的会话数据,而达到各个功能模块都能够跨省份、跨服务器共享本次会话中的私有数据的目的。

解决方案:       

     每个省份使用一台服务器来做为Memcached服务器来存储用话的会话中的数据,当然也可以多台服务器,但必须确保每个省份的做Memcached服务器数量必须一致,这样才能够保证Memcached客户端操作的是同一份数据,保证数据的一致性。

会话数据的添加、删除、修改:

Memcached客 户端,添加、删除和、修改会话信息数据时,不仅要添加、删除、修改本省的Memcached服务器数据,而且同时要对其它省份的Memcahed服务器做 同样的操作,这样用户访问其它省份的服务器的功能模块进也能读取到相同的会话数据。Memcached客户端服务器的列表使用局域网的内网 IP(如:192.168.1.179)操作本省的Memcahed服务器,使用公网的IP((如:202.183.62.210))操作其它省份的 Memcahe服务器。

会话数据的读取

系统所有模块读取会话数据的Memcached客户端服务器列表都设为本省Memcached服务器地址的内网IP来向Memcahed服务器中读取会话数据。

 

    

  如 上图所示,A省有四台服务器,B省也有四台服务器,两个三份都有三台Web服务器、一台Memcached服务器,且A省四台服务器同在一个局域网内的可 能通过内网IP相互访问,B省也一样。假如:A省的A1,B1,C1这三台Web服务器其中的一台要添加或修改、或删除会话数据,它首先调服 Memcached客户端使服D1服务器的内网IP向D1服务器中添加或修改、或删除会话数据,操作完成后,还用调用Memcache客户端使服D2服务 器的公网IP向D2服务器做同样的操作,这样才算完整的操作过程,也可以当做一个事务来处理。假如:A省的A1,B1,C1这三台Web服务器其中一台服 务器想要读取会话的数据,只需要调用Memcached客户端使服D1服务器的内网IP读取数据即可,如果发现该数据不存在,即做向Memcached服 务器添加数据的相关业务处理。B省处理逻辑同A省。 

 同一会话的确认:

        使用Cookie来保持客户与服务端的联系。 每一次会话开始就生成一个GUID作为SessionID,保存在客户端的Cookie中,作用域是顶级域名,这样二级、三级域名就可以共享到这个Cookie,系统中就使用这个SessionID来确认它是否是同一个会话。

        

会话数据的唯一ID

    会话数据存储在Memcached服务器上的唯一键Key也就是会话数据数据的唯一ID定义为:SessionID_Name, SessionID就是保存在客户端Cookie中的SessionID, Name就是会话数据的名称,同一次会话中各个会话数据的Name必须是唯一的,否则新的会话数据将覆盖旧的会话数据。

会话的失效时间:

       会话的失效通过控制Cookie的有效时间来实现,会话的时间设为SessionIDCookie中的有效时间,且每一次访问SessionID时都要重新设置一下Cookie的有效时间,这样就达到的会话的有效时间就是两次间访问CookieSessionID值的的最长时间,如果两次访问的间隔时间超过用效时间,那么保存在SessionIDCookie将会失效,并生成新的SessionID存放在Cookie, SessionID改变啦,会话就结束啦。

Memcached服务器中会话数据的失效

      每 一次向Memcache服务器中添加会话数据时,都把有效时间设为一天也就是24小时,让Memcached服务使用它内部的机制去清除,不必在程序中特 别做会话数据的删除操作。数据在Memcache服务器中有有效时间只是逻辑上的,就算是过了24 小时,如果分配给Memcached服务的内存还够用的话,数据还是保存在内存当中的,只是Memcache客户端读取不到而已。只有到了分配给 Memcached服务的内存不够用时,它才会清理没用或者比较旧的数据,也就是懒性清除。

[Flex]FluorineFx.NET与.Net互操作系列教程

mikel阅读(775)


Flex与.NET互操作系列文章索引
Flex与.NET互操作(一):基于Socket的网络连接 
Flex与.NET互操作(二):基于WebService的数据访问(上) 
Flex与.NET互操作(三):基于WebService的数据访问(下)
Flex与.NET互操作(四):使用HttpService、URLReqeust和URLLoader加载/传输数据
Flex与.NET互操作(五):使用FileReference+HttpHandler实现文件上传/下载
Flex与.NET互操作(六):Flex和.NET协同开发利器FluorineFx
Flex与.NET互操作(七):了解FluorineFx的环境配置(远程对象、网关、通道、目的地)
Flex与.NET互操作(八):使用FluorineFx网关实现远程访问
Flex与.NET互操作(九):FluorineFx.NET的认证(Authentication )与授权(Authorization)
Flex与.NET互操作(十):基于FluorineFx.Net的及时通信应用(ApplicationAdapter)(一) 
Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Remote Procedure Call)(二)





[Flex]Flex与.NET互操作(十):基于FluorineFx.Net的及时通信应用(Appl

mikel阅读(682)

   使用FluorineFx.Net开发的每一个实时通讯功能应用都拥有一个应用程序适配器(ApplicationAdapter),用来管 理整个实时通讯应用的生命周期,以及接受和拒绝客户端的连接等。应用程序适配器对象也就相当于是一个Flash媒体服务器应用程序的对象。

      使用FluorineFx.Net开发及时通信应用,我们可以通过ASP.NET网站来宿主,一个ASP.NET网站可以承载多个实时通讯应用程序。这些应用程序都存储ASP.NET网站的根目录下指定文件夹的根目录中。如下图示:

                               

      在开发FluorineFx的及时通信应用的时候,按照FluorineFx的目录结构配置ASP.NET的站点目录是非常简单的,只 需要在ASP.NET站点下面建立一个名为apps的目录,那么FluorineFx就会认为此目录下的每一个文件夹就表示一个FluorineFx应 用。

      我们可以通过FluorineFx来开发及时文字聊天、视频聊天、视频录制、媒体播放程序以及WebGame等类似及时应用程序。比如 我们需要开发一个视频聊天系统,OK,那便在ASP.NET站点目录下建立一个apps目录,然后在apps下建立一个名为VideoChat的目录,如 下目录结构:

                              

       在Flash或是Flex客户端,我们就可以使用下面的代码来连到这个基于FluorineFx的视频聊天应用。

private var ns:NetConnection;
public function ConnectionFxApp():void
{
    ns 
= new NetConnection();
    ns.connect(
"rtmp://localhost:1974/VideoChat");
    ns.client
=this;
    ns.addEventListener(NetStatusEvent.NET_STATUS,onNetStatusHandler);
}
private function onNetStatusHandler(evt:NetStatusEvent):void
{}

 

      每个应用程序都有其自己的应用范围和层次的根源,这和FMS、Red5是一样的规律。无论是范围还是层次,他都有一个唯一的名字(应用名、实例名等)来区分,我们也可以叫他们为应用程序的实例。这些实例可以是聊天室房间、视频流以及共享对象等。   

                                 

      这些其实是非常容易理解的,如上图示的聊天室房间分配,分别有Room1,Room2,Room3三个聊天房间,那么对于VideoChat这个聊天应用来说要进行视频聊天就有三条连接线路,分别是:

      1、http://localhost:1974/VideoChat/Room1

      2、http://localhost:1974/VideoChat/Room2

      3、http://localhost:1974/VideoChat/Room3

      除了聊天室和应用的层次外,应用程序里还有诸入视频流、共享对象等。他们可以共同应用,也可以为不同的房间分别建立不同的共享对象,如果你要想实现不同房间里的用户可以相互聊天不想让别的房间的人看到,OK,此时你就可以为该房间独立创建一个共享对象来实现。

 

      当开发程序需要去连接客户端,处理服务端事件、调用客户端方法等,就需要自定义一个应用程序适配器 (ApplicationAdapter),此适配器必须继承并实现 FluorineFx.Messaging.Adapter.ApplicationAdapter。 FluorineFx.Messaging.Adapter.ApplicationAdapter做为一个新的应用程序的基类,提供了客户端与应用程序 之间通信的接口方法、数据流以及共享对象等。它能够时时处理应用程序的启动、停止、客户端的连接和断开。

 

      在Flash或是Flex客户端,我们可以通过NetConnectin.call()调用应用程序适配器的方法。如下示例代码块:

服务器端应用适配器所定义的代码:

public class Application : ApplicationAdapter
{
   
public string Echo(string msg)
   {
      
return "Echo: " + msg;
   }
}

Flash/Flex客户端可以通过以下代码块调用服务器端的方法:

private var ns:NetConnection;
private function CallServerMethod():void
{
    nc 
= new NetConnection();
    nc.connect(
"rtmp://rtmp://localhost:1974/application");
    nc.onResult 
= function(obj) 
    {
        trace(
"The result is " + obj);
    }
    nc.call(
"Echo", nc, "Hello");
}

 

     本文就先介绍到这里,下篇将接着本篇介绍FluorineFx.NET中客户端调用服务器端方法和服务器端呼叫客户端的方法。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[Flex]Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Rem

mikel阅读(625)

      FluorineFx.NET提供了完善的RPC(Remote Procedure Call)功能,无论是通过Flash还是Flex开发的客户端应用(.swf)都可以非常简单方便的采用RPC的方式调用.NET的服务器端方 法,.NET的服务器端同样也可以非常方便的呼叫客户端,调用客户端的方法(比如实现系统广播)。
一、客户端的RPC(客户端调用服务器端

       要想实现客户端访问服务器端的方法,首先得对ActionScript中的NetConnection比较熟悉,该类提供了一个示例方法call()专们用来做RPC访问,该方法的定义如下:

public function call(command:String, responder:Responder,  arguments):void 

 

      比如说我们在开发一个及时应用的时候,所有的客户端都需要同步服务器的系统时间,这个时候我们就可以在服务器端提供一个返回当前系统时间的方法给客户端调用。如下:

//返回当前系统时间
public string GetServerTime()
{
     
return DateTime.Now.ToString();
}

      

      有了这个方法客户端就可以通过NetConnection的call()方法实行远程调用了,并通过Responder来处理调用的结果,如果所调用的服务器端方法没有返回值,则在调用的时候可以不使用Responder。如下完整的Flex实例代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" fontSize="12">
    
<mx:Script>
        
<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
        
            
private var nc:NetConnection;
            
private var responder:Responder = new Responder(onResult,onError);
            
            
private function connectionServer(event:MouseEvent):void
            {
                nc 
= new NetConnection();
                nc.connect(
"rtmp://localhost:1617/RPCDemo","abc","123");
                nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
                nc.client 
= this;
            }
            
            
private function onStatusHandler(event:NetStatusEvent):void
            {
                
this.connStatus.text = "连接状态:" + event.info.code;
                
if(event.info.code == "NetConnection.Connect.Success")
                {
                    trace(
"连接服务器成功");
                }
            }
            
            
/* 调用服务器端方法 */
            
private function onGetServerTime(event:MouseEvent):void
            {
                nc.call(
"GetServerTime",responder);
            }
            
            
/* 处理调用服务器端方法成功时返回的结果 */
            
private function onResult(result:String):void
            {
                
this.txtResult.text = "方法GetServerTime返回结果" + result;
            }
            
            
/* 处理调用服务器端方法时候时返回的结果 */
            
private function onError(event:Event):void
            {
                trace(
"调用服务器端方法出错");
            }
        ]]
>
    
</mx:Script>
    
<mx:Button x="30" y="43" label="连接服务器" click="connectionServer(event)"/>
    
<mx:Label x="30" y="75" width="296" id="connStatus"/>
    
<mx:Button x="30" y="112" label="调用服务端方法" click="onGetServerTime(event)"/>
    
<mx:Label x="30" y="153" id="txtResult" width="296"/>
</mx:Application>

 

       FluorineFx.Net的RPC就这么简单,客户端调用服务器端的方法,直接通过当前连接的call()方法调用,使用Responder来处理调用的结果。

二、服务器端的RPC(服务器端调用客户端)

      客户端和服务器端的调用是可以双向的,服务器端也同样可以调用客户端的方法。无论是客户端还是服务器端,相互调用始终都是离不开当前的 连接对象。如上面客户端是通过当前连接的call()方法实现的调用服务器端方法,那么服务器端调用客户端的方法同样如此,也是通过当前连接对象来处理。

      FluorineFx中用于连接的接口是IConnection,只要客户端成功的连接上了服务器端,那么在服务器端就会保存所有连接 到服务器端的连接对象的各种属性。实现调用客户端方法的则是IServiceCapableConnection接 口,IServiceCapableConnection接口的定义如下所示:

      如果要想实现服务器端调用客户端的方法,则必须使用此接口来完成,需要将当前连接的接口IConnection强制转化为

IServiceCapableConnection接口,然后调用其invoke()方法来完成服务器端对客户端方法的调用。

 

/// <summary>
/// 调用客户端的方法
/// </summary>
/// <param name="connection"></param>
public void InvokeClient()
{

    IServiceCapableConnection conn 
= FluorineContext.Current.Connection as IServiceCapableConnection;
    
if (connection != null)
    {
        
object[] args = new object[] { GetServerTime() };
        conn.Invoke(
"RecServerMessage", args);
    }
}

 

      在服务器端可以通过FluorineContext.Current.Connection获取到当前客户端的连接对象,然后将其转化为IServiceCapableConnection接口对象,调用其Invoke()方法便可实现调用客户端的方法。

      这里需要注意一点就是,提供给服务器端调用的客户端方法必须是公共方法(public修饰的方法),否则服务器端会报"Not found method:方法名"的异常。如下代码块:

/**
 * 接收服务器端的调用方法
 * 
*/
public function RecServerMessage(message:Object):void
{
    Alert.show(message.toString());
}

 

      FluorineFx.Net所提供的上面这两种功能是非常实用的,特别是在开发及时应用的时候,客户端RPC可以方便是实现将客户端 是数据请求到服务器端进行处理,比如开发网络游戏中,游戏客户端的不同数据随时都在改变,那么我们就可以使用客户端RPC来实现这一需求。服务器端RPC 同样可以应用于此,游戏中的数据变化了通过客户端RPC请求到服务器端,服务器端进行相与的业务计算和处理之后将客户端需要的数据通过服务器端RPC返回 到当前请求的客户端。

 

      或许有的朋友会想或是问到,如果我要开发一个聊天室,要实现系统广播消息是不是可以使用这种方式来实现。答案是肯定的,系统要广播消 息,可以从服务器端获取到所有连接到服务器端的客户端,然后循环的调用客户端的方法来实现系统广播消息。关于如何实现系统广播这里我就不作介绍,因为 FluorineFx.Net为我们提供了更好的方式来实现系统广播。有兴趣的朋友请关注我的后续文章,在下一篇文章里我将详细介绍这方面的知识点。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[MVC]Asp.net MVC 示例项目"Suteki.Shop"分析之---IOC(控制反转)

mikel阅读(724)

   在Suteki.Shop中,未使用微软自已的Unity框架来实现IOC,而是使用了大名鼎鼎Castle Windsor。
因为引用了Windsor,就有必要简要介绍一下。而我的理解,这个IOC容器(Container)包括下面几个
重要概念:
 

      容器(Container):Windsor是一个反转控制容器。它创建在一个微内核的基础之上,这个微内
核能够扫描类并且试图找到这些类用到哪些对象引用、对象依赖,然后把这些依赖信息提供给类使用

      组件(Component):也就是我们通常所说的业务逻辑单元及相应的功能实现,组件是一个可复
用的代码单元。它应该实现并暴露为一个服务。组件是实现一个服务或接口的类

      服务(Service) :也就是相应的组件接口或N个Component按业务逻辑组合而成的业务逻辑接口。
接口是服务的规范,它创建一个抽象层,你可以轻松的替换服务的实现

      扩张单元插件(Facilities):提供(可扩张)容器以管理组件
      
     我们可以直接使用组件(会在下面的内容中提到),也可以把组件转换成相应的服务接口来使用。
 
     还记得上一篇文章中提到的Service吗? 说白了,它就是一个服务。而Suteki.Shop做的更“夸张”,
只要是带有业务逻辑性质的功能代码都可以被视为Component或服务,比如说前几篇文章中所提到的
Filter,ModelBinder。甚至是服务组件初始化的辅助类(WindsorServiceLocator)也一并拿下。

     为了便于理解,下面就到Suteki.Shop中看一下其是如何做的:)
   
     首先我们看一下整个Suteki.Shop项目启动的入口,同时这也是Windsor IOC容器初始化的起点。
而这块功能代码是放在了Global.asax(Suteki.Shop\Global.asax)中的Application_Start方法中
实现的,下面是该方法的声明:

protected void Application_Start(object sender, EventArgs e)
{
    RouteManager.RegisterRoutes(RouteTable.Routes);
    InitializeWindsor();
}

 

     代码中的RouteManager.RegisterRoutes是实现对Route规则的绑定,而规则的内容是被硬编码到
RouteManager中实现的。关于Route的资料网上有不少,园子里也有不少朋友写过,这里就不做说明了。

     接就上面方法就会运行InitializeWindsor(),这就是Windsor容器初始化的方法:

/// <summary>
/// This web application uses the Castle Project's IoC container, Windsor see:
/// http://www.castleproject.org/container/index.html
/// </summary>
protected virtual void InitializeWindsor()
{
    
if (container == null)
    {
        
// create a new Windsor Container
        container = ContainerBuilder.Build("Configuration\\Windsor.config"); 
        WcfConfiguration.ConfigureContainer(container);
        ServiceLocator.SetLocatorProvider(() 
=> container.Resolve<IServiceLocator>());
        
// set the controller factory to the Windsor controller factory (in MVC Contrib)
        System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));
    }
}

 

      注:“Configuration\\Windsor.config”中的内容较长,主要是一些XML配置节点。大家可以抽时
间阅读一下即可。

     这个方法是今天讲解的主要内容,下面就介绍一下其中的代码。
   
     首先是判断container(IWindsorContainer类型)是否为空,如果容器为空则创建并初始化该容器。
也就是调用ContainerBuilder(Suteki.Shop\ContainerBuilder)类的Build方法来从外部的config文件
中加载默认信息。我们这里就看一下Build方法的实现:

public static IWindsorContainer Build(string configPath)
{
        var container 
= new WindsorContainer(new XmlInterpreter(configPath));
        
// register handler selectors
        container.Kernel.AddHandlerSelector(new UrlBasedComponentSelector(
            
typeof(IBaseControllerService),
            
typeof(IImageFileService),
            
typeof(IConnectionStringProvider)
            ));
        
// automatically register controllers
        container.Register(AllTypes
            .Of
<Controller>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Configure(c 
=> c.LifeStyle.Transient.Named(c.Implementation.Name.ToLower())));
        container.Register(
            Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSQLUnitOfWorkManager>().LifeStyle.Transient,
            Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
            Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
            Component.For
<AuthenticateFilter>().LifeStyle.Transient,
            Component.For
<UnitOfWorkFilter>().LifeStyle.Transient,
            Component.For
<DataBinder>().LifeStyle.Transient,
            Component.For
<LoadUsingFilter>().LifeStyle.Transient,
            Component.For
<CurrentBasketBinder>().LifeStyle.Transient,
            Component.For
<ProductBinder>().LifeStyle.Transient,
            Component.For
<OrderBinder>().LifeStyle.Transient,
            Component.For
<IOrderSearchService>().ImplementedBy<OrderSearchService>().LifeStyle.Transient,
            Component.For
<IEmailBuilder>().ImplementedBy<EmailBuilder>().LifeStyle.Singleton
        );
        
return container;
}

     

      首先是读入指定配置文件的XML结点信息,将构造一个 WindsorContainer实现,同时在其微内核中
添加“容器处理组件”的方式(AddHandlerSelector),注意这种处理方式是按我们在业务逻辑中规定
的方式处理的。

      紧跟着又向该容器中注册了Controller,而且配置属性的LifeStyle被指定为Transient类型,这里
有必要介绍一下Castle容器的组件生存周期,主要有如下几种:   

    Singleton : 容器中只有一个实例将被创建
    Transient : 每次请求创建一个新实例
    PerThread: 每线程中只存在一个实例
    PerWebRequest : 每次web请求创建一个新实例
    Pooled :使用"池化"方式管理组件,可使用PooledWithSize方法设置池的相关属性。

     

      可以看到在本项目中,组件的生命周期基本上都被指定成为Transient类型,即当请求发生时创建,
在处理结束后销毁。

      接着再看一下该方法的其余代码,也就是对ModelBinder,Filter,Service这类业务逻辑的组件注
册。同时我们看到有的组类在进行接口注册的同时还被绑定了默认的实现类,其这种硬编码的方法是
是一种“可选”方式。

      说完了Build方法之前,再回到Global.asax文件中的InitializeWindsor方法,看一下其余的代码。
我们看到这样一行:
 

     WcfConfiguration.ConfigureContainer(container);

    
      类WcfConfiguration的ConfigureContainer方法就是继续向当前创建的容器中添加组件,而这次要
加入的组件是Windows Live Writer的IMetaWeblog接口实现类,如下:

public static class WcfConfiguration
{
    
public static void ConfigureContainer(IWindsorContainer container)
    {
        var returnFaults 
= new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true };
        container.AddFacility
<WcfFacility>(f =>
        {
            f.Services.AspNetCompatibility 
= AspNetCompatibilityRequirementsMode.Required;
            f.DefaultBinding 
= new XmlRpcHttpBinding();
        })
            .Register(
                Component.For
<IServiceBehavior>().Instance(returnFaults),
                Component.For
<XmlRpcEndpointBehavior>(),
                Component.For
<IMetaWeblog>().ImplementedBy<MetaWeblogWcf>().Named("metaWebLog").LifeStyle.Transient
                );
    }
}

 
      如前面所说的,扩张单元插件(Facilities)可以在不更改原有组件的基础上注入你所需要的功能代码,
这里就使用了其AddFacility方法来添加扩展单元来注册并管理我们的Windows Live Writer组件。

      下面继分析InitializeWindsor方法中的其余代码,看完了ConfigureContainer方法,接着就是下面这
一行代码了:

     ServiceLocator.SetLocatorProvider(() => container.Resolve<IServiceLocator>());

  
      刚看到这一行让我感觉似曾相识,记得以前在看Oxite的Global.asax中也看过类似的这样一行代码。

     ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));  

     
      只不过那个项目中用的是 Unity而不是Castle Windsor。但实际的功能是一样的。即完成对容器中服务
地址的解析绑定。有了它,就可以通过Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
中所定义的方法如:DoGetInstanceDoGetAllInstances 来获取相应的服务组件(集合)的实例。

     比如本项目中的DoGetInstance及DoGetAllInstances()实现代码如下:

    (文件位于:Suteki.Common\Windsor\WindsorServiceLocator.cs):

protected override object DoGetInstance(Type serviceType, string key)
{
    
if (key != null)
        
return container.Resolve(key, serviceType);
    
return container.Resolve(serviceType);
}
/// <summary>
/// When implemented by inheriting classes, this method will do the actual work of
/// resolving all the requested service instances.
/// </summary>
/// <param name="serviceType">Type of service requested.</param>
/// <returns>
/// Sequence of service instance objects.
/// </returns>
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
    
return (object[])container.ResolveAll(serviceType);
}

        
        
      注,对该WindsorServiceLocator类的IOC绑定在ContainerBuilder.Build中,如下:  

 
  container.Register(
       Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSQLUnitOfWorkManager>().LifeStyle.Transient,
       Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
       Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
 

     
     
      而InitializeWindsor方法中的最后一行代码如下:    

     System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));

     
      这里要说明的是WindsorControllerFactory这个类是在 MvcContrib项目中提供的,用于构造一个
Castle项目类型的Controller工厂。
     
    
      好了,今天的内容可能有点杂,主要就是Castle闹的,因为其中有一些概念要了解之后才能把项目中
的代码看懂。不过从整体上来看,Suteki.Shop的IOC机制实现的还是很清晰的,没什么太难看的代码。

      今天就先到这里了。
    
     
   
      原文链接: http://www.cnblogs.com/daizhj/archive/2009/05/26/1456678.html

      作者: daizhj,代震军,LaoD

      Tags: mvc,Suteki.Shop,Castle,IOC

      网址: http://daizhj.cnblogs.com/