[Lucene]DotLucene:37行代码实现全文搜索

mikel阅读(702)

DotLucene是一个强有力的开源全文搜索引擎,它是从Apache的Lucene(java)项目移植到.Net(C#)上的。

DotLucene的效率非常高,并且还具有对搜索结果评级、高光、搜索非结构数据以及本地化等特点。它还和Lucene的索引兼容,因此你可以在不同的平台间迁移而不会丢失任何索引数据。

本文介绍了如何通过简洁的代码来使用DotLucene完成全文搜索功能。

本文翻译自CodeProject上 Dan Letecky 的 DotLucene: Full-Text Search for Your Intranet or Website using 37 Lines of Code 一文,文章版权为原作者所有。

译者:Samuel Chen

Image

DotLucene: 优秀的全文搜索引擎

有可能用37行代码写一个全文搜索么? 恩,我正准备使点小技巧用DotLucene来完成这个麻烦的工作. DotLucene 是一个Jakarta Lucene搜索引擎的移植项目,该项目由 George Aroush et al 维护。下面是它的一些特性:

  • 它能被用在 ASP.NET、WinForms 或者 console 应用;
  • 非常高效的性能; 
  • 搜索结果评级; 
  • 搜索结果中查询关键字高光 ;
  • 搜索结构化和非结构化数据;
  • Metadata 搜索 (时间查询、搜索指定域/字段…)
  • 索引大小大约是索引文本的30%;
  • 并且能存储所有编入索引的文档 Can store also full indexed documents
  • 纯.Net托管代码,单个执行文件 (244 kB)
  • 非常友好的许可 (Apache Software License 2.0)
  • 本地化 (支持 巴西语、捷克语、中文、荷兰语、英语、法语、日语、韩语和俄语)
  • 可扩展 (源代码已包含)
注意

    不要过于在意代码行数。我将用不超过37行代码给你演示他的核心功能,但是要做成一个真正实用的应用,你还需要花更多的时间… 

演示项目

这里,我们将做一个简单的项目演示怎么去做到如下几点:

  • 索引在指定目录(包括子目录)中找到的Html文件
  • 用一个ASP.NET应用程序来搜索索引
  • 搜索结果中高亮显示查询的单词

DotLucene还具有更多的潜力。在实际的应用中你大概想这么去做:

  • 当目录中怎就新文档时添加到索引,而不用重新编译整个索引
  • 包含各种文件类型。DotLuncene能够索引任何能转换成纯文本的文件类型
为什么不使用微软索引服务(Microsoft Indexing Server)?

如果你喜欢用索引服务,没问题。然而,使用DotLucene会有更多好处:

  • DotLucene 是一个100%托管代码的单执行文件,不需要任何依赖
  • 它能被使用到一个共享主机。如果事先准备好索引,你可以不需要磁盘的写权限
  • 使用它,你可以从任何源((数据库、网站…)索引任何类型数据(电子邮件、XML、HTML文件…)。那是因为你需要提供纯文本给索引器(Indexer),载入和解析取决于你
  • 允许你选择要包含在索引中的指定属性("字段"),从而你可以使用这些字段来搜索(例如,作者、日期、关键字等)
  • 它是一个开源软件
  • 它易于扩展
第1行:建立索引

下面的代码从存盘存储建立一个新的索引,directory是存储索引的目录路径参数。

IndexWriter writer = new IndexWriter(directory, new StandardAnalyzer(), true);

这个例子中我们总是重新创建索引(In this example we always create the index from scratch),但这不是必须的,你也可以打开一个已有的索引并添加文档进去。你还可以通过删除然后添加它们的新版本来更新现存的文档(译注:这里应该 是指对象的创建)

第2 – 12行:添加文档

我们为每一个HTML文档添加两个字段到索引:

  • "text" 字段,容纳HTML文件的文本内容(去除了标记),文本数据本身并不会存储在索引中
  • "path" 字段,容纳文件路径,它将会被(索引并)完整的存入索引中
public void AddHtmlDocument(string path)
{
Document doc = new Document();
string rawText;
using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default))
{
rawText = parseHtml(sr.ReadToEnd());
}
doc.Add(Field.UnStored("text", rawText));
doc.Add(Field.Keyword("path", path));
writer.AddDocument(doc);
}
第13 – 14行:优化并保存索引

添加完文档后,你需要关闭索引器。使用优化将会提高搜索性能。

writer.Optimize();
writer.Close();

第15行:打开索引搜索

在做任何搜索之前,你需要打开索引。directory参数是存储索引的目录路径。

IndexSearcher searcher = new IndexSearcher(directory);
第16 – 27行:搜索

现在,我们解析查询了("text"是默认搜索字段)

Query query = QueryParser.Parse(q, "text", new StandardAnalyzer());
Hits hits = searcher.Search(query);

变量hits是搜索结果文档的集合,我们将通过它来将结果存储到DataTable

DataTable dt = new DataTable();
dt.Columns.Add("path", typeof(string));
dt.Columns.Add("sample", typeof(string));
for (int i = 0; i < hits.Length(); i++)
{
// get the document from index
Document doc = hits.Doc(i);
// get the document filename
// we can't get the text from the index because we didn't store it there
DataRow row = dt.NewRow();
row["path"] = doc.Get("path");
dt.Rows.Add(row);
}
第28 – 37行:高亮Lines 28 – 37: Query Highlighting

我们先创建一个高亮器对象highlighter,并将使用加黑(bold)字体来高亮显示(<B>查询词</B>)。

QueryHighlightExtractor highlighter = new
QueryHighlightExtractor(query, new StandardAnalyzer(), "<B>", "</B>");

