Janus WinForms Controls Suite v2是 Janus Systems 公司开发的一套控件组合,可以通过由100% 的.Net程式码组件来创建出具有Outlook风格的应用程序界面,提供对Visual Studio Developer最新的样式控制。所有的控制将使.NET应用程序更加强大,并易于控制编程,节省开发时间。点这里下载->Janus WinForms Controls Suite v2.0.1000 [分卷压缩包1]
点这里下载->Janus WinForms Controls Suite v2.0.1000 [分卷压缩包2]

posted @ 2011-05-18 13:54 宋辉 阅读(92) 评论(0) 编辑
Andrew B. Cencini
Microsoft Corporation
2002年12月

 

适用于:
    Microsoft® SQL™ Server 2000

摘要:学习如何充分利用 SQL Server 2000 的全文搜索功能。本文包含有关实现最大吞吐率和最佳性能的几点提示和技巧。

目录

简介

使用 Microsoft® SQL™ Server 2000 的全文搜索功能,可以对在非结构化文本数据上生成的索引执行快速、灵活的查询。常用的全文搜索工具是网站的搜索引擎。为了帮助读者理解全文搜索功能的最佳使用方法,本文介绍了大量抽象概念;并对优化全文索引和查询以实现最大吞吐率和最佳性能,提供了几点提示和技巧。

全文搜索功能简介

全文搜索功能在 SQL Server 7.0 中引入。全文搜索的核心引擎建立在 Microsoft Search (MSSearch) 技术上,Microsoft Exchange 和 Microsoft SharePoint™ Portal Server 等产品中也采用了此项技术。

SQL Server 7.0 全文搜索中公开的功能可提供基本的文本搜索功能,并使用早期版本的 MSSearch;而 SQL Server 2000 的全文搜索实现则包含一组可靠的索引和查询功能,并在 SQL Server 7.0 的基础之上添加了几项增强功能。这些增强功能包括:通过 Microsoft 群集服务完全支持群集操作,能够过滤和索引 IMAGE 列中存储的文档,提供改进的语言支持,以及在性能、可缩放性和可靠性方面进行了改进。

MSSearch 生成、维护和查询文件系统中(而不是 SQL Server 中)存储的全文索引。MSSearch 进行全文索引时使用的逻辑和物理存储单元是目录。全文目录在每个数据库中包含一个或多个全文索引 - 可以为 SQL Server 中的每个表创建一个全文索引,且索引中可以包含该表中的一列或多列。每个表只能属于一个目录,且每个表只能创建一个索引。我们将简单介绍有关组织全文目录和索引的最佳方案 - 但首先,让我们来简单了解一下全文搜索的工作原理。

配置全文搜索功能

要为 SQL Server 中存储的文本数据创建全文索引,应该先完成以下几步准备工作。第一步是以全文方式启用包含要生成索引的文本数据的数据库(如果您尚未执行此操作)。

注意:执行以下语句将丢弃并重新创建属于要启用全文搜索的数据库的所有全文目录。除非要重新创建全文目录,否则请确保在要启用的特定数据库中未创建任何全文目录。

如果您是 sysadmin 角色的成员或此数据库的 db_owner,可以继续进行并发出以下语句:

use Northwind 
exec sp_fulltext_database 'enable'

接下来,您需要创建全文目录,以存储全文索引。正如前面所提到的,此目录中的数据存储在文件系统中(而不是 SQL Server 中),因此,在考虑全文目录的存储位置时应该仔细选择。除非指定其他位置,否则全文目录将存储在 FTDATA 目录(位于 Microsoft SQL Server\MSSQL 存储位置中)的子目录中。以下是在非默认位置创建全文目录的方法:

exec sp_fulltext_catalog 'Cat_Desc', 'create', 'f:\ft'

在本例中,全文目录将创建为“f:\ft”的子目录,如果您查看文件系统的该部分,将看到它有了自己的目录。MSSearch 使用的全文目录的命名规则是:

SQL+dbid+catalogID

目录 ID 从 00005 开始,并且每新建一个目录就递增 1。

如果可能的话,最好在其所在的物理驱动器上创建全文目录。如果生成全文索引的进程需要进行大量的 I/O 操作(具体而言,就是从 SQL Server 中读取数据,然后向文件系统写入索引),则应避免使 I/O 子系统成为瓶颈。

那么,全文目录有多大呢?通常情况下,全文目录的系统开销比 SQL Server 中存储的数据(对其进行全文索引)量高出大约 30%;但是,此规则取决于数据中唯一单词(或主键)的分布,以及被您视为是干扰词的单词的分布。干扰词(或终止词)是指要排除在全文索引和查询以外的词语(因为它们不是您感兴趣的搜索词,而且出现频率很高,所以只会使索引变得很大,而不会有实际效果)。稍后,我们将介绍有关干扰词选择方面的注意事项,以及如何优化干扰词以改善查询性能。

如果您尚未执行此操作,请在每个要生成全文索引的表上创建一个唯一的单列非空索引。这个唯一索引用于将表中的每一行映射到 MSSearch 内部使用的一个唯一可压缩主键。接下来,您需要让 MSSearch 知道您要为表创建全文索引。对表发出以下语句可将该表添加到所选的全文目录中(在本例中,它是我们在前面创建的“Cat_Desc”):

exec sp_fulltext_table 'Categories', 'create', 'Cat_Desc',
   'PK_Categories'

下一步是向此全文索引添加列。您可以为每一列选择一种语言,如果该列的类型为 IMAGE,则必须再指定一列,以指示 IMAGE 列的每一行中存储的文档类型。

在列语言选择方面,有一些重要但尚未成文的注意事项。这些注意事项与文本的标记方式以及 MSSearch 对文本的索引方式有关。被索引的文本是通过一个称作单词分隔符(用作单词边界标记)的组件提供的。在英文中,单词分隔符通常是空格或某种形式的标点符号;而在其他语言中(例如德语),单词或字符可以组合在一起;因此,所选的列语言应表示要存储在该列的行中的语言。如果不确定,最好的方法通常是使用中性单词分隔符(只使用空格和标点符号执行标记功能)。选择列语言的另一个好处是“寻根溯源”。全文查询中的寻根溯源是指在特定语言中搜索某一单词的所有变化形式的过程。

选择语言的另一个考虑因素与数据的表示方法有关。对于非 IMAGE 列数据来说,不需要执行特殊的过滤操作;而文本通常需要将单词分隔组件按原样传递。单词分隔符主要用于处理书面文本。因此,如果文本中有任何类型的标记(例如 HTML),则在索引和搜索过程中,语言精确性将不会很高。这种情况下,您有两种选择 - 首选方法是只将文本数据存储在 IMAGE 列中,并指明其文档类型,以便对其进行过滤。如果不选择此方法,则可以考虑使用中性单词分隔符,并且可能的话,在干扰词列表中添加标记数据(例如 HTML 中的“br”)。在指定了中性语言的列中不能进行任何基于语言的寻根溯源,但有些环境可能会要求您选择此方法。

在知道列选项后,通过发出以下语句在全文索引中添加一列或两列:

exec sp_fulltext_column 'Categories', 'Description', 'add'

您可能注意到,此处未指定任何语言 - 这种情况下,将使用默认的全文语言。可以通过系统存储过程“sp_configure”为服务器设置默认全文语言。

将所有列添加到全文索引后,即可执行填充操作。填充方法之多实在是不胜枚举,此处不作详细介绍。在本例中,只需对表启动完全填充,并等待它执行完毕:

exec sp_fulltext_table 'Categories', 'start_full'

您可能希望使用 FULLTEXTCATALOGPROPERTY 或 OBJECTPROPERTY 函数来监视填充状态。要获取目录填充状态,可以执行:

select FULLTEXTCATALOGPROPERTY('Cat_Desc', 'Populatestatus')

通常情况下,如果完全填充正在进行,则返回的结果是“1”。有关如何使用 FULLTEXTCATALOGPROPERTY 和 OBJECTPROPERTY 的详细信息,请参阅 SQL Server Books Online。

全文查询

查询全文索引与执行 SQL Server 中的标准关系型查询略有不同。由于索引是在 SQL Server 外部进行存储和管理的,因此全文查询处理大部分由 MSSearch 完成(因此,那些一部分是关系型、一部分基于全文的查询将被单独处理),这样做有时会损害性能。