通过对结果遍历,我们将载入原文中最相似的部分。

for (int i = 0; i < hits.Length(); i++)
{
// ...
string plainText;
using (StreamReader sr = new StreamReader(doc.Get("filename"),
System.Text.Encoding.Default))
{
plainText = parseHtml(sr.ReadToEnd());
}
row["sample"] = highlighter.GetBestFragments(plainText, 80, 2, "...");
// ...
}
资源

[Lucene]Lucene.Net系列一 ---simple example

mikel阅读(736)

What’s Lucene
Lucene是一个信息检索的函数库(Library),利用它你可以为你的应用加上索引和搜索的功能.

Lucene的使用者不需要深入了解有关全文检索的知识,仅仅学会使用库中的一个类,你就为你的应用实现全文检索的功能.

不过千万别以为Lucene是一个象google那样的搜索引擎,Lucene甚至不是一个应用程序,它仅仅是一个工具,一个Library.你也 可以把它理解为一个将索引,搜索功能封装的很好的一套简单易用的API.利用这套API你可以做很多有关搜索的事情,而且很方便.

What Can Lucene Do

Lucene可以对任何的数据做索引和搜索. Lucene不管数据源是什么格式,只要它能被转化为文字的形式,就可以被Lucene所分析利用.也就是说不管是MS word, Html ,pdf还是其他什么形式的文件只要你可以从中抽取出文字形式的内容就可以被Lucene所用.你就可以用Lucene对它们进行索引以及搜索.

How To Use Lucene — A Simple Example
示例介绍:

为作为输入参数的文件夹下的所有txt类型的文件做索引,做好的索引文件放入index文件夹.

然后在索引的基础上对文件进行全文搜索.

1.       建立索引

IndexWriter writer = new IndexWriter("index"new StandardAnalyzer(), true);
IndexDocs(writer, 
new System.IO.FileInfo(args[0]));               
writer.Optimize();
writer.Close(); 

IndexWriter是对索引进行写操作的一个类,利用它可以创建一个索引对象然后往其中添加文件.需要注意它并不是唯一可以修改索引的类.在索引建好后利用其他类还可以对其进行修改.

构造函数第一个参数是建立的索引所要放的文件夹的名字.第二个参数是一个分析对象,主要用于从文本中抽取那些需要建立索引的内容,把不需要参与建索 引的文本内容去掉.比如去掉一些a the之类的常用词,还有决定是否大小写敏感.不同的选项通过指定不同的分析对象控制.第三个参数用于确定是否覆盖原有索引的.

第二步就是利用这个writer往索引中添加文件.具体后面再说.

第三步进行优化.

第四步关闭writer.

 

下面具体看看第二步:

 

  public static void IndexDirectory(IndexWriter writer, FileInfo file)
         
{
              
if (Directory.Exists(file.FullName))
              
{
                   String[] files 
= Directory.GetFileSystemEntries(file.FullName);
                   
// an IO error could occur
                   if (files != null)
                   
{
                       
for (int i = 0; i < files.Length; i++)
                       
{
                            IndexDirectory(writer, 
new FileInfo(files[i]));  //这里是一个递归
                       }

                   }

              }

              
else if (file.Extension == ".txt")
              
{
                   IndexFile(file, writer);
              }

         }


 

         
private static void IndexFile(FileInfo file, IndexWriter writer)
         
{
              Console.Out.WriteLine(
"adding " + file);
              
try
              
{
                   Document doc 
= new Document();                   
                   doc.Add(Field.Keyword(
"filename", file.FullName));

                   doc.Add(Field.Text(
"contents"new StreamReader(file.FullName)));

                   writer.AddDocument(doc);
              }

              
              
catch (FileNotFoundException fnfe)
              
{
                   
              }

     }


主要就是两个函数一个用于处理文件夹(不是为文件夹建立索引),一个用于真正为文件建立索引.

因此主要集中看一下IndexFile这个方法.首先建立Document对象,然后为Document对象添加一些属性Field.你可以把Document对象看成是虚拟文件,将来将从此获取信息.而Field则看成是描述此虚拟文件的元数据(metadata).

其中Field包括四个类型:

Keywork

该类型的数据将不被分析,而会被索引并保存保存在索引中.
UnIndexed 该类型的数据不会被分析也不会被索引,但是会保存在索引.
UnStored 和UnIndexed刚好相反,被分析被索引,但是不被保存.
Text 和UnStrored类似.如果值的类型为string还会被保存.如果值的类型为Reader就不会被保存和UnStored一样.

 

最后将每一个Document添加到索引当中.

需要注意的是索引不仅可以建立在文件系统上,也可以建立在内存中.

例如

IndexWriter writer = new IndexWriter("index", new StandardAnalyzer(), true);

在第一个参数不是指定文件夹的名字而是使用Directory对象,并使用它的子类RAMDirectory,就可以将索引建立在内存当中.

 

2.       对索引进行搜索

IndexSearcher indexSearcher= new IndexSearcher(indexDir);
Query query 
= QueryParser.Parse(queryString, "contents",new StandardAnalyzer());
Hits hits 
= indexSearcher.Search(query);

第一步利用IndexSearcher打开索引文件用于后面搜索,其中的参数是索引文件的路径.

第二步使用QueryParser将可读性较好的查询语句(比如查询的词lucene ,以及一些高级方式lucene AND .net)转化为Lucene内部使用的查询对象.

第三步执行搜索.并将结果返回到hits集合.需要注意的是Lucene并不是一次将所有的结果放入hits中而是采取一次放一部分的方式.出于空间考虑.