从本质上说,执行全文查询时,查询词传递给 MSSearch,后者遍历其内部数据结构(索引),并向 SQL Server 返回主键和排位值。如果执行 CONTAINS 或 FREETEXT 查询,则通常看不到主键或排位值,但如果执行 CONTAINSTABLE 或 FREETEXTTABLE 查询,则将获得这些值,然后这些值通常会与基表合并在一起。与基表合并主键的进程需要很高的系统开销 - 稍后,我们将向您介绍一些巧妙的方法以尽量减少或完全避免这种合并。

如果您通过不断思考,对全文查询如何返回数据有了一个初步了解,就可以推测出 CONTAINS/FREETEXT 查询仅执行 CONTAINSTABLE/FREETEXTTABLE 查询并与基表进行合并。有了这样的了解,您应该避免使用这些类型的查询,除非不这样做的开销更高。在 Web 搜索应用程序中,使用 CONTAINSTABLE 与 FREETEXTTABLE 比使用不带 TABLE 的同类函数好得多。

到现在为止,您已经知道全文查询是用来从 SQL Server 之外存储的 MSSearch 索引中访问数据的特殊方法,还知道如果盲目地与基表进行合并,就会遇到麻烦。应该了解的另外一个重要内容是 CONTAINS 样式查询与 FREETEXT 样式查询之间的本质差别。

CONTAINS 查询用于对所查询的所有词语执行完全匹配查询。无论您只查找单个单词,还是查找以“orange”开头的所有单词,系统只返回包含所有搜索词的结果。因此,CONTAINS 查询速度很快,因为它们通常返回很少的结果,并且不需要执行过多的附加处理。CONTAINS 查询的缺点包括令人生厌的干扰词过滤问题。经验丰富的开发人员以及过去使用过全文搜索的数据库管理员,在试图匹配只包含单个干扰词的单词或词组时,曾遇到过“您的查询只包含干扰词”这样令人吃惊的错误。要避免收到此错误,方法之一是在执行全文查询之前过滤出干扰词。向包含干扰词的 CONTAINS 查询返回结果是不可能的,因为此类查询只返回与整个查询字符串完全匹配的结果。由于干扰词不是全文索引项,因此包含干扰词的 CONTAINS 查询不会返回任何行。

FREETEXT 查询消除了 CONTAINS 查询中偶尔出现的所有警告说明。当发出 FREETEXT 查询时,实际上发出的是词根查询。因此,当您搜索“root beer”时,“root”和“beer”包含其所有形式(寻根溯源与语言相关;所用的语言由生成索引时指定的全文列语言确定,并且在所有查询的列中必须相同),并且系统将返回至少与这些词语之一匹配的所有行。

FREETEXT 查询的负面影响是它们通常比 CONTAINS 查询耗用更多的 CPU - 因为要寻根溯源以及返回更多的结果,就需要包含更复杂的排位计算。不过,基于 FREETEXT 的查询非常灵活,而且速度非常快,是基于 Web 的搜索应用程序中通常使用的最佳选择。

排位和优化

我经常遇到使用全文搜索的用户,他们问我排位编号是什么意思,以及如何将排位编号转换成某种用户可以理解的值。对这个问题,回答可长可短,在这里我将进行简要回答。简单而言,这些排位编号不如结果返回的顺序那样重要。也就是说,当您按照排位对结果进行排序时,总是首先返回关联程度最高的结果。排位值本身常常变化 - 全文搜索使用概率排位算法,即返回的每个文档的关联性受全文索引中的任何或所有其他文档的直接影响。

有些人认为,一种有助于增加某些行排位的技巧是在这些行的全文索引列中重复常用的搜索关键字。尽管在某种程度上,这种方法可能会提高这些行因某些关键字而首先返回的几率,但在其他情况下,可能会适得其反 - 而且还存在使词语查询性能降低的风险。较好的解决方案是为搜索应用程序实现“最佳选择”系统(请参阅以下示例),这样就可以确保首先返回某些文档。多次重复使用关键字会使这些特定关键字的全文索引扩大,并使得 MSSearch 在查找正确行和计算排位时浪费时间。如果全文索引数据量很大,并尝试使用了此方法,您可能会发现某些全文查询很耗时。如果能够实现更细致(也可能更精确)的“最佳选择”系统,您会发现它明显改善了查询性能。

多次重复数据的另一个问题与用于组合关系型查询和全文查询的常用技巧有关。许多使用全文搜索的用户都深受此问题的困扰,每当他们试图将某种过滤器应用于全文查询返回的结果时,便会遇到这样的问题。正如前面所说的,全文查询为每个匹配行返回一个主键和一个排位 - 要收集有关这些行的任何详细信息,必须与它的基表进行合并。由于从无限制的全文查询中可能会返回任意数量的结果,因此合并可能需要大量系统开销。人们发现避免合并的一个有效方法是只在全文索引中添加要过滤的数据(如果可能)。换句话说,如果用户要从报纸上所有文章的正文中搜索关键字“Ichiro”,并且只希望返回该报上体育专栏中的文章,则查询语句通常如下所示:

-- [方法 1:]
-- 开销最高:先全部选择,然后再合并和过滤
SELECT ARTICLES_TBL.Author, ARTICLES_TBL.Body, ARTICLES_TBL.Dateline, 
   FT_TBL.[rank] 
FROM FREETEXTTABLE(Articles, Body, 'Ichiro') AS FT_TBL
INNER JOIN Articles AS ARTICLES_TBL
ON FT_TBL.[key] = ARTICLES_TBL.ArticleID
WHERE ARTICLES_TBL.Category = 'Sports'

-- [方法 2:]
-- 可以使用,但会导致意外结果并变慢,或者会返回不准确的结果: 
-- 执行全文过滤,并且只提取主键和排位
-- (处理在 Web 服务器上完成)
SELECT [key], [rank] 
FROM CONTAINSTABLE(Articles, *, 'FORMSOF(INFLECTIONAL('Ichiro') 
      AND "sports"')

这两个查询要么不必要地占用大量系统开销,要么存在返回错误结果的可能性(在第二个查询中,“sports”很可能出现在所有类型的文章中)。这两项技术还存在其他变体,但这是两种非常简单的模型。如果可行,我通常建议您对数据进行水平划分。即,“类别”列的每个可能值都自成一列(或表),并且与该文章相关的可搜索关键字仅存储在此列中。采用此方法,而不是使用一个“正文”列和一个“类别”列,可以去掉“类别”列,而使用存储可搜索关键字的“Body_<category>”列。如以下示例所示:

-- 如果您可以调整架构,这非常有效 – 每个类别
-- 都成为自己的列(或表格),并且需要命中的
-- 全文索引也较少。这明显需要作一些解释……
SELECT [key], [rank] 
FROM FREETEXTTABLE(Articles, Body_Sports, 'Ichiro')

对于包含大量数据,且这些数据可适应此架构(或许是主架构)更改的系统,其性能会得到显著的提高。但在何时应用多个过滤器或不应用过滤器方面却有着明显的限制。当然,还有其他的方法可以解决这些问题。通过以上示例,您会了解一种将某些搜索条件抽象到架构的方法 - 实际上是“欺骗”优化程序(更确切的说是“成为”优化程序),因为在 SQL Server 本身的全文查询中当前不存在本地优化。

其他性能技巧

人们在聊天时常常问我的另一个问题是如何才能分页显示全文查询结果。换句话说,如果我要发出“root beer”查询,一次在某一 Web 页上显示 40 个结果,并且只希望返回该页面上的 40 个结果(例如,如果我在第三页,我希望仅返回第 81 至第 120 条结果)。

对于分页显示结果,我曾见过多种方法,但没有一种方法能够做到百分之百有效。我所推荐的方法可以最大程度地减少全文查询执行的次数(实际上,对于要分页显示的每个结果集只需执行一次),并将 Web 服务器用作一个简单的缓存。从更高的层面来讲,您只需在全文查询中检索一个完整的主键和排位值行集合(如果需要,可以在架构中使用最佳选择并提取常用过滤器),并将其存储在 Web 服务器的内存中(这取决于您的应用程序和负载,想象将 <32 字节的典型主键大小与 <4 字节的排位大小相加 [等于 <36 字节],然后乘以通常返回的结果集 <1000 行,最后等于 <35K。假定一个在任何给定时间返回 <1000 个活动查询结果集中的一个活动缓存集,您将发现此活动缓存集在 Web 服务器上占用的内存少于 35MB - 这还可以接受)。

为了分页显示结果,该进程只遍历 Web 服务器的内存中存储的数组,并对 SQL Server 发出 SELECT 以便只显示需要显示的行和列。这又回到了全文查询仅返回主键和排位的概念中 - SELECT(甚至许多这样的查询语句)比全文查询的速度快许多倍。使用 SELECT 而不是与基表合并多个行,并结合多个其他策略,您可以保留 SQL Server 计算机上更多的 CPU 周期,并且更有效、更划算地利用 Web 领域。

另一种可以替代 Web 服务器端缓存的方法是在 SQL Server 自身中缓存结果集,并定义多种用于浏览这些结果的方法。虽然本文着重说明 Web 服务器 (ASP) 级别的应用程序设计,但 SQL Server 的可编程功能还为生成高性能的 Web 搜索应用程序提供了强大的框架。

小结

Microsoft SQL Server 2000 的全文搜索功能为索引和查询数据库中存储的非结构化文本数据提供了可靠、快速而灵活的方法。如果要广泛地将这种快速、准确的搜索功能应用于各种应用程序,那么很有必要充分利用其速度和精确性,来实现全文搜索解决方案。通过分布计算负载并通过某些巧妙的方式对数据进行组织,可以省下钱来购买其他硬件和软件,以摆脱因不必要的缓慢查询带来的困扰。在开发优秀的搜索应用程序时,通常要考虑到许多因素和注意事项,希望本文提供的信息和示例对您学习使用 SQL Server 2000 生成出色的 Web 搜索应用程序会有所帮助。

附录 A:实现全文搜索功能的最佳选择

改进全文查询性能和有效性的一种可行方法是实现“最佳选择”系统。此系统是一种很简单的方法,可确保某些与特定查询表达式匹配的行先于其他行返回。最佳选择没有复杂的预编程逻辑(例如,SharePoint Portal Server 就包含这样的逻辑),因此,通常是首选办法。

在本示例中挑选出最佳选择,并将唯一的主键和一些关键字存储在单独的表中。FREETEXTTABLE 查询对(非常小的)最佳选择表执行,并且从该查询中返回的任何结果都与对基表的 FREETEXTTABLE 查询结果一同返回。在给定这些搜索条件下,最先返回的将是所有“最佳选择”行,随后是被 MSSearch 视为关联程度最高的行(以递减顺序返回)。

下面是一个非常简单的用于创建最佳选择系统的示例脚本。

use myDb

create table documentTable(ftkey int not null, document ntext)
create unique index DTftkey_idx on documentTable(ftKey)

/*
   在此插入文档
   (要生成全文索引的所有文档)
*/

-- 为所有文档表创建全文目录和索引
exec sp_fulltext_catalog 'documents_cat', 'create', 'f:\ftCats'
exec sp_fulltext_table 'documentTable', 'create', 'documents_cat', 
      'DTftkey_idx'
exec sp_fulltext_column 'documentTable', 'document', 'add'
exec sp_fulltext_table 'documentTable', 'start_change_tracking'
exec sp_fulltext_table 'documentTable', 'start_background_updateindex'

/*
   现在创建最佳选择表和索引
   (添加应该始终最先返回的文档)
*/
create table bestBets(ftKey int not null, keywords ntext)
create unique index BBftkey_idx on bestBets(ftKey)

/*
   在此插入最佳选择
*/

-- 为最佳选择表创建全文目录和索引
exec sp_fulltext_catalog 'bestBets_cat', 'create', 'f:\ftCats'
exec sp_fulltext_table 'bestBets', 'create', 'bestBets_cat', 'BBftkey_idx'
exec sp_fulltext_column 'bestBets', 'keywords', 'add'
exec sp_fulltext_table 'bestBets', 'start_change_tracking'
exec sp_fulltext_table 'bestBets', 'start_background_updateindex'

首先创建了一个通用的“所有文档”表,用于存储所有要全文索引的文档。通常情况下,文档表中包含其他列,但在本文中,只包含两列 - 主键索引和文档本身。全文目录和索引是为文档表而创建的。

接着创建了“最佳选择”表,用于存储所有全文查询中首先返回的特殊文档。此表只需具有全文主键列和文档本身(对将某些文档作为查询目标的策略进行优化,包括在该文档本身不包含的文档中添加其他关键字)。全文目录和索引是为最佳选择表而创建的。

最佳选择表和文档表可以共享文档(最佳选择文档还存储在常规文档表中,它们共享同一个主键值),也可以相互排斥(最佳选择文档只存储在最佳选择表中)。为便于检索,使最佳选择表与文档表互斥更为容易 - 这样做就无需从最佳选择和返回的普通搜索结果行集合中删除共享操作。另一方面,使用此方法维护文档可能很难实现,因为在此方法中,要在查询中添加逻辑来删除返回的行集合之间的共享文档。

如果给定上面的表,则可以创建两个存储过程,以便对最佳选择表和文档表进行搜索。可使用 Web 服务器级别的逻辑或其他存储过程来缓存和显示所需结果(与最佳选择一起使用时,请参阅下面有关缓存、显示和分页的一个完整、有效的示例)。

首先,创建一个用于检索最佳选择行(如果有)的存储过程:

create procedure BBSearch @searchTerm varchar(1024) as

select [key], [rank] from freetexttable(bestBets, keywords, @searchTerm) order by [rank] desc

确保已对传入搜索字符串进行清理,以避免在服务器上随意执行 T-SQL,并确保用单引号将该字符串括起。这种情况下,使用 FREETEXTTABLE 比使用 CONTAINSTABLE 要好,因为 FREETEXTTABLE 将采用寻根溯源功能,并找到与任何搜索词相匹配的最佳选择。

接下来,第二个存储过程检索与常规搜索标准匹配的文档(如果有):

create procedure FTSearch @searchTerm varchar(1024) as

select [key], [rank] from freetexttable(documentTable, keywords, @searchTerm) order by [rank] desc

此外,请确保已清理传入搜索字符串,并用单引号将该字符串括起。

执行这些存储过程时,应该在两个存储过程中传入相同的搜索词,首先执行最佳选择搜索,然后执行普通全文搜索。下一节更全面地介绍了在构建 Web 搜索应用程序时,如何与其他全文搜索技术一起使用最佳选择。

附录 B:使用最佳选择、结果分页和有效全文查询逻辑的示例应用程序

在本例中,我们实现了一个几乎利用了本文介绍的所有优化方案的 Web 搜索应用程序。我们对联机零售商目录使用简单的搜索引擎方案,并假定在通信量很高的情况下,所有用户都期待在很短的响应时间内获得结果。本示例使用了前一节中的最佳选择表和存储过程。

此应用程序只是一些可用于实现最佳全文搜索性能的高级策略的简单示例。本示例使用了 ASP,也可使用 ISAPI、ASP.NET 或其他平台来实现具有各自优缺点的类似解决方案。会话对象并不一定对所有应用程序都适用,如果使用不当,可能带来一定程度的危险。在本例中,我们使用会话对象来实现快速有效的缓存机制 - 当然还有许多其他方法可以在不同程度上实现该功能。

下面是 ASP 页的通用代码:

<% @Language = "VBScript" %>
<% Response.buffer = true %>
<html>
   <head>
      <title>FT 测试</title></head>
   <body>
<pre>
----------------- 开始测试 ------------------

<%

Dim firstRow   ' 分页显示行时的第一行
Dim lastRow      ' 分页显示行时的最后一行
Dim pageSize   ' 页面大小(每次的行数)
Dim cn      ' 连接对象
Dim rs      ' FT 主键/排位返回的结果集(重复使用)
Dim useCache   ' 使用缓存或命中 FT(0:不使用;1:使用)
Dim alldata      ' 要缓存的结果行集合
Dim bbdata      ' 要缓存的最佳选择行集合
Dim connectionString   ' SQL 连接字符串

' 确定是否要从缓存获取数据
' 默认为否,否则接受传入的数据
if (request.Form("useCache") <> "") then
   useCache = request.Form("useCache")
elseif (request.QueryString("useCache") <> "") then
   useCache = request.QueryString("useCache")
else
   useCache = 0
end if

' 设置常量
pageSize = 24
firstRow = 0
lastRow = 23
connectionString = <在此输入您的连接字符串>

'----------------------------------------------------------------'
' 显示与最佳选择/搜索词匹配的简单主键/排位                       '
'----------------------------------------------------------------'
Private Sub SearchNPage()

   Dim p         ' 循环通过行时的计数器
   Dim numRows      ' 缓冲/结果集中的总行数

   if (useCache <> "1") then ' 获取最佳选择/结果并将其缓存

      Dim queryArg   ' 传入的查询词
      if (request.Form("searchTerm") <> "") then
         queryArg = request.Form("searchTerm")
      elseif (request.QueryString("searchTerm") <> "") then
         queryArg = request.QueryString("searchTerm")
      else
         response.Write("未提供搜索词" & VbCrLF)
         exit sub
      end if      

      ' 理想情况下,应该在此清理查询词...
      ' 添加自定义的清理逻辑,以防止
      ' 随意执行 SQL

      ' 调用 CleanString(queryArg)

      ' 建立与 SQL 的连接
      Set cn = Server.CreateObject("ADODB.Connection")
      cn.Open connectionString

      ' 从传入的干净字符串中获取最佳选择匹配项
      set rs = cn.Execute("exec BBSearch '" & queryArg & "'")

      ' 如果有最佳选择,则获取最佳选择
      if not(rs.EOF) then
         bbData = rs.GetRows
      end if

      ' 现在从传入的干净字符串中获取普通匹配项
      set rs = cn.Execute("exec FTSearch '" & queryArg & "'")

      ' 如果未返回任何结果,则结束
      if (rs.EOF and IsEmpty(bbdata)) then
         response.Write("没有匹配的行" & VbCrLF)
         call ConnClose
         exit sub
      end if

      ' 否则,获取行
      if not(rs.EOF) then
         alldata = rs.GetRows
         Session("results") = alldata
      end if

      call ConnClose

   else ' 从缓存加载 (usecache=1)

      alldata = Session("results")

      ' 在此获取要使用的行范围
      if (request.Form("firstRow") <> "") then
         firstRow = request.Form("firstRow")
         lastRow = firstRow+pageSize
      elseif (request.QueryString("firstRow") <> "") then
         firstRow = request.QueryString("firstRow")
         lastRow = firstRow+pageSize
      end if

   end if ' useCache<>TRUE

   ' 对于本应用程序,只是打印出所有最佳选择
      ' (可能比页面大小大),然后分页显示普通结果
      ' 此处假设:在使用缓存时,如果没有新的最佳选择,
' 则使用以前显示的最佳选择
   if not(IsEmpty(bbdata)) then
      response.Write("最佳选择:" & VbCrLf)
      for p = 0 to ubound(bbdata, 2)
response.Write(bbData(0,p) & " "  & bbData(1,p) & VbCrLf)
      next
      response.Write(VbCrLf)
   end if

   ' 返回搜索结果(可能只有最佳选择)
   if not(IsEmpty(alldata)) then
      if uBound(alldata, 2) < lastRow then
         lastRow = uBound(allData, 2)
      end if

      response.Write("搜索结果:" & VbCrLf)

      for p = firstRow to lastRow
response.Write(allData(0,p) & " "  & allData(1,p) & VbCrLf)
      next
   end if  ' not(IsEmpty(alldata))

End Sub

'----------------------------------------------------------------'
' 关闭并清除连接对象                                             '
'----------------------------------------------------------------'
Private Sub ConnClose
   rs.Close
   Set rs = Nothing
   cn.Close
   Set cn = Nothing
End Sub

call SearchNPage

%>

---------------- 测试结束 ----------------

<form action="<本页>" method="post">
<input type=submit value="next <%=pageSize%> rows" NAME="Submit1">
<input type=hidden name="useCache" value="1">
<input type=hidden name="firstRow" value=<%=lastrow+1%>>
</form>

</pre>
   </body>
</html>

一个简单的 HTML 窗体页面即可像下面一样利用上面的脚本:

<html>
<head><title>输入搜索词</title>
</head>

<body>

<form action="<搜索 ASP 页面>" method="post">
搜索词:<input name="searchTerm">
<p>
<input type="submit" value="Search">
</form>

</body>
</html>

正如以上两个代码示例所示,创建可执行有效全文查询(用最佳选择完成)并缓存和分页显示结果的 Web 应用程序,并不需要花费太多的工夫。只需使用最低的系统开销,即可添加用于提供其他数据、增强最佳选择的外观以及在搜索结果中导航的逻辑(此外,强烈建议您实现其他用于错误处理、安全设置和清理传入数据的严密逻辑)。

通过上面的高级建议和示例,使用 SQL Server 2000 全文搜索设计和实现快速可缩放的 Web 搜索应用程序就是轻而易举的事情了。

附录 C:资源

Full-Text Search Deployment(英文)

是那些初次接触全文搜索的用户的最佳参考。介绍了填充方法及硬件和软件需求,并为使用 SQL Server 2000 全文搜索提供了提示、技巧和其他文档。

全文搜索公共新闻组 (microsoft.public.sqlserver.fulltext)

查找有关全文搜索问题的答案以及有用提示和技巧的理想场所。全文搜索新闻组是 SQL Server 开发小组和博学的 Microsoft MVP 成员经常光顾的场所。

posted @ 2011-02-15 16:50 宋辉 阅读(39) 评论(0) 编辑

IHttpHandler是ASP.NET处理实际操作的接口。在MSDN是这样定义的:使用自定义的HTTP处理程序同步处理HTTP Web请求而实现的协定。(注意:这里写的很清楚是同步HTTP请求如果是异步的话就要使用IHttpAsyncHandler接口程序了)。他包含一个属性IsReusable用于获取当前IHttpHandler实例是否可用一般设置为True.一个方法ProcessRequest(HttpContext context)进行实际的操作过程。

自定义IHttpHandler

使用自定义的IHttpHandler处理程序需要三步

1)定义一个实现了IHttpHandler接口的类。