至此,本文演示了如何从一个文件夹下的所有txt文件中查找特定的词。并围绕该个实例介绍了lucene.net的索引的建立以及如何针对索引进行搜索.最后给出源代码供大家学习.

[Lucene]为自己的系统搞个全文搜索

mikel阅读(693)

为自己的系统搞个全文搜索
 
在本文我又提到lucene了,在java业界,提到全文检索,几乎没有什么人不知道它。
用google搜索一下,满世界都是有关资料。具有代表性的就是车东的“基于Java的全
文索引引擎Lucene简介”,我要写的也就只有最简单的三板斧,再加上支持中文的
ChineseAnalyzer以及按照时间排序的搜索结果排序方法。这些都可以在其他地方找
到相关资料,我只是把他们提出来,作为lucence应用中经常遇到的麻烦解决办法。
去年MSN上面有个朋友跟我提到希望用lucene构建个网站的全文检索,我当时就觉得
很简单,直说没问题没问题,不过他提到一个要求就是搜索结果要安装时间排序,
我查阅了些资料,发现lucene并不提供用户自定义排序方式,而只能按照自己相关
性算法排序。后来我在车东的weblucene项目找到了IndexOrderSearcher。解决了结
果排序常规需求。IndexOrderSearcher跟一般IndexSearch使用差不多,仅仅在构建
对象的时候多加一个参数IndexOrderSearcher.ORDER_BY_DOCID_DESC
IndexOrderSearcher indexsearcher =
new IndexOrderSearcher("/home/lucenetest/index",IndexOrderSearcher.ORDER_BY_DOCID_DESC);
新版本的lucene还提供了一个MultiFieldQueryParser,可以同时检索多个字段,以前
QueryParser比较麻烦。

private  static  ChineseAnalyzer  chineseAnalyzer  =  new  ChineseAnalyzer();
public  Hits  search(String  queryText)
{
 if  (queryText  ==  null)
        {
         return  null;
 }
 Query  query;
   try
   {
         query  =  MultiFieldQueryParser.parse(queryText,  new  String[]{"title"},chineseAnalyzer);
        return  indexsearcher.search(query);
   }
   catch(Exception  e)
   {
        return  null;
   }
}
下面是构建索引,定时从数据库取出数据索引,做完记录完成时间,我是把时间写入一个txt文件。
package  com.test.search;
import  org.apache.lucene.analysis.Analyzer;
import  org.apache.lucene.analysis.cn.*;
import  org.apache.lucene.analysis.standard.StandardAnalyzer;
import  org.apache.lucene.document.*;
import  org.apache.lucene.index.*;

import  java.io.*;
import  java.SQL.*;
import  java.util.Date;

import  com.test.db.*;
import  com.test.utility.*;

/**
  *  Title:  SearchIndexer
  *  Description:  全文索引
  *  Copyright:      Copyright  (c)  2001
  *  Company:  test
  *  @author  Sean
  *  @version  1.0
  */
public  class  SearchIndexer 
{
    private  String  indexPath  =  null;
    protected  Analyzer  analyzer  =  new  ChineseAnalyzer();

    public  SearchIndexer(String  s) 
    {
        this.indexPath  =  s;
    }
    /**
      *  索引某日期以前的所有文档
      *  @param  fromdate
      *  @return
      */
    public  final  void  updateIndex(String  fromdate) 
    {
        Connection  conn  =  DbUtil.getCon();
        IndexWriter  indexWriter  =  null;
        try 
        {
             indexWriter  =  getWriter(false);
             //索引发布系统内部文件
                PreparedStatement  pstm  =  conn.prepareStatement(
                        "select  title,body,creationtime  from  document 
                        where  creationtime  >  '"  +  fromdate  +
                        "'  order  by  creationtime");
                ResultSet  rs  =  pstm.executeQuery();
                while  (rs.next()) 
                {
                    String  creationtime  =  rs.getString("creationtime");
                    String  title  =  rs.getString("title");
                    String  body  =  rs.getString("body");

                   
                    if  (title  ==  null  ||  body  ==  null) 
                    {
                        continue;
                    }
                    try 
                    {
                        addDocsToIndex(title,body,  creationtime,indexWriter);
                    }
                    catch  (Exception  ex) 
                    {
                        ex.printStackTrace();
                    }
              }
            indexWriter.optimize();
        }
        catch  (Exception  ex) 
        {
            ex.printStackTrace();
        }
        finally 
        {
            try 
            {
                indexWriter.close();
                conn.close();
            }
            catch  (Exception  e) 
            {
                e.printStackTrace();
            }
        }
    }
    /**
      *  检查索引文件是否存在
      *  @param  s
      *  @return  索引是否存在
      */
    private  boolean  indexExists(String  s) 
    {
        File  file  =  new  File(s  +  File.separator  +  "segments");
        return  file.exists();
    }
    /**
      *  增加一组索引
      *  @param  title
      *  @param  body
      *  @param  creationtime
      *  @param  indexwriter
      *  @return
      */
    private  final  void  addNewsToIndex(String  docid,  String  url,String  title,  String  body,
                                String  ptime,  IndexWriter  indexwriter)  throws IOException 
    {
        if  (indexwriter  ==  null) 
        {
            return;
        }
        else 
        {
            try 
            {
                Document  document  =  new  Document();
                document.add(Field.Text("title",  title));
                document.add(Field.Text("body",  body));
                document.add(new  Field("creationtime",  creationtime,  true,  true,  false));
                indexwriter.addDocument(document);
            }
            catch  (Exception  ex) 
            {
         ex.printStackTrace();
            }
            return;
        }
    }
    /**
      *  取得IndexWriter
      *  @param  flag  是否新建索引
      *  @return  IndexWriter
      */
    private  IndexWriter  getWriter(boolean  flag)  throws  IOException 
    {
        String  s  =  indexPath;
        if  (s  ==  null) 
        {
            throw  new  IOException("索引文件路径设置错误.");
        }
        indexPath  =  s  +  File.separator  +  "search";
        IndexWriter  indexwriter  =  null;
        if  (flag) 
        {
            try 
            {
                indexwriter  =  new  IndexWriter(indexPath,  analyzer,  true);
            }
            catch  (Exception  exception) 
            {
                System.err.println("ERROR:  Failed  to  create  a  new  index  writer.");
                exception.printStackTrace();
            }
        }
        else 
        {
            if  (indexExists(indexPath)) 
            {
                try 
                {
                    indexwriter  =  new  IndexWriter(indexPath,  analyzer,  false);
                }
                catch  (Exception  exception1) 
                {
                    System.err.println("ERROR:  Failed  to  open  an  index  writer.");
                    exception1.printStackTrace();
                }
            }
            else 
            {
                try  {
                    indexwriter  =  new  IndexWriter(indexPath,  analyzer,  true);
                }
                catch  (Exception  exception2) 
                {
                    System.err.println("ERROR:  Failed  to  create  a  new  index  writer.");
                    exception2.printStackTrace();
                }
            }
        }
        return  indexwriter;
    }