2)在Web.config文件中注册这个类

3)执行相应的HttpRequst

下面是一个图片防盗链接的实例:

01 public class LinkProtectHandler: IHttpHandler
02     {
03         #region IHttpHandler 成员
04   
05         public bool IsReusable
06         {
07             get { return true; }
08         }
09   
10         public void ProcessRequest(HttpContext context)
11         {
12             //具体执行的操作
13             //获取文件服务器端物理路径
14             string fileName = context.Server.MapPath(context.Request.FilePath);
15             //如果UrlReferrer为空则显示一张默认的禁止盗链的图片
16             if (context.Request.UrlReferrer.Host==null)
17             {
18                 context.Response.ContentType = "image/JPEG";
19                 context.Response.WriteFile("/LinkProtect.jpg");
20             }
21             else
22             {
23                 //如果UrlReferrer中不包含自己站点的主机域名,则显示一张默认的禁止盗链图片
24                 if (context.Request.UrlReferrer.Host.IndexOf("mydomain.com")>0)
25                 {
26                     context.Response.ContentType = "image/JPEG";
27                     context.Response.WriteFile(fileName);
28                 }
29                 else
30                 {
31                     context.Response.ContentType = "image/JPEG";
32                     context.Response.WriteFile("/LinkProtect.jpg");
33                 }
34             }
35         }
36   
37         #endregion
38     }
1 Web.Config里面注册如下
1 <httpHandlers>
2   <remove verb="*" path="*.asmx"/>
3   <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
4   <add verb="*" path="*.jpeg" validate="false" type="WebApplication3.LinkProtectHandler,WebApplication3"/>
5 </httpHandlers>

 

自定义IHttpHandlerFactory

IHttpHandlerFactory在MSDN中是这样定义的:定义类工厂为创建新的IHttpHandler对象而必须实现的协定。他包含2个接口方法,GetHandler():返回实现IHttp接口的类的实例。ReleaseHandler():始工厂可以重复使用现有的处理程序实例。这个接口就是可以把很多实现IHttpHandler接口的方法放在Factory中在Web.config注册时主要注册这个类即可,在使用的时候交由Factory进行判断到底使用哪个IHttpHandler实例。

01 public class CustomerHttpHandlerFactory:IHttpHandlerFactory
02 {
03  
04     #region IHttpHandlerFactory 成员
05  
06     public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
07     {
08         //获取文件服务器物理路径
09         string path = context.Request.PhysicalPath;
10         //获取文件名后缀名
11         string exstention = Path.GetExtension(path);
12         //进行判断,交由相应的处理程序
13         if (exstention==".rss")
14         {
15             return new RssHandler();
16         }
17         else if (exstention==".atmo")
18         {
19             return new ATMOHandler();
20         }
21         else
22         {
23             return null;
24         }
25     }
26  
27     public void ReleaseHandler(IHttpHandler handler)
28     {
29           
30     }
31  
32     #endregion
33 }

 

在Web.Config里面进行如下定义:

1 <httpHandlers>
2   <remove verb="*" path="*.asmx"/>
3   <add verb="*" path="*.rss,*.atom" validate="false" type="WebApplication3.CustomerHttpHandlerFactory,WebApplication3"/>
4 </httpHandlers>

 

IHttpAsyncHandler异步接口

IHttpAsyncHandler在MSDN中是这样描述的:定义 HTTP 异步处理程序对象必须实现的协定。他包含3个方法:

BeginProcessRequest:启动对 HTTP 处理程序的异步调用,EndProcessRequest:进程结束时提供异步处理 End 方法。ProcessRequest:通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。 (继承自 IHttpHandler。)和一个IsReusable属性。

posted @ 2011-01-17 15:18 宋辉 阅读(32) 评论(0) 编辑
中国制造交易网
FMS 入门教程(转载:http://hi.baidu.com/jamesxiang/blog/item/9bba4d381897772097ddd81a.html)

我把我以前的那些FMS学习笔记整理了一下,做了下修改,成了一页的FMS教程,方便想学FMS的新手来看,如果还有错误或者看不懂的地方欢迎留言,或email偶,偶会进行更改

什么是Flash Media Server ?

Flash大家庭里的一员,这个东东以前叫Flash Communication Server,传说中的FCS就是这个,现在改叫FMS了...
不见不知道哦,一见真可怕,adobe还有这么cool的东东,自从见了她,让我做些小东东的兴趣全没了,一心想研究这个

这东东能做什么?

视频录制啊,flash聊天室啊,在线视频会议啊啊, 网络游戏?

不管怎样先安个试试吧

先到这里下载免费的开发者版本吧
http://www.macromedia.com/software/flashmediaserver/
然后安装,安装时记得用户名和密码不要瞎填自己要记住,如果你已经瞎填了那就到 安装目录下\conf\fms.ini 里找吧

偶装到了c盘,找到目录
C:\Program Files\Macromedia\Flash Media Server 2

attachments/200708/29_145830_1.jpg


先要打开服务器哦

你在本机装的fms,本机就是服务器了哦!记得现在你的机器既是客户端又是服务端哦
开始===程序===Macromedia===Flash Media Server 2
有两个start****,都要打开,关时候两个都要关,如果你怕麻烦直接找tool文件夹里的批处理文件StartServerService.bat吧,双击他,他会帮你搞定地,想关就找StopServerService.bat。~

如果想设置默认开机时候fms自动打开还是需要手动打开,可以在 开始菜单里--运行--输入services.msc ,就会打开服务,找到Flash Media Server项,设置为手动或者自动,这里也可以随时关闭服务或打开服务

applications文件夹

你可以在里边建一个文件夹例如叫 FirstApp,这就建了一个Application,以后所有此项目服务器端的flv文件啊,共享文件啊都会在这里边,有时服务器端需要写程序的时候里边会有个main.asc 文件,这个就是服务器端程序,服务器端程序目前只支持as1写,是在服务器上执行的,你也可以用trace调试,怎么trace管理服务器时候你会看到,你可能会建若干个app,在一台服务器上同时运行,比如录象的app,聊天室的app。。。这些所有的app都通过fms自带的fms2_console.swf管理,现在就去看看如何管理服务器吧!

管理服务器~

服务器打开后就要管理了哦,点开fms2_console.swf,别看他只是个swf,后台管理就靠他了,输入你安装时候的密码和用户,服务器地址写 localhost 就好看到了吧,熟悉熟悉他吧

attachments/200708/29_145946_2.jpg


以后我们写程序最常用的就是这个View Applications ,每当有客户端连接服务器时,左边就会列出连接的是哪个app,有多少连接 下图为连接applications文件夹下的bs文件夹(bs文件夹,因为每个文件夹对应一个app,就是bs app),连接数为1,458是实例名(实例名默认为 _definst_ ,每个文件夹可以有若干实例,互相不影响,这个特性可以用来做聊天是的房间,以后再说)

attachments/200708/29_150034_3.jpg


选中某个app后,或者客户端有连接,会看到当前打开的app的状态,这个

attachments/200708/29_150053_4.jpg


Live Log 服务器端的trace就这里看了 ,右边依次是客户端情况 , 共享对象,流,执行的情况(占内存,cpu等),后边两个小按钮,调试时候常用哦,reload和unload!
每当服务器端main.asc修改后一定记得reload或者unload一下,否则不会生效,unload会把窗口关闭,有客户端连的时候还会自动打开
总有人告诉我,他的fms经常会出现诡异现象,比如连接不上,代码已经删了还会执行,一生气连文件夹都删了,还会执行,怀疑是自己的rp有问题。。
那不是rp问题,记住出现问题就reload !实在不行就去tools文件夹点StopServerService.bat

conf文件夹

还有重要的是conf文件夹了,里边是一些服务器端的配置文件,以后可能会用,先不用动。。。。

自此,安装部分结束,go on...

有一种协议叫rtmp

客户端和服务器端通信是使用协议rtmp的

现在在服务器端applications文件夹(当然偶的客户端和服务器端是一台机器了)里建个test1文件夹,你的地址就为

rtmp:/test1 或者 rtmp://localhost/test1

注意两个地址中的 "/ "符号

打开flash

与服务器通信首先要建个NetConnection()
nc = new NetConnection();

连接

nc.connect("rtmp://localhost/test1");

怎么知道连没连上呢?顺利连接服务器后会触发一个onStatus事件,自己trace一下info.code

nc.onStatus = function(info) {
//trace(info)
//trace(info.code)
for (i in info) {
trace(i+": "+info[i]);
}
};

完整代码:
nc = new NetConnection();
nc.onStatus = function(info) {
trace(info.code);
if (info.code == "NetConnection.Connect.Success") {
trace("接通");
}
};
nc.connect("rtmp://localhost/test1");

注意:默认情况下服务器是允许你连接的,但只是默认,如果服务器拒绝你连接的话,上述代码就不好用了。怎么回事?我们看看连接的过程吧

连接过程

每当客户端试图连接服务器,一个NetConnection.connect(),服务器将会调用application.onConnect 来鉴定是不是允许客户端连接,onConnect()方法返回null 或不返回则将进入未决状态,直到onConnect方法中返回true或执行acceptConnection(client)则允许,返回false或执行rejectConnection(client)则拒绝,如图(从左往右看)

attachments/200708/29_150312_5.jpg


服务器文件是以.asc形式存在的,可以在test1文件夹里建一个main.asc
application.onConnect=function(client){
this.rejectConnection(client);

//this.acceptConnection(client)

}

这样就拒绝连接了,动手试试,别忘了,服务器端改动的话,别忘了到fms2_console.swf去reload !不知道按哪个的到上边找,每当有客户端连接,那个reload按钮的界面就会出来哦。

检查是否uri错误

如果你的rtmp地址是从其他什么地方传过来的,可以顺便检查一下rtmp是否错误,下边代码如果地址是错误的就会trace出来
mync = new NetConnection();
mync.onStatus = function(info) {
if (info.code == "NetConnection.Connect.Success") {
trace("连接成功");
}
};
//正确的uri
//uri = "rtmp://localhost/connect";
//错误的uri
uri = "rtmpppppp://localhost/connect";
if (mync.connect(uri, "N神")) {
trace("尝试连接服务器中。。");
} else {
trace("没有尝试连接服务器~是uri错误???");
}

ok 了,现在我们深入一点点。。看看连接上的一些细节问题

info.code:

连接后info.code会告诉你连接的状态,上边看的都是NetConnection.Connect.Success, 还有一些其他值,和这些值是什么意思,自己看看。

值得注意的是。NetConnection.Connect.Rejected,收到这条消息的时候说明服务器端拒绝了你,接着马上你会收到另一条,NetConnection.Connect.Closed,连接就关闭了~~~
mync = new NetConnection();
mync.onStatus = function(info) {
switch (info.code) {
case "NetConnection.Connect.Success" :
trace("连接成功");
break;
case "NetConnection.Connect.Failed" :
//关掉服务器的情况
trace("尝试连接失败,服务器有可能挂掉了 -_-b");
break;
case "NetConnection.Connect.Rejected" :
//注意这里,服务器拒绝你的情况,如果遭到拒绝,将会调用两次mync.onStatus,
//第一次"NetConnection.Connect.Rejected"第2次"NetConnection.Connect.Closed"
trace("遭到服务器拒绝");
trace("服务器返回信息:"+info.application.msg);
break;
case "NetConnection.Connect.Closed" :
trace("连接关闭");
break;
}
};
mync.connect("rtmp://localhost/connect", "N神");
//mync.connect("rtmp://localhost/connect","小新")

服务器端拒绝连接?好象见过。。。回头找找。。。。。。。。哦在这里
application.onConnect = function(client) {

this.rejectConnection(client);

}

我不能所有人都拒绝了。。我要把讨厌的人拒绝了。。。
传给服务器一个人名~~
mync.connect("rtmp://localhost/connect", "N神");

服务器看看是不是讨厌的人。。
application.onConnect = function(client, name) {
trace(name);
if (name == "N神") {
//拒绝连接,并返回个错误对象{msg:"服务器不想"+name+"进去,哈哈~"},包含错误消息
application.rejectConnection(client, {msg:"服务器不想"+name+"进去,哈哈~"});
} else {
application.acceptConnection(client);
//成功不能返回客户端信息
}
};

看客户端的代码。。

case "NetConnection.Connect.Rejected" :
//注意这里,服务器拒绝你的情况,如果遭到拒绝,将会调用两次mync.onStatus,
//第一次"NetConnection.Connect.Rejected"第2次"NetConnection.Connect.Closed"
trace("遭到服务器拒绝");
trace("服务器返回信息:"+info.application.msg);
break;
遭到服务器拒绝后会trace出服务器返回的错误消息,这是一个最基础的与服务器交互的例子,以后还会有很多滴

下边进入新一层次。。。

视频,流

这个比较重要,但超简单,网上播放电影,在线录制,在线播放,视频会议,视频电话,全靠他了 ,下边我们先做一个最简单 录制和播放

录制视频

打开flash,新建一个fla,Ctrl + L 打开library,右键新建视频,确定。

拉到舞台上起个名叫my_video

第一帧开始加代码

//从麦和设像头显示视频显示在my_video上

my_video.attachVideo(Camera.get());
my_video.attachAudio(Microphone.get());

//像以前一样连接

nc = new NetConnection();
nc.connect("rtmp://localhost/aaaa"); //注意这里,Applications文件夹里要有aaaa文件夹哦!

//可以理解为在nc连接上绑一个流

nsOut = new NetStream(nc);

//在流上加麦克风和视频头

nsOut.attachVideo(Camera.get());
nsOut.attachAudio(Microphone.get());

//发布 2.flv
nsOut.publish("2", "record");

第一个参数是文件名,后一个参数要"record"才是录制


把fla发布一下, 录一会儿,把视频关掉,打开你的

叉盘:\Program Files\Macromedia\Flash Media Server 2\applications\aaaa\

是不是多了个streams\_definst_

打开C:\Program Files\Macromedia\Flash Media Server 2\applications\aaaa\streams\_definst_

看见2.flv了吧。。

这里下载原文件 : http://www.nshen.net/blog/attachments/200601/25_163617_c1.fla

播放flv

用fms播放的flv目前是无法下载的,这可以保护你的版权 :)