    public  static  void  main(String[]  args) 
    {
        String  lastUpdate  =  "/home/lucenetest/lastUpdate.txt";
        SearchIndexer  searchIndexer  =  new  SearchIndexer("/home/lucenetest/index");
        //取出上次更新时间
        String  str  =  Util.readTxtFile(lastUpdate);
        if(str==null  ||  str.length()==0)
        {
            str  =  new  java.util.Date().toString();
        }
        searchIndexer.updateIndex(str);
        //写入当前时间
        Util.writeTxtFile(lastUpdate,new  java.util.Date(),false);
    }
}

写个cmd或者sh在相应操作系统下面定时执行SearchIndexer就可以了

[SQLServer]SQL Server2005全文索引

mikel阅读(722)

Microsoft SQL Server 2005 引入了新的 Transact-SQL 数据定义语言 (DDL) 语句,用来创建、实现和管理全文目录和索引。以下是新的全文搜索 DDL 语句的列表。
Create FULLTEXT CATALOG (Transact-SQL)
使用此语句可以为数据库创建全文目录。
Create FULLTEXT INDEX (Transact-SQL)
使用此语句可以针对数据库某个表的一列或多列创建全文索引。
Alter FULLTEXT CATALOG (Transact-SQL)
使用此语句可以更改全文目录的属性。
Alter FULLTEXT INDEX (Transact-SQL)
使用此语句可以更改全文索引的属性。
Drop FULLTEXT CATALOG (Transact-SQL)
使用此语句可以删除全文目录。
Drop FULLTEXT INDEX (Transact-SQL)
使用此语句可以从指定的表中删除全文索引。

 

———————————————————————————-

而以下是老旧的sql2000的方法:

———————————————————————————-
一个完整的SQL SERVER数据库全文索引的示例。(以pubs数据库为例)

首先,介绍利用系统存储过程创建全文索引的具体步骤:

1) 启动数据库的全文处理功能 (sp_fulltext_database)
2) 建立全文目录 (sp_fulltext_catalog)
3) 在全文目录中注册需要全文索引的表 (sp_fulltext_table)
4) 指出表中需要全文索引的列名 (sp_fulltext_column)
5) 为表创建全文索引 (sp_fulltext_table)
6) 填充全文目录 (sp_fulltext_catalog)


———********示例********————-
以对pubs数据库的title和notes列建立全文索引,之后使用索引查询title列或notes列中包含有datebase 或computer字符串的图书名称:

在这之前,需要安装Microsoft Search服务,启动SQL server全文搜索服务


user pubs –打开数据库
go
–检查数据库pubs是否支持全文索引,如果不支持
–则使用sp_fulltext_database 打开该功能
if(select databaseproperty('pubs','isfulltextenabled'))=0
execute sp_fulltext_database 'enable'

–建立全文目录FT_PUBS
execute sp_fulltext_catalog 'FT_pubs','create'

–为title表建立全文索引数据元
execute sp_fulltext_table 'title','create','FT_pubs','UPKCL_titleidind'

–设置全文索引列名
execute sp_fulltext_column 'title','title','add'
execute sp_fulltext_column 'title','notes','add'

–建立全文索引
–activate,是激活表的全文检索能力,也就是在全文目录中注册该表
execute sp_fulltext_table 'title','activate'

–填充全文索引目录
execute sp_fulltext_catalog 'FT_pubs','start_full'
go

–检查全文目录填充情况
While fulltextcatalogproperty('FT_pubs','populateStatus') <>0
begin

–如果全文目录正处于填充状态,则等待30秒后再检测一次
waitfor delay '0:0:30'
end

–全文目录填充完成后,即可使用全文目录检索

select title
form
where CONTAINS(title,'database')
or CONTAINS(title,'computer')
or CONTAINS(notes,'database')
or CONTAINS(notes,'database')


'————–以下介绍一下全文操作类的系统存储过程
过程名称:sp_fulltext_service
执行权限:serveradmin或系统管理员
作 用:设置全文搜索属性