打开flash,新建一个fla ,Ctrl + L 打开library,右键新建视频,确定。

拉到舞台上,这回起个名叫view ,我们来播放你刚才录的那个视频

nc = new NetConnection();
nc.connect("rtmp://localhost/aaaa");
res = new NetStream(nc);

//view元件要加载res流

view.attachVideo(res);
view.attachAudio(res);

//播放

res.play("2");

原文件 : http://www.nshen.net/blog/attachments/200602/13_223604_p1.fla

现场流

上边做的都是先录制好了视频,然后才播放,网上的实时视频会议,视频电话是怎么做的呢?总不能先录好再播放吧?
这要用到现场流,现场流是指你连接到服务器后,你在发布的同时,其他人就可以实时的看到你
很难吗?看看吧,把上边录制视频的例子拿下来
nsOut.publish("2", "record"); 这句改成 nsOut.publish("2", "live");
把"record" 改成"live"后,就不会生成flv了,取而代之的是一个看不到的实时的视频流
ok 了,发布,这就是直播端了,同时再发布上边那个播放端,已经可以实时看见你了吧。现在你是用本机测试,等你有了服务器。其他人也能同时看见你了哦

到这里你已经可以自己做一个网页上的直播了,发布端不要让别人看到,让别人看你的播放端就好了 :) 至于为什么要用两个swf,因为目前为止你还不知道怎么样跟服务器之间传递消息,这样做可以避免这些东西,等你把后边的东西全都学完就可以在一个swf里,写个视频会议之类的东东了

远程共享

远程共享?

共享的概念就是让每个连接到服务器的swf都能实时的得到服务器端共享的数据。
一个人更改了这些数据,其他人都会看得到通知。可以想象聊天室里的发言,一个人发了以后其他人都可以看到。

attachments/200708/29_151512_6.jpg


共享对象

共享对象,说英文大概你比较熟ha~ SharedObject, 恩flash中有两种sharedObject,local sharedobject (LSO) 和 remote sharedobject (RSO),也就是本地共享和远程共享,偶们讨论远程的,不过之前你最好先去了解了解本地的,对你有好处...

RSO在服务器端是以文件形式存储的,扩展名是.fso,为什么不是.rso?....我也想问呢- -b

代码

初始化RSO需要先与服务器建立一个连接,续上节 ,我们先与服务器建立一个连接

//初始化远程共享要利用nc通道
var myNC = new NetConnection();
myNC.onStatus = function(info) {
if (info.code == "NetConnection.Connect.Success") {
//成功则利用此nc初始化rso
initRSO(this);
}
};

跟以前的代码一样,只是连接成功后多了一个initRSO()函数,看不懂的回去重头再看一下。。。

下边是initRSO了,跟连接结构差不多
function initRSO(NC) {
//在服务器上建立myRSO.fso文件,第2个参数指定nc通道,第3个指定文件在服务器上持久保留,即使服务器重启了,还是有
my_rso = SharedObject.getRemote("myRSO", NC.uri, true);
my_rso.onSync = function(list) {
//list 是一个对象数组 ,类似这种[{name:"x",code:"success"},{name:"y",code:"success"}] ,下边会详细讲
//初始成功
};
my_rso.connect(NC); //连接
}

了解了吧,看一个完整的例子

画一个mc起名叫mc,在第一帧上写代码,
//初始化远程共享要利用nc通道
var myNC = new NetConnection();
myNC.onStatus = function(info) {
if (info.code == "NetConnection.Connect.Success") {
//成功则利用此nc初始化rso
initRSO(this);
}
};
myNC.connect("rtmp://localhost/test1"); //不会不知道要建test1文件夹吧

function initRSO(NC) {
my_rso = SharedObject.getRemote("myRSO", NC.uri, true);
//onSync是回调函数,每次服务器端so数据有改变,这里都会有反映!这里的意思每当有人按鼠标,这里都会有反映,我们读取so的data下的值就可以了
my_rso.onSync = function() {
mc._x=this.data.x
mc._y=this.data.y
};
my_rso.connect(NC);
}

onMouseDown = function () {
//改变so的数据
my_rso.data.x = _root._xmouse
my_rso.data.y = _root._ymouse
};

然后发布设置中设置只允许网络,发布看看

现在你可以开多个播放器窗口,点其中一个,看看其他的窗口变不变

源文件: http://www.nshen.net/blog/attachments/200601/11_145320_test3.fla

连接流程

attachments/200708/29_151814_7.jpg


再写一个,看起来很像在做网游~
mync = new NetConnection();
mync.onStatus = function(info) {
if (info.code == "NetConnection.Connect.Success") {
initRSO();
}
if (info.code == "NetConnection.Connect.Closed") {
trace("关闭");
}
};

function initRSO() {
my_RSO = SharedObject.getRemote("myRSO", mync.uri, true);
trace(my_RSO);
my_RSO.onSync = function() {
mc._x = this.data.hero.x;
};
my_RSO.connect(mync);
}
mync.connect("rtmp:/my_app/test1");

mc.onEnterFrame = function() {
my_RSO.data.hero.x = this._x;
if (Key.isDown(Key.LEFT)) {
this._x -= 5;
}
if (Key.isDown(Key.RIGHT)) {
this._x += 5;
}
};

写个简单的聊天室

很简单的东西,基本上就是共享对象的运用,没有用到服务器端,大型聊天室可能不会这么做,这个只适用于初学者 :/

注释很详细,不说多了,可以直接下载原文件

http://www.nshen.net/blog/attachments/200602/smallchat.fla

代码:
//用户名
myname="游客"
//建立连接
var myNC = new NetConnection();
myNC.connect("rtmp://localhost/smallchat");

//搞到rso
Talk_SO = SharedObject.getRemote("Talk", myNC.uri, false);
Talk_SO.onSync = function() {
//先把聊天文本框清空
remoteText.text = "";
//把聊天列表显示出来,talklist的格式就是[谁谁说:啊啊啊,谁谁谁说:2222]
var t = this.data.talklist;
for (var i = 0; i<t.length; i++) {
writeln(t[i]);
}
};
Talk_SO.connect(myNC);

//发消息函数
function post() {
//如果不存在talklist就建一个,这里没用server端,是个技巧
if (Talk_SO.data.talklist[0] == undefined) {
Talk_SO.data.talklist = [];
}
//限制数组长度,是个队列。保证里边有5条消息,当然也可以更多,但如果没有限制,flash会垮的
if (Talk_SO.data.talklist.length>=5) {
Talk_SO.data.talklist.shift();
}
//把消息装到so里
Talk_SO.data.talklist.push(myname+"说:"+meText.text);
meText.text = "";
}
//文字显示,换行
function writeln(msg) {
remoteText.text += msg+"\n";
remoteText.vPosition =remoteText.maxVPosition
}
//-----------------------------------------------
Btn.onRelease = function() {
post();
};
this.onKeyDown = function() {
if (Key.isDown(Key.ENTER)) {
post();
}
};
Key.addListener(this);

深入onSync