过程名称:sp_fulltext_catalog
执行权限:db_owner及更高角色成员
作 用:创建和删除一个全文目录,启动或停止一个全文目录的索引操作


过程名称:sp_fulltext_database
执行权限:db_owner角色成员
作 用:初始化全文索引或删除数据库中所有全文目录


过程名称:sp_fulltext_table
执行权限:db_ddladnmin或db_owner角色成员
作 用:将一个表标识为全文索引表或非全文索引表


过程名称:sp_fulltext_column
执行权限:db_ddladnmin角色成员
作 用:指出一个全文索引表中的那些列假如或退出全文索引

[设计]如何设计通用的网站模板

mikel阅读(879)

现在网络上已经到处可以看到使用模板开发出来的网站。使用模板开发网站有很多好处,最主要的就是模板与程序完全脱离,用户可以根据规定好的标签任意开发模 板,导入到模板引擎里就能正常运行。所以美工人员跟程序编写人员彻底的独立了。提高了开发网站的效率,程序的重用性发挥的淋漓尽致。
    智能建站系统、速成网站、自助建站系统、只会打字就能做网站,等等广告字眼我们已经不再陌生了,所有的这些无非都是围绕着模板做文章。也正是因为 这样的系统越来越普及,对于要求不高的企业或者个人用户,拥有一个完全属于自己的网站,用现成的模板要比找网络公司定制省钱的多。性价比要高的多,所以目 前中小型网络公司的生存问题堪忧。
    提供模板做论坛的discuz公司,各位站长应该都很熟悉,但是做企业网站、独立购物网系统,国内做的比较好的几家公司,想必大家可能不太熟悉:ShopEx携购网店系统(ShopXG)HiShop狼烟网络(mynet.cn) 等等,大家有机会可以去他们的官网看看各自的优势。
    下面我们来详细讲解下如何开发网站模板。
    涉及到模板的部分我们可以分成以下几块:自定义标签、模板文件、数据控制项、模板引擎
    1. 自定义标签 自定义标签是模板文件与模板引擎之间交互的基础,也可以说是协议。 目前网络上普遍的标签样式为:{$****} {#*****#},这样的表示方法,主要是为了区分html等脚本标签。 标签的内容区可以设置多种属性。比如len=100 表示数据长度不超过100个字节。 标签变量根据不同的类型,我们可以把名称规定为如下方式:
    {$var_**} 变量标签,表示这个标签的数据来源是某一个字段的值
    {$const_**} 常量标签,表示这个标签是一个常量数据,比如,当前的日期等
    {$temp_small} 小模板数据。(什么叫小模板,我们在后面详细讲解)
    {$page_**} 分页数据。例如首页,上页,下页等翻页的代码部分。等等。
    举例:{$var_news_title len=30} 表示显示新闻的标题,如果新闻标题的字数超过 30个字,那么截取。
    2.模板文件
    模板文件就是由美工人员开发的脚本代码,里面不涉及到任何程序。由于数据经常会涉及到一个循环显示的问题,所以我们把模板文件又分成:大模板、小模板。
    模板文件一般都是普通的html文件,源代码里包含了事先约定好的自定义标签,
    图标说明每个模块的共性:

    图1:新闻列表        图2:产品列表
    大家看上面两个模块,一个是新闻列表,一个是产品列表。无论从数据类型还是展现方式上乍一看截然不同。
    但是大家仔细的看一下,很容易看出来他们其实有很多共同的特点–他们都是有大模板(模块的外框部分)、小模板(模块的数据循环部分)组成。另外,大家可以去找个网站分解下看看。是不是都可以把他们分解成不同的模块,然后每个模块又可以分成大模板、小模板组成。
    我们以上图中新闻列表来给大家讲解下:
    大模板文件就是整个外框部分。基本html如下面所示:

	<div  class=”newslist”>
<h>携购网店 – 不可多得的创业好项目</h>
<ul>代理商加盟携购之后能得到什么?!</ul>
<div>{$temp_small}</div>
</div>

    从上面可以看出,大模板文件里并没有涉及到数据部分,只有一个小模板标签{$temp_small} ,当模板引擎解析到这个标签的时候,将对应的小模板文件解析后,用最终生成的html数据将该标签替换掉。
    接下来,我们来看一下小模板的组成:
    这里特别需要注意下,当模板引擎在解析小模板的时候,是把小模板的数据当作一个循环体,说明白些:比如有10条新闻,那么模板引擎将循环10次小模板数据,将具体的新闻内容分别去替换小模板里的标签,然后组合成一个最终的html.
    小模板的脚本部分:

	<ul  clas=”small_item”>
<li>{$var_news_title  len=100}</li>
<li>{$var_news_add_time}</li>
</ul>

    当然,需要分页的时候,还需要做分页模板,解析引擎会解析分页模板后,将查询到的当前数据替换掉对应的分页变量标签,最后,替换掉大模板中的分页模板标签。
    分页的脚本部分举例:

		<a href="{$var_link_firstpage_link}" id="modulelist_linkup_first">首页</a>
<a href="{$var_link_prevpage_link}" id="modulelist_linkup_prev">上一页</a>
{$var_link_pageteam} <a href="{$var_link_nextpage_link}" id="modulelist_linkup_next">下一页</a>
<a href="{$var_link_lastpage_link}" id="modulelist_linkup_last">末页</a>总数:{$var_link_totalnum} 每页条数:{$var_link_perpagecnt}
总页数:{$var_link_totalpage}当前页码:{$var_link_curpagenum}/{$var_link_totalpage}

    最后从结构上我们再总结下模板的组成关系:

    3.数据控制部分
    该部分是要将哪些数据展示在模板数据项的核心。主要包括以下几部分:
    a. 查询条件 ,从数据库里要查出哪些数据。
    b. 排序类型, 将查询得到的数据,通过何种方式进行排序展示。
    c. 当前页码, 跟每页显示条数配合使用,
    d. 每页显示条数
    e. 是否忽略URL地址里传过来的参数,这个项非常重要,因为像分页和查询,他们都需要从url将参数传过来,而有些定死的数据又不希望受到url参数的影响,所以该项在控制数据显示方面尤为重要。
    f. 接受url的参数列表。等等.
    有了数据控制部分,才能让相同类型的模块展示不同的数据。
    4. 模板引擎
    我们知道模板文件是静态的html脚本页面,它并不具有业务操作能力,单纯的模板没有任何存在的意义。需要模板引擎通过数据控制将获取到的数据按照模板文件规定的样式展示出来,
    模板引擎的好坏,直接影响到整个程序的性能,模板要跟数据库打交道,由于模板文件有非常多的数据冗余,而且,设计不合理的模板,同个页面可能需要多次相同的数据库查询操作,如果模板引擎不能很好的区分出来。那么对数据库的压力也是非常大的。
    如果大家对开发模板网站有兴趣,强烈推荐去用下携购公司开发的VTEditor可视化网站模板开发系统。官方网站:http://www.xiegoo.com/ .是目前唯一真正实现可视化模板编辑的系统。

[Google]搜索引擎 创意 shell

mikel阅读(910)

http://goosh.org

搜索引擎 创意 shell

主要功能:shell版的搜索引擎

自由言论:在web上用用shell也是一件挺有意思的事情,蛮有一种复古的感觉。感谢google提供这种shell检索的方式(help查看可用命令)。

页面截图:

[C#]虚拟主机上实现二级别域名解析(泛解析) (asp.net)

mikel阅读(848)

前两天写了一篇立主机无限二级域名解释的方式,不过由于服务器出了点问题,自己比较忙,这篇就拖了点时间才写出来!

现在实现无限二级域名解释的方式,如重写url的组件也相当多!实现方式也不少!实现方式也不难。

独立主机的实现方法最简单的一个可以参考:http://www.cnblogs.com/flyboy/archive/2009/01/06/1369932.html

 

不过不幸的是所有这些都要求主机支持域名泛解析,一旦拿到虚拟主机上,一切都枉然!

我也是在自己机器上做好后拿到虚拟主机上发现完全作废!不过也不是没办法!

下面就讲讲如何实现虚拟主机上实现二级别域名解析(泛解析) (ASP.NET)吧!

 

1、首先,在域名解析里,要把*.iloveyou.io 隐含转向指向你网站的某个转门用来判断的页面,注意,必须是隐含!我们假设为 http://www.iloveyou.io/wr.aspx

2、在http://www.iloveyou.io/wr.aspx 页面取的来路的路径!

        protected void Page_Init(object sender, EventArgs e)
        {

          string    path = Request.UrlReferrer.AbsoluteUri.ToLower();

             。。。

         }

       这里取得的不是当前路径,而是来路路径,也就是转向前url的路径了!

       取得后就进行你需要的处理! 用上一节的例子来讲,把 http://liangsan.iloveyou.io 解析到 http://www.iloveyou.io/love/?toName=liangsan 这个页面。

3、    用 Response.Redirect(newurl);来实现跳转~!

 

        protected void Page_Init(object sender, EventArgs e)
        {

            string path = Request.UrlReferrer.AbsoluteUri.ToLower();//得到的如path =http://liangsan.iloveyou.io/

             。。。

          string newurl=…//如 http://www.iloveyou.io/love/?toName=liangsan

          Response.Redirect(newurl);

         }

 

具体的例子可以上一个爱情表白网站看下: http://www.iloveyou.io

缺点,不过这个方法有个缺点就是,百度收录页面看不到二级域名页面的内容,不利搜索引擎!

不过因为我没有自己独立主机,又要实现,用户可以申请 “心上人名字.iloveyou.io"的域名做为表白使用,只能放弃搜索引擎了!

不知道朋友你有没什么更好的解决方法,有的话请告知小弟!谢谢!

以上就是本人所知道和已经实现的虚拟主机上实现二级别域名解析(泛解析) (ASP.NET),如有不足请指正!谢谢!

[Tool]ASP.NET程序调试经常要用的辅助工具

mikel阅读(727)

在进行ASP.NET调试的时候,我们经常需要借助一些外部工具来辅助我们。俗话说,工欲善其事 必先利其器。可别小看了这些工具,它是你解决复杂问题的必备利器。比较常用的有这些:

HTTP抓包工具: Microsoft Network Monitor,Fiddler2,HttpWatcher 等,主要用来查看HTTP消息的header,以及body。

代码查看器: Reflector,利用反编译来查看assembly里的代码。通过这个工具,我们可以很轻易的看到.NET Framework的一些功能是怎么实现的。

文件监视器: Process Monitor,可以用来监视系统的哪些文件正在被修改,注册表中有什么改动等等,功能非常强大。

数据库工具:SQL Sever Profiler,可以用来查看SQL Server正在执行哪些脚本。对于和SQL Server通信的程序的调试,它是非常实用的。
 

其实还有很多好用的工具,但是如果能熟练使用这几个工具了,大部分的问题都能轻易解决了。下次将会介绍一下IIS 7中调试ASP.NET程序的一些实用的方法。

[Comet]用Mochiweb打造百万级Comet应用,第一部分

mikel阅读(812)

原文:A Million-user Comet Application with Mochiweb, Part 1

参考资料:Comet–基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”

               MochiWeb–建立轻量级HTTP服务器的Erlang库

 

      在这个系列中,我将详述我所发现的mochiweb是怎样支持那么巨大的网络连接的,为大家展示怎样用mochiweb构建一个comet应用,这个应用 中每个mochiweb连接都被注册到负责为不同用户派送消息的路由器上。最后我们建立一个能够承受百万并发连接的可工作的应用,更重要的我们将知道这样 的应用需要多少内存才能使它跑起来。

 

本部分内容如下:

  • 建立一个基本的comet应用, 它每10秒钟给客户端发送一条消息
  • 调整linux内核参数,使它能够处理大量的TCP连接
  • 写一个能够建立大量网络连接的压力测试工具 (也就是 C10k测试)
  • 检查每个连接到底需要多少内存.

本系列续作将包括怎样建立一个真正的信息路由系统,降低内存使用的技巧,100K和1m并发连接的测试。

基础是你需要知道一些linux命令行操作和一点Erlang知识,否则看不懂别怪我呀,呵呵

写一个Mochiweb测试程序

概括如下:

  1. 安装编译Mochiweb
  2. 运行: /your-mochiweb-path/scripts/new_mochiweb.erl mochiconntest
  3. cd mochiconntest 之后编辑 src/mochiconntest_web.erl

这部分代码(mochiconntest_web.erl)只是接收连接并且每十秒用块传输方式给客户端发送一个初始的欢迎信息。

 

mochiconntest_web.erl

  1. module ( mochiconntest_web) .
  2. export ( [ start/1 , stop/0 , loop/2 ] ) .
  3. %% 外部API
  4. start( Options ) ->
  5.     { DocRoot , Options1 } = get_option( docroot, Options ) ,
  6.     Loop = fun ( Req ) ->
  7.                    ?MODULE :loop ( Req , DocRoot )
  8.            end ,
  9.     % 设置最大连接数为一百万,缺省2048
  10.     mochiweb_http:start ( [ { max, 1000000 } , { name, ?MODULE } , { loop, Loop } | Options1 ] ) .
  11.  
  12. stop( ) ->
  13.     mochiweb_http :stop ( ?MODULE ) .
  14.  
  15. loop( Req , DocRoot ) ->
  16.     "/" ++ Path = Req :get ( path) ,
  17.     case Req :get ( method) of
  18.         Method when Method =:= ‘GET’ ; Method =:= ‘HEAD’ ->
  19.             case Path of
  20.                 "test/" ++ Id ->
  21.                     Response = Req :ok ( { "text/html; charset=utf-8" ,
  22.                                       [ { "Server" ,"Mochiweb-Test" } ] ,
  23.                                       chunked} ) ,
  24.                     Response :write_chunk ( "Mochiconntest welcomes you! Your Id: " ++ Id ++ "\n " ) ,
  25.                     %% router:login(list_to_atom(Id), self()),
  26.                     feed( Response , Id , 1 ) ;
  27.                 _ ->
  28.                     Req :not_found ( )
  29.             end ;
  30.         ‘POST’ ->
  31.             case Path of
  32.                 _ ->
  33.                     Req :not_found ( )
  34.             end ;
  35.         _ ->
  36.             Req :respond ( { 501 , [ ] , [ ] } )
  37.     end .
  38.  
  39. feed( Response , Path , N ) ->
  40.     receive
  41.         %{router_msg, Msg} ->
  42.         %    Html = io_lib:format("Recvd msg #~w: ‘~s’<br/>", [N, Msg]),
  43.         %    Response:write_chunk(Html);
  44.     after 10000 ->
  45.         Msg = io_lib:format ( "Chunk ~w for id ~s\n " , [ N , Path ] ) ,
  46.         Response :write_chunk ( Msg )
  47.     end ,
  48.     feed( Response , Path , N +1 ) .
  49.  
  50. %%内部API
  51. get_option( Option , Options ) ->
  52.     { proplists:get_value ( Option , Options ) , proplists:delete ( Option , Options ) } .

 

启动Mochiweb应用

make && ./start-dev.sh
缺省的Mochiweb在所有网卡接口的8000端口上进行监听,假如是在桌面系统上做这些事,你可以使用任何浏览器访问http://localhost:8000/test/foo 进行测试。
这里只是命令行测试:

$ lynx --source "http://localhost:8000/test/foo"
Mochiconntest welcomes you! Your Id: foo<br/>
Chunk 1 for id foo<br/>
Chunk 2 for id foo<br/>
Chunk 3 for id foo<br/>
^C

是的,它可以工作。 现在,让我们使劲整它,呵呵。

调整linux内核参数,使它能够处理大量的TCP连接

为节省时间我们需要在进行大量并发连接测试之前调整内核的tcp设置参数,否则你的测试将会失败,你将看到大量的Out of socket memory 信息(假如在伪造将得到, nf_conntrack: table full, dropping packet. )

下面的是我用到的sysctl设置 – 你的配置可能不一样,但是大致就是这些:

# General gigabit tuning:
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_syncookies = 1
# this gives the kernel more memory for tcp
# which you need with many (100k+) open socket connections
net.ipv4.tcp_mem = 50576   64768   98152
net.core.netdev_max_backlog = 2500
# I was also masquerading the port comet was on, you might not need this
net.ipv4.netfilter.ip_conntrack_max = 1048576

把这些写到 /etc/sysctl.conf中然后运行 sysctl -p 使其生效。不需要重启,现在你的内核能够处理大量的连接了,yay。

建立大量连接

有很多方法可以用. Tsung 就十分好, 也有很多其他比较好的工具如ab, httperf, httpload等等可以生成大量的无用请求。 但是它们中任何一款都不适合测试comet应用, 正好我也想找个借口测试一下Erlang的http客户端, 因此我写了一个基本的测试程序用以发起大量的连接。
只是因为你可以但并不意味着你就这样做.. 一个连接就用一个进程确实有点浪费。我用一个进程从文件中调入一批url链接,另一个进程建立连接并接收数据 (当定时器的进程每10秒打印一份报告)。所有从服务器接收来的数据都被丢弃,但是它增加计数,这样我们能够跟踪到底有多少http数据块被传输了。

floodtest.erl

  1. module ( floodtest) .
  2. export ( [ start/2 , timer/2 , recv/1 ] ) .
  3.  
  4. start( Filename , Wait ) ->
  5.     inets :start ( ) ,
  6.     spawn( ?MODULE , timer, [ 10000 , self( ) ] ) ,
  7.     This = self( ) ,
  8.     spawn( fun( ) -> loadurls ( Filename , fun( U ) -> This ! { loadurl, U } end , Wait ) end ) ,
  9.     recv( { 0 ,0 ,0 } ) .
  10.  
  11. recv( Stats ) ->
  12.     { Active , Closed , Chunks } = Stats ,
  13.     receive
  14.         { stats} -> io :format ( "Stats: ~w\n " ,[ Stats ] )
  15.         after 0 -> noop
  16.     end ,
  17.     receive
  18.         { http,{ _Ref ,stream_start,_X } } ->  recv ( { Active +1 ,Closed ,Chunks } ) ;
  19.         { http,{ _Ref ,stream,_X } } ->          recv ( { Active , Closed , Chunks +1 } ) ;
  20.         { http,{ _Ref ,stream_end,_X } } ->  recv ( { Active -1 , Closed +1 , Chunks } ) ;
  21.         { http,{ _Ref ,{ error,Why } } } ->
  22.             io :format ( "Closed: ~w\n " ,[ Why ] ) ,
  23.             recv( { Active -1 , Closed +1 , Chunks } ) ;
  24.         { loadurl, Url } ->
  25.             http :request ( get, { Url , [ ] } , [ ] , [ { sync, false} , { stream, self} , { version, 1.1 } , { body_format, binary} ] ) ,
  26.                 recv( Stats )
  27.     end .
  28.  
  29. timer( T , Who ) ->
  30.     receive
  31.     after T ->
  32.         Who ! { stats}
  33.     end ,
  34.     timer( T , Who ) .
  35.  
  36. % Read lines from a file with a specified delay between lines:
  37. for_each_line_in_file( Name , Proc , Mode , Accum0 ) ->
  38.     { ok, Device } = file:open ( Name , Mode ) ,
  39.     for_each_line( Device , Proc , Accum0 ) .
  40.  
  41. for_each_line( Device , Proc , Accum ) ->
  42.     case io:get_line ( Device , "" ) of
  43.         eof  -> file :close ( Device ) , Accum ;
  44.         Line -> NewAccum = Proc ( Line , Accum ) ,
  45.                     for_each_line( Device , Proc , NewAccum )
  46.     end .
  47.  
  48. loadurls( Filename , Callback , Wait ) ->
  49.     for_each_line_in_file ( Filename ,
  50.         fun( Line , List ) ->
  51.             Callback ( string:strip ( Line , right, $\n) ) ,
  52.             receive
  53.             after Wait ->
  54.                 noop
  55.             end ,
  56.             List
  57.         end ,
  58.         [ read] , [ ] ) .

每个连接我们都要用一个临时的端口,每个端口也是一个文件描述符, 缺省情况下这被限制为1024。为了避免Too many open files问题出现,你需要为你当前shell更改这个限制 ,可以通过修改/etc/security/limits.conf ,但是这需要注销再登陆。目前你只需要用sudo修改当前shell就可以了(假如你不想运行在root状态下,调用ulimit后请su回非权限用户):

udo bash
# ulimit -n 999999
# erl

你也可以把临时端口的范围区间增到最大:
# echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

为压力测试程序生成一个url列表文件
( for i in `seq 1 10000`; do echo "http://localhost:8000/test/$i" ; done ) > /tmp/mochi-urls.txt

现在在erlang提示符下你可以编译调用floodtest.erl 了:
erl> c(floodtest).
erl> floodtest:start("/tmp/mochi-urls.txt", 100).

这将每秒钟建立十个连接 (也就是每个连接100毫秒).

它将以{Active, Closed, Chunks}的形式输出状态信息 ,Active表示已建立连接数, Closed表示因每种原因被终止的连接数,Chunks是mochiweb以块传输模式处理的数据块数。 Closed应该为0,Chunks应该大于Active,因为每个活跃连接接收多个数据块 (10秒一个)。
10,000个活跃连接的mochiweb进程的固定大小是450MB-也就是每个连接45KB。 CPU占用率就好像预想中的一样微乎其微.

总结

第一次尝试是可以理解的。每个连接45KB内存看起来有些高 – 用libevent再做些调整我可以把它做到将近4.5KB每个连接 (只是猜猜, 谁有这方面的经验请留个回复). 如果就代码量和时间效率上对erlang和c做下考量,我想多花点内存还是有情可原的。
后续中,我将建立一个消息路由器 (我们可以把mochiconntest_web.erl中的 25行和41-43行的注释取消 )也探讨一下减少内存用量的方法。我也会分享当100k和1M个连接时的测试结果。