onSync有个list参数,这个开始有些难度了。不想动脑的可以跳过没影响,只是以后写出的程序效率会低点 :(

看代码:
my_rso = SharedObject.getRemote("myRSO", NC.uri, true);
my_rso.onSync = function(list) {//.......};
my_rso.connect(NC); //连接

在onSync回调中我们可以知道我们的my_rso被改变了,但my_rso里具体什么改变了呢? 我们就要分析这个 list 参数 了

list参数其实是一个对象数组 ,首先它是一个数组,里边装了很多对象(Object),每一个对象都包括了SharedObject中一个插槽(slot)的改动信息。我暂时给他起名叫插槽信息对象。。。这名字太猥亵了。。但我就这么叫了。。

插槽信息对象包含两个属性,name 和 code,偶尔还会有个oldValue?我不太常用,不说它

name 描述被改变的属性名

code 描述该属性的改变方式 ,有可能为以下几种值:"success" , "change" , "delete" , "reject" , "clear" ,具体含义后边说

说白了这个插槽信息对象大概就是这么个样子:

{name:"x",code:"success"}

表示x属性被修改成功

要得到这些插槽信息对象就要for in 这个list参数

for (var i in list) {

list[i] 就是插槽信息对象

}

要分析具体so哪改变了,就是分析list[i],比如

if(list[i].code=="change") trace("list[i].name"+被+"change了")

if(list[i].code=="delete") trace("list[i].name"+被+"delete")

“change”是啥?“delete”是啥?

"success" , "change" , "delete" , "reject" , "clear" 具体含义:

success : 表示当前影片修改so的插槽获得了成功

change : 表示so的插槽被别人修改,或填加

也就是说,你修改so的某个属性成功了会收到 "success" ,与此同时其他影片会收到 "change"

reject : 拒绝修改

例如发生在两个或多个客户端同时要修改一个so的插槽,这时候fms会只让一个client修改,并返回"success" 其他的会收到"reject"

delete , clear : 这个好理解,一个是删除,一个是清空,看例子:

比如服务器端删除某个so
so = SharedObject.get("某个so");
so.lock( );
var names = so.getPropertyNames( );
for (i in names) {
so.setProperty(names[i], null);
}
so.unlock( );

这样client端会收到 若干个插槽信息对象,所有的code都为"delete",表示若干个item被删除

然而这样:
so = SharedObject.get("某个so");
so.clear( );

client端就只会收到一个插槽信息对象,code属性为“clear”。

client端与server端直接交互

看完了SO,看一下client与server端如何直接进行交互的

原文件在这里:
http://www.nshen.net/blog/attachments/200602/25_152648_csc.rar

1. 客户端呼叫服务器

fla:
//客户端呼叫server端msgfromclient函数,并将返回值trace出来
mync = new NetConnection();
mync.connect("rtmp://localhost/connect");
//返回值接收对象
var resObj = new Object();
resObj.onResult = function(val):Void {
trace("val"+val);
};
/*
我们用mync去call服务器端的msgfromclient函数,resObj是返回接收对象,当服务器有返回值后,会自动直接调用这个对象的onResult处理函数,后边可以传递给server无数个参数,这里只传一个字符串
*/
mync.call("msgfromclient", resObj, "第一个call");



服务器端代码是放在main.asc里的,你可以到你的application下的connect目录下建一个main.asc,写代码

main.asc:
//要把函数定义到Client上!!
application.onConnect = function(client) {
/* 在这里定义也可以,在Client.prototype里定义也是可以的
client.msgfromclient=function(what){
trace(what+"进来了")

var aa="呼叫成功并返回结果"
return aa
}
*/
application.acceptConnection(client);
};

Client.prototype.msgfromclient=function(what){
trace(what+"进来了")

var aa="呼叫成功并返回结果"
return aa

}


现在去试一下吧。。。成功了的话,再继续.......

2. 服务器端呼叫指定的客户端

fla:
//server呼叫client端
//要把函数定义到nc上!!
//
mync = new NetConnection();
mync.onStatus = function(info) {
if (info.code == "NetConnection.Connect.Success") {
trace("连接成功");
}
};
mync.connect("rtmp://localhost/connect");
mync.msgfromserver = function(msg) {
trace(msg);
};

main.asc:
application.onConnect = function(client) {

application.acceptConnection(client);
//这里呼叫刚连线成功的客户
//跟client呼叫server基本一样,服务器一般很少让client端返回值所以第2个参数设为null
client.call("msgfromserver",null,"服务器叫你啊")
};

3. 服务器端呼叫所有的客户端(广播)

有些时候需要服务器广播数据给所有连接上的客户端,这里就用到了广播的概念

广播其实SharedObject的时候已经讲过了一种实现,就是把数据放到remote SharedObject中,当数据改变了,自然所有客户端都会onSync
这里再讲一种用call来实现的:

下边是很常见的一个情况,当某人下线了的时候要通知所有客户端,某某已经下线了

server端:

application.onDisconnect=function(newClient){
//遍历客户端列表,分别call他们
for(var i=0;i<application.clients.length;i++) {
application.clients[i].call("client_fun",null,sendvar);
}

}

Client端:

nc.client_fun=function(myvar){....... }

这个自己完善一下吧,这里就不贴fla了

还有:

关于广播,不只有服务器端广播给所有客户,还有可能某一个客户端对所有客户端直接进行广播,当然上边的例子你如果都看懂了的话,你已经可以自己做某一个客户端对所有客户端的广播了。怎么做?

第1步 某一个客户端呼叫服务器
第2步 服务器广播给所有客户端

这样就形成了,某客户端对所有客户端的广播,当然如果你能细心的耐心的看看帮助的话,你会发现Shared Object 和 NetStream都有send方法就是做这件事的,而且更为简洁,服务端不用写代码 :)

好了,看到这里,fms常用的大部分概念都说到了,这个教程也就基本结束了,但请注意,现在你只是入门阶段.想学更多的,你可能需要多翻翻手册,多找找教程,英文有不少很好的教程进阶.

其他需要注意的问题:

* 中文编码:

有些时候我们用flash去读取外部的php,asp.....文件里的中文显示在flash里会出现乱码的情况,为了解决在flash里显示中文很多教程里通常都直接加了一句System.useCodepage=true
问题就在这,显示不了外部中文是因为flash内默认用Unicode编码,外部的文件大多都是gb2312,加上这句System.useCodepage=true代表强制flash使用系统默认的gb2312,这样flash就显示正确了,但fms服务器端默认也是用unicode的,这样客户端跟服务器端不同编码有时就会出错了,搜了一下server字典好象没有System.useCodepage=true了。。。所以解决办法就是去掉System.useCodepage=true,在外部php或asp中把编码转成utf-8,至于怎么转,不知道,问你的asp或php程序员吧 ,另外不要用记事本编辑你的asc文件。。即使编辑最后要一定另存为utf-8格式。

* 判断影片播放结束
ns.onStatus=function(info){
if(info.code=="NetStream.Play.Stop")trace("结束")
}

乍看好象没错,但是如果设置了缓冲以后(setBufferTime)就不好用了,仔细研究了一下原因4这样的

监视onStatus(info) ,info.code:

开始播放

NetStream.Play.Start (其实还没播放)

然后缓冲(根据setBufferTime设置的秒数缓。。)

NetStream.Buffer.Full (缓冲装满了,这才开始播放)

然后播放完了

NetStream.Play.Stop (其实还没播放完)

注意了,然后还要播放缓冲 - -b

NetStream.Buffer.Empty (缓冲空了,这才播放完了。。)

群里的kinglong兄比较聪明~,先Stop的时候做个记号,然后再满足Empty才算播放完,也就是两个条件,因为网速慢也会Empty。。。好办法
但我看了一下帮助,好象有一个专门的事件通知播放结束
ns.onPlayStatus=function(info){
if(info.code=="NetStream.Play.Complete")trace("感谢观看帮助")
}


* 防火墙,端口

默认安装的话默认端口是1935,管理是1111端口,记得防火墙要把1935和1111端口打开。
FLASH MEDIA SERVER 2

2005年10月17日,香港――Macromedia公司(Nasdaq: MACR)今天发布Macromedia Flash Media Server 2,这款服务器软件成功组合了传统流媒体功能和灵活的开发环境,用于创建和交付创新的互动式媒体应用。作为最近发布的Macromedia Flash Professional 8和Flash Player 8的补充,Flash Media Server 2使Flash Platform成为用于跨各种浏览器和操作系统创建、提供互动式富媒体应用的领先的解决方案。目前,领先的媒体、娱乐、电信、零售和广告等行业用户迅速采用Macromedia Flash Video,用于为全世界数百万浏览者提供定制的、无缝的视频体验。

Flash Platform提供了一流的网络视频体验。如今,绝大部分的网络视频需要利用一大批眼花缭乱的可选件和下载件才能展示,如果把最新发布的Flash Media Server 2与无所不在的Flash Player相结合,就能跨众多平台、各种连接速率和浏览器,可靠地提供流畅、即时的视频体验。发布者也可以把视频集成到自己的网站和应用当中,而且可以完全控制回放、互动和品牌建立,获得无缝体验。

Macromedia高级副总裁、总经理Tom Hale说:“Flash Platform是向互联网上最广泛的受众提供网络视频和丰富媒体应用的最佳方式,就集成到网络体验当中的流视频而言,目前还没有比Flash Media Server 2更好的选择。”

Flash Media Server 2是在大规模部署环境下提供录制和实况Flash Video的基础,如视频点播、实况网络广播、MP3媒体流、视频博客和视频/音频聊天等应用。新版本引入了可扩展的新款Edge-Origin服务器,这个企业级就绪的可选架构简化了负载均衡、故障替换和集群工作,从而能够确保流式传输富交互体验具有更高的可靠性和扩展性,即使是在流量高峰期间。Flash Media Server 2拥有独特的可编程流媒体环境,包括性能可靠的应用编程接口(API),对媒体流、资产和特性(如定制互动、用户验证和带宽检测)实现精确的代码控制,从而提供真正不同的用户体验。Flash Media Server 2视频体验充分利用了Flash Player 8中新的高质量视频编解码器,并具有自动检测客户端的带宽连接、相应调整媒体流的功能。

Scripps Networks公司的Flash开发人员Tim O'Hare说:“在HGTVPro,我们的主要挑战是,开发出生动而丰富的宽带视频应用,来满足最终用户和广告客户不断提高的要求。通过使用Macromedia Flash Media Server 2和Macromedia Flash Professional,我们获得了前所未有的能力,来创建并向我们的广大受众展示了HGTVPro一系列的互动内容。现在,Flash Media Server 2提供了Flash Video流式传输方面的增强功能新选项,让我们可以部署更广泛的内容,更重要的是,这些内容可以通过多种平台来浏览。凭借极具互动性、创新性的控制功能,Flash Video提供了其他任何媒体播放器根本不可能提供的网上内容。”

Flash Media Server可以在标准的Windows或Linux服务器上运行,可以与现有的数据库、万维网和数据服务集成。进一步拓展的扩展功能使用户能够管理基于XML的媒体元数据、访问本地系统资源、控制日志和报告,以便与现有的媒体吸收和管理系统更紧密地进行集成。



FMS下载与配置 (转载http://www.aosea.com/showtopic-8029.aspx)
软件下载地址:

http://www.flashcom.com.cn/tools/FlashMediaServer2.rar
flashmediaserver2(FMS2)下载+无限制序列号 (英文版,无限制版 PS:ADOBE没有出到中文版)

点击下载就可以了.........................

Flash Media Server安装说明:解压压缩包后直接运行FlashMediaServer2.exe一直NEXT直到出现



然后输入帐号密码,此帐号密码为FMS远程管理的帐号密码,(管理程序在安装目录里的fms2_console.swf),NEXT,到提示输入端口号,第一个为1935为应用程序服务器使用的端口号,第二个1111为远程管理FMS服务器使用的端口号,这两个端口号需要在防火墙之类的拦截软件中打开这些端口否则服务将不能正常运行。安装结束后提示是否运行该软件:

我们这里不给服务器运行先,把所有的钩全部去掉确定后FINISH,然后到软件安装包目录里找到license.lic这个序列号文件拷贝到软件安装目录C:\Program Files\Macromedia\Flash Media Server 2\licenses的文件夹下,然后到C:\Program Files\Macromedia\Flash Media Server 2\tools下运行StartServerService.bat文件启动FMS服务器,(StopServerService.bat]为停止服务)

PS:到这里我们的安装就完全结束了,大家只要按照上面的步骤来安装的都可以成功,需要注意点的就是有部分杀毒软件可能会屏蔽或者拦截FMS用的1935端口,所以大家记得一定要开启这些端口,同时并保证不被拦截,有些朋友用命令查看1935端口是开启状态,但是就是连接不了原因就可能是被拦截了.......安装不成功的朋友不用怀疑是FMS安装包的问题,这个安装包我用了半年了没有任何问题,测试过环境WIN XP WIN 2003全部没有问题. PS:转贴的请保留原文地址!!!!!
今天到此....
posted @ 2010-12-06 11:21 宋辉 阅读(188) 评论(0) 编辑

QR二维条码简介 QR码- 维基百科

C# 创建生成实例:http://www.codeproject.com/KB/cs/qrcode.aspx

二维条码创建读取程序:http://files.cnblogs.com/zjfree/PsqrEdit_Zh.rar

二维条码在线生成:http://zxing.appspot.com/generator/

在线解析二维条码:http://zxing.org/w/decode.jspx

Google 二维条码 API

官方文档:http://code.google.com/intl/zh-CN/apis/chart/docs/gallery/qr_codes.html

宽度:
高度:
内容:
编码:
容错:

图片地址:
posted @ 2010-12-02 14:51 宋辉 阅读(77) 评论(0) 编辑