[转载]当网站遭遇DDOS攻击的解决方案及展望

mikel阅读(746)

[转载]当网站遭遇DDOS攻击的解决方案及展望 – 李晨光 – 博客园.

一、事件发生
春节长假刚过完,WEB就出现故障,下午1点吃完回来,立即将桌面解锁并习惯性的检查了Web服务器。通过Web服务器性能监视软件图像显示的向下滑行的红色曲线看到WEB出现问题了。
根据上述的问题,我马上开始核查Web服务器的日志,试 试是否能检测到问题究竟什么时候开始,或者发现一些关于引起中断的线索。正当查询线索过程中。公司首席运营官(CIO)告诉我,他已经接到客户的投诉电 话,报告说无法访问他们的网站。于是从台式机中敲入网站地址,试着从台式电脑访问他们的网站,但是看到的只是无法显示此页面的消息。
回想前几天也未对Web服务器做了任何改变也未对Web 服务器做过任何改变,服务器曾经出现过的性能问题。在Web服务器的日志文件中没有发现任何可疑之处,因此接下来我去仔细查看防火墙日志,和路由器日志。 仔细查看了防火墙日志,打印出了那台服务器出问题时的记录。并过滤掉正常的流量并保留下可疑的记录。表中显示了打印出来的结果。
源IP地址
目的IP地址
源端口号
目的端口号
协议
172.16.45.2
192.168.0.175
7843
7
17
10.166.166.166
192.168.0.175
19
7
17
10.168.45.3
192.168.0.175
34511
7
17
10.166.166.166
192.168.0.175
19
7
17
192.168.89.111
192.168.0.175
1783
7
17
10.166.166.166
192.168.0.175
19
7
17
10.231.76.8
192.168.0.175
29589
7
17
192.168.15.12
192.168.0.175
17330
7
17
10.166.166.166
192.168.0.175
19
7
17
172.16.43.131
192.168.0.175
8935
7
17
10.23.67.9
192.168.0.175
22387
7
17
10.166.166.166
192.768.0.75
19
7
17
192.168.57.2
192.168.0.175
6588
7
17
172.16.87.11
192.768.0.75
21453
7
17
10.166.166.166
192.168.0.175
19
7
17
10.34.67.89
192.168.0.175
45987
7
17
10.65.34.54
192.168.0.175
65212
7
17
192.168.25.6
192.168.0.175
52967
7
17
172.16.56.15
192.168.0.175
8745
7
17
10.166.166.166
192.168.0.175
19
7
17
表一 防火墙日志
之后在路由器日志上做了同样的工作并打印出了看上去异常的记录。
攻击期间的路由器日志
图一
解释:
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:98.4%的数据包的大小在33字节到64字节之间(注意红色标记)。
参数解释:
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:98.4%的数据包的大小在33字节到64字节之间。
Protocol 协议名称
Total Flows 自从最后一次清除统计信息后,这种协议的信息流的个数。
Flows/Sec 每秒钟时间内出现这种协议的信息流的平均个数,它等于总信息流数/综合时间的秒数。
Packets/Flow 遵守这种协议的信息流中平均的数据包数。等于这种协议的数据包数,或者在这段综合时间内,这种协议的信息流数。
Bytes/Pkt 遵守这种协议的数据包的平均字节数(等于这种协议总字节数,或者在这段综合时间内,这种协议的数据包数)。B/Pkt ,这一信息流中每个数据包的平均字节数
Packets/Sec 每秒钟时间内这种协议的平均数据包数(它等于这种协议的总数据包),或者这段综合时间的总秒数。
Active(Sec)/Flow 从第一个数据包到终止信息流的最后一个数据包的总时间(以秒为单位,比如TCP FIN,终止时间量等等),或者这段综合时间内这种协议总的信息流数。
Idle(Sec)/Flow 从这种协议的各个非终止信息流的最后一个数据包起,直到输入这一命令时止的时间总和(以秒为单位),或者这段综合时间内信息流的总时间长度。
正常路由日志
图二
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:2%的数据包的大小在33字节到64字节之间。
注意网站的访问量直线下降。很明显,在这段时间没人能访问他的Web服务器。我开始研究到底发生了什么,以及该如何尽快地修复。
二、事件分析
我的Web服务器发生了什么?很有可能攻击,那么受到什 么样的攻击呢?从这一攻击是对回显端口看,即是端口7,不断发送小的UDP数据包来实现。攻击看似发自两个策源地,可能是两个攻击者同时使用不同的工具。 在任何情况下,超负荷的数据流都会拖垮Web服务器。然而攻击地址源不确定,不知道是攻击源本身是分布的,还是同一个地址伪装出许多不同的IP地址,这个 问题比较难判断。假如源地址不是伪装的,是真实地址,则可以咨询ARIN I美国Internet号码注册处,从它的“whois”数据库查出这个入侵1P地址属于哪个网络。接下来只需联系那个网络的管理员就可以得到进一步的信 息。
那么假如源地址是伪装的,追踪这个攻击者就麻烦得多。若 使用的是Cisco路由器,则还需查询NetFlow高速缓存。NetFlow是Cisco快速转发(CEF)交换框架的特性之一。为了追踪这个伪装的地 址,必须查询每个路由器上的NetFlow缓存,才能确定流量进入了哪个接口,然后通过这些路由器一次一个接口地往回一路追踪,直至找到那个IP地址源。 然而这样做是非常难的,因为在Web Server和攻击者的发起pc之间可能经由许多路由器,而且属于不同的组织。另外,必须在攻击正在进行时做这些分析。
经过分析之后,将防火墙日志和路由器日志里的信息关联起 来,发现了一些有趣的相似性,如表黑色标记处。攻击的目标显然是Web服务器192.68.0.175,端口为UDP 7,即回显端口。这看起来很像拒绝服务攻击(但还不能确定,因为攻击的分布很随意)。地址看起来多多少少是随意而分散的,只有一个源地址是固定不变的,其 源端口号也没变。这很有趣。接着又将注意力集中到路由器日志上。
立刻发现,攻击发生时路由器日志上有大量的64字节的数据包,而此时Web服务器日志上没有任何问题。他还发现,案发时路由器日志里还有大量的“UDP-other”数据包,而Web服务器日志也一切正常。这种现象与基于UDP的拒绝服务攻击的假设还是很相符的。
攻击者正是用许多小的UDP数据包对Web服务器的回显 (echo 7)端口进行洪泛式攻击,因此他们的下一步任务就是阻止这一行为。首先,我们在路由器上堵截攻击。快速地为路由器设置了一个过滤规则。因为源地址的来源很 随机,他们认为很难用限制某个地址或某一块范围的地址来阻止攻击,因此决定禁止所有发给192.168.0.175的UDP包。这种做法会使服务器丧失某 些功能,如DNS,但至少能让Web服务器正常工作。
路由器最初的临时DOS访问控制链表(ACL)
access-list 121 remark Temporary block DoS attack on web server 192.168.0.175
access-list 105 deny udp any host 192.168.0.175
access-list 105 permit ip any any
这样的做法为Web服务器减轻了负担,但攻击仍能到达web,在一定程度上降低了网络性能。 那么下一步工作是联系上游带宽提供商,想请他们暂时限制所有在他的网站端口7上的UDP入流量。这样做会显著降低网络上到服务器的流量。
三、针对DOS预防措施
对于预防及缓解这种带宽相关的DoS攻击并没有什么灵丹妙药。本质上,这是一种“粗管子打败细管子”的攻击。攻击者能“指使”更多带宽,有时甚至是巨大的带宽,就能击溃带宽不够的网络。在这种情况下,预防和缓解应相辅相成。
有许多方法可以使攻击更难发生,或者在攻击发生时减小其影响,具体如下:
Ø 网络入口过滤 网络服务提供商应在他的下游网络上设置入口过滤,以防止假信息包进入网络(而把它们留在Internet上)。这将防止攻击者伪装IP地址,从而易于追踪。
Ø 网络流量过滤 过滤掉网络不需要的流量总是不会错的。这还能防止DoS攻击,但为了达到效果,这些过滤器应尽量设置在网络上游。
Ø 网络流量速率限制 一些路由器有流量速率的最高限制。这些限制条款将加强带宽策略,并允许一个给定类型的网络流量匹配有限的带宽。这一措施也能预先缓解正在进行的攻击,同时,这些过滤器应尽量设置在网络上游(尽可能靠近攻击者);
Ø 入侵检测系统和主机监听工具 IDS能警告网络管理员攻击的发生时间,以及攻击者使用的攻击工具,这将能协助阻止攻击。主机监听工具能警告管理员系统中是否出现DoS工具
Ø 单点传送RPF 这是CEF用于检查在接口收到的数据包的另一特性。如果源IP地址CEF表上不具有与指向接收数据包时的接口一致的路由的话,路由器就会丢掉这个数据包。丢弃RPF的妙处在于,它阻止了所有伪装源IP地址的攻击。
针对DDOS预防措施
看了上面的实际案例我们也了解到,许多DDoS攻击都很 难应对,因为搞破坏的主机所发出的请求都是完全合法、符合标准的,只是数量太大。借助恰当的ACL,我们可以阻断ICMP echo请求。但是,如果有自己的自治系统,就应该允许从因特网上ping你。不能ping通会使ISP或技术支持团队(如果有的话)丧失某些故障排解能 力。也可能碰到具有Cisco TCP截获功能的SYN洪流:
Router(config)#ip tcp intercept list 101
Router(config)#ip tcp intercept max-incomplete high 3500
Router(config)#ip tcp intercept max-incomplete low  3000
Router(config)#ip tcp intercept one-minute high 2500
Router(config)#ip tcp intercept one-minute low 2000
Router(config)#access-list 101 permit any any
如果能采用基于上下文的访问控制(Context Based Access Control,CBAC),则可以用其超时和阈值设置应对SYN洪流和UDP垃圾洪流。例如:
Router(config)# ip inspect tcp synwait-time 20
Router(config)# ip inspect tcp idle-time 60
Router(config)# ip inspect udp idle-time 20
Router(config)# ip inspect max-incomplete high 400
Router(config)# ip inspect max-incomplete low  300
Router(config)# ip inspect one-minute high  600
Router(config)# ip inspect one-minute low 500
Router(config)# ip inspect tcp max-incomplete host 300 block-time 0
警告:建议不要同时使用TCP截获和CBAC防御功能,因为这可能导致路由器过载。
打开Cisco快速转发(Cisco Express Forwarding,CEF)功能可帮助路由器防御数据包为随机源地址的洪流。可以对调度程序做些设置,避免在洪流的冲击下路由器的CPU完全过载:
Router(config)#scheduler allocate 3000 1000
在做了这样的配置之后,IOS会用3s的时间处理网络接口中断请求,之后用1s执行其他任务。对于较早的系统,可能必须使用命令scheduler interval<milliseconds>。
四、总结
无论是出于报复、敲诈勒索、发起更大规模攻击,DoS或 DDoS攻击都是一种不容轻视的威胁。非同一般的DoS攻击通常是某种不完整的漏洞利用—使系统服务崩溃,而不是将控制权交给攻击者。防范这种攻击的办法 是及时打上来自厂商的补丁,或者对于Cisco系统,及时将操作系统升级到更新版本。同时,要关闭有漏洞的服务,或者至少要用访问控制列表限制访问。常规 的DoS攻击,特别是DDoS攻击,经常不是那么有章法,也更难防范。如果整个带宽都被蹩脚的ping洪流所耗尽,我们所能做的就很有限了。最后,必须与 ISP和权力部门协作,尽可能从源头上阻止攻击。要用不同供应商、不同AS路径并支持负载均衡功能的不止一条到因特网的连接,但这与应对消耗高带宽的常规 DoS/DDoS洪流的要求还相差很远。我们总是可以用CAR或NBAR来抛弃数据包或限制发动进攻的网络流速度,减轻路由器CPU的负担,减少对缓冲区 和路由器之后的主机的占用。

[转载]Javascript中各种trim的实现

mikel阅读(982)

[转载]Javascript中各种trim的实现 – snandy – 博客园.

這是lgzx公司的一道面試題,要求給js的String添加一個方法,去除字符串兩邊的空白字符(包括空格、製錶符、換頁符等)。

1 String.prototype.trim = function() {
2 //return this.replace(/[(^\s+)(\s+$)]/g,"");//會把字符串中間的空白符也去掉
3 //return this.replace(/^\s+|\s+$/g,""); //
4 return this.replace(/^\s+/g,"").replace(/\s+$/g,"");
5 }

JQuery1.4.2,Mootools 使用

1 function trim1(str){
2 return str.replace(/^(\s|\xA0)+|(\s|\xA0)+$/g, '');
3 }

JQuery1.4.3,Prototype 使用,该方式去掉g以稍稍提高性能 在小规模的处理字符串时性能较好

1 function trim2(str){
2 return str.replace(/^(\s|\u00A0)+/,'').replace(/(\s|\u00A0)+$/,'');
3 }

Steven Levithan 在进行性能测试后提出了在JS中执行速度最快的裁剪字符串方式,在处理长字符串时性能较好

01 function trim3(str){
02 str = str.replace(/^(\s|\u00A0)+/,'');
03 for(var i=str.length-1; i>=0; i--){
04 if(/\S/.test(str.charAt(i))){
05 str = str.substring(0, i+1);
06 break;
07 }
08 }
09 return str;
10 }

最后需要提到的是 ECMA-262(V5) 中给String添加了原生的trim方法(15.5.4.20)。此外Molliza Gecko 1.9.1引擎中还给String添加了trimLefttrimRight 方法。

[转载]SQL Server性能调优:资源管理之内存管理篇(下)

mikel阅读(962)

[转载]SQL Server性能调优:资源管理之内存管理篇(下) – hjq19851202 – 博客园.

在上篇文章SQL Server性能调优:资源管理之内存管理篇(上),介绍了SQL Server的内存管理的一些理论知识,这篇利用这些知识来解决现实中常见的一些问题。

一、数据页缓存压力的调优

前篇我们说过,如果用户访问的数据页面都缓存在内存里,这样的相应速度是最快的。 但是现实中,数据库的大小都是大于物理内存的,SQL Server不可能将用户需要的所有数据都缓存在内存中,当用户需要的数据不在内存中,将会发生Paging动作从硬盘中读取需要的数据,偶尔的 Paging不会从整体上影响SQL Server的性能,但如果Paging动作经常发生将会严重影响SQL Server整体性能。

当我们进行数据页缓存的调优时,第一步先是确定是否有数据页缓存的压力,第二步是确定数据页缓存页的压力是由哪里引起的,主要可以分成外部压力和内部压力。

1、是否有数据页缓存压力

确定是否有数据页缓存压力,主要可以从下面的一些内存性能计数器和sys.sysProcesses来确认。

SQL Server:Buffer Manager-Lazy Writes/Sec的值经常发生。

SQL Server:Buffer Manager-Page Life Expectancy的经常反复变化,始终升不上去。

SQL Server:Buffer Manager-Page Reads/Sec的值经常不为0。

从sys.sysprocesses这一系统视图的wait_type中能看到 ASYNC_IO_COMPLETION值,这一值代表的意思是“等待I/O操作的完成”,这通常代表内存不足发生了硬盘读写,也可能有人会说这是硬盘的 速度太慢导致的,只要换上速度快的硬盘就能解决这个问题了。确实换上速度快的硬盘能使SQL Server的响应速度提高一些,但是如果上面那三个计数器的值经常,那硬盘的问题就不是主要问题,它只是内存不够(因)导致的硬盘读写(果),根本原因 还是在内存上。

从上面的分析中,可以确认系统中存在数据页缓存压力,现在就来分析这一压力的来源,是外部压力还是内部压力。

2、压力的来源

1)外部压力

SQL Server:Buffer Manager-Total Server Memory的值是否变小了。如果变小了那就说明是,SQL Server的能使用的内存被系统或者外部程序给压缩了。这就是外部压力。

2)内部压力

SQL Server:Buffer Manager-Total Server Memory的值没什么变化,但是和SQL Server:Buffer Manager-Target Server Memory的大小基本相等。这就是SQL Server的数据页的内存需求已经等于了系统能提供的内存大小了。说明是数据库内部压力。

3、解决办法

1)外部压力

发生外部压力的大多数情形都是由于系统中还运行了其他的服务器软件,在它需要内存 的时候抢掉了SQL Server的内存。因此解决方案也就是将SQL Server运行在专门的服务器上。还有一种情形会导致外部压力的发生,那就是操作系统在占用大量内存的操作(比如备份),解决方案就是将这些操作方到 SQL Server运行压力小的时候(比如凌晨1、2点的时候)。

2)内部压力

a、找出读取数据页面最多的语句,对它进行调优。找出这些语句可以通过sys.dm_exec_query_status动态视图和sys.dm_exec_sql_text动态函数的关联查询。

— 物理读取页面最多的100条语句
SELECT TOP 100
qs.total_physical_reads,qs.execution_count,
qs.total_physical_reads /qs.execution_count as avg_io,
qt.text, db_name(qt.dbid) as dbname, qt.objectid
FROM sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY qs.total_physical_reads desc
GO

— 逻辑读取页面最多的100条语句
SELECT TOP 100
qs.total_logical_reads,qs.execution_count,
qs.total_logical_reads /qs.execution_count as avg_io,
qt.text, db_name(qt.dbid) as dbname
FROM sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY qs.total_logical_reads desc
GO

找出这些语句然后经可以用语句调优的方式来进行调优了。

b、如果你认为语句已经没有调优的空间了,或者像快速的提高服务器性能就只能增加物理内存了。

二、Buffer Pool中的Stolen Memory的压力调优

1、通过Memory Clerk的分析

由于Buffer Pool里的Stolen内存都是SQL Server自己申请的,所以在Memory Clerk的动态管理视图里可以查看。通过分析各Clerk的大小,基本就能判断Stolen内存压力的来源。常见的使用Stolen的内存较多的 Memory Clerk。

a)CACHESTORE_SQLCP:缓存动态TSQL语句的执行计划的地方。这通常和程序员的代码有关,如果程序员习惯使用动态TSQL语句,这部分的内存中缓存的执行计划就会非常大。解决方法就是使用存储过程或者参数话的TSQL。

b)OBJECTSTORE_LOCK_MANAGER:SQL Server里锁结构使用的内存。如果SQL Server中的阻塞严重的话,这部分内存的内存使用量会很大。解决方案就是解决阻塞问题了。

2、通过sys.sysprocesses里面的waittype字段进行分析

1)CMEMTHREAD(0X00B9)

当多个用户向同一缓存区中申请内存或者释放内存,在某一时刻只会有一个连接的操作 可以成功,其他的连接必须等待。这种情况比较少,主要是发生在哪些并发度非常高的系统中,而且通常都是在编译动态的TSQL语句。解决方案就是使用存储过 程或者参数化的TSQL语句,提高执行计划的重用。

2)RESOURCE_SEMAPHORE_QUERY_COMPLIE(0X011A)

当用户传送过的语句或者调用的存储过程过分复杂,SQL Server编译它所需要的内存会非常大。SQL Server为了防止过多的内存被用来做编译动作,所以设置了编译内存的上限。当有太多复杂的语句同时在编译,编译所需要的内存可能达到这个上限,这将有 部分语句将处于等待内存进行编译的状态,也就该waittype。

解决方法有:尽量多的使用存储过程或参数化的TSQL语句,简化每次需编译的语句复杂度,分成几个存储过程,实在不行的话可以考虑定期运行DBCC FREEPROCCACHE语句来手工清除缓存中的执行计划,保证stolen中内存量。

三、Multi-Page Memory压力调优

由于32位的SQL Server会在启动的时候分配好Multi-Page的大小而且比较小,默认是384MB,因此对于32位的SQL Server比较容易发生Multi-Page Memory的压力。该部分的压力主要可能由下面三种情形导致。

1、程序连接数据库时的Network Packet Size大小,如果设置成8KB或者更高的时候,而且连接又非常大时。对于32位的SQL Server该部分的内存使用量会很快达到上限。解决方法就是将程序中设置的Network Packet Size改成默认的4KB,或者升级到64位SQL Server,这样Multi-Page的大小就没有限制了。

2、程序员使用了很多复杂的TSQL语句或者存储过程,它的执行计划超过了 8KB,这将占用Multi-Page的空间。由于32位的SQL Server中该部分的大小比较小,它将很快被填满,而由于Buffer Pool很大没有压力,它将不会触发Lazy Writer,Mullti-Page中的执行计划将不会被清理。而这时如果用户需要申请Multi-Page Memory就必须等待。这会体现在sys.sysprocessed的waittype字段上,该值等于 SOS_RESERVEDMEMBLOCKLIST。解决方案:语句进行调整,将它的执行计划控制在8KB以内,如果不行的话可以考虑定期运行DBCC FREEPROCCACHE语句来手工清理执行计划,或者升级到64位SQL Server。

这篇写得很乱,大家凑合看吧。。。

[转载]SQL Server性能调优:资源管理之内存管理篇(上)

mikel阅读(973)

[转载]SQL Server性能调优:资源管理之内存管理篇(上) – hjq19851202 – 博客园.

SQL Server来说,最重要的资源是内存、Disk和CPU,其中内存又是重中之重,因为SQL Server为了性能要求,会将它所要访问的数据全部(只要内存足够)放到缓存中。这篇就来介绍SQL Server的内存管理体系。

SQL Server作为Windows上运行的应用程序,必须接受Windows的资源管理,利用Windows的API来申请和调度各类资源。但是,由于 Windows的资源管理体系,是为了满足大多数的应用程序所设计的,这对于SQL Server这种定位于企业级、支持多用户和高并发性的数据库应用程序来说不是很适合,为此SQL Server开发了自己的一套资源管理体系——SQLOS(SQL操作系统)。也就是说SQL Server的资源管理分两层,第一层是在Windows上,通过Windows的API来申请资源。第二层是在SQL Server上,利用SQLOS来决定如何使用从Windows那里申请来的资源。

一、操作系统层面的SQL Server内存管理

由于SQL server的内存是通过Windows的API来申 请的,如果Windows自己本身就缺少内存,SQL Server由于申请不到内存,性能自然受影响。因此做SQL Server的内存检测,第一步就是查看系统层面的内存,以确保系统本身不缺内存,这一步简单但是必不可少。这里先介绍Windows的一些内存管理理 念,然后介绍如何检查系统的内存情况。

1、Windows的一些内存术语

Virtual Address Space(虚拟地址空间):应用程序能够申请访问的最大地址空间。对于32位的服务器,地址寻址空间为2的32次方,也就是4GB,但是这4GB并不是 都给SQL Server使用的,默认情况下是用户态2GB,核心态2GB,所以说对于32位的系统SQL Server只有2GB的内存可供使用。不过可以通过设置/3GB boot.int参数,来调整系统的配置,使用户态为3GB,核心态为1GB。或者开启AWE(地址空间扩展),将寻址空间扩展为64GB,不过该设置有 缺陷,下面会分析。

Physical Memory(物理内存):也就是通常所说的电脑的内存大小。

Reserved Memory(保留地址):应用程序访问内存的方式之一,先保留(Reserve)一块内存地址空间,留着将来使用(SQL Server中的数据页面使用的内存就是通过这个方式申请  的)。被保留的地址空间,不能被其他程序访问,不然会出现访问越界的报错提示。

Committed Memory(提交内存):将保留(Reserve)的内存页面正式提交(Commit)使用。

Shared Memory(共享内存):对一个以上进程可见的内存。

Private Bytes(私有内存):某进程提交的地址空间中,非共享的部分。

Working Set:进程的地址空间中存放在物理内存中的部分。

Page Fault(页面访问错误):访问在虚拟地址空间,但不存在于Working Set中会发生Page Fault。这个又分两种情况,第一种是目标页面在硬盘上,这钟访问会带来硬盘读写,这种称为Hard Fault。另外一种是目标页面在物理内存中,但是不是该进程的Working Set下,Windows只需要重新定向一下,成为Soft Fault。由于Soft Hard不带来硬盘读写,对系统的性能影响很小,因此管理员关心的是Hard Fault。

System Working Set:Windows系统的Working Set。

2、Windows的内存检测

可以通过Windows的性能监视器来检测Windows的内存使用情况,如何使用性能监视器,可以看这篇文章《使用“性能监视器”监视系统性能/运行情况》 。在检测内存上,比较重要的计数器有下面一些:

分析Windows系统的内存总体使用情况的计数器

Memory:Available MBytes:系统中空闲的物理内存数。

Memory:Pages/Sec:由于Hard Page的发生,每秒钟从硬盘中读取或者写入的页面数。该计数器等于Memory:Pages Input/Sec与Memory:Pages Output/Sec之和。

分析Windows系统自身的内存使用情况的计数器:

Memory:Cache Bytes:系统的Working Set,也就是Windows系统使用的物理内存数。

对于每个进程的内存使用情况的计数器:

Process:Private Bytes:进程提交的地址空间中非共享的部分。

Process:Working Set:进程的地址空间中存放在物理内存中的那部分。

从这些计数器中,我们可以看到系统中是否还有空闲内存,哪个进程使用的内存最多,在发生问题的时候是否有内存使用量突变等情况。这为接下来分析SQL Server的使用提供一个前提条件。

二、SQL Server内部的内存管理

1、内存使用分类

按用途分类

1)Database cache(数据页面)。SQL Server中的页面都是以8KB为一个页面存储的。当SQL Server需要用到某个页面时,它会将该页面读到内存中,使用完后会缓存在内存中。在内存没有压力的情况下,SQL Server不会将页面从内存中删除。如果SQL Server感觉到内存的压力时,会将最长时间没有使用的页面从内存中删除来空出内存。

2)各类Consumer(功能组件)

Connection的连接信息

General:一组大杂烩。语句的编译、范式化、每个锁数据结构、事务上下文、表格和索引的元数据等

Query Plan:语句和存储过程的执行计划。和Database cache类似,SQL Server也会将执行计划缓存以供将来使用,减少编译时间。

Optimizer:生成执行计划的过程中消耗的内存。

Utilities:像BCP、Log Manager、Backup等比较特殊的操作消耗的内存。

3)线程内存:存放进程内每个线程的数据结构和相关信息消耗的内存,每个线程需0.5MB的内存。

4)第三方代码消耗的内存:SQL Server的进程里,会运行一些非SQL Server自身的代码。例如:用户定义的CLR或Extended Stored Procedure代码

按申请方式分类

1)预先Reserve一块大的内存,然后在使用的时候一块一块的Commit。Database Page是按这种方式申请的。

2)直接用Commit方式申请的内存,成为Stolen方式。除了Database Page之外其他内存基本都是按这种方式申请的。

按申请内存的大小分类

1)申请小于等于8KB为一个单位的内存,这些内存称为Buffer Pool

2)申请大于8KB为一个单位的内存,这些内存称为Multi-Page(或MemToLeave)

SQL Server对于Database Page都是采用先Reserved后Commit的方式申请的,而数据页都是以8KB为单位进行申请的。

对于Consumer中的内存申请,一般都是按Stolen方式申请的,且大多数的执行计划的大小都是小于8KB的,少数特别复杂的存储过程的执行计划会超过8KB,默认的连接的数据包是4KB,除非客户端特别设置了超过8KB(不建议)

第三方代码的内存申请一般是按Stolen方式申请的,个别比如CLR中可能会用Reserved/Commit的方式申请。

线程的内存每个都以0.5MB的方式申请,自然是放在MemToLeave中。

之所以花了这么大篇幅来讲SQL Server的内存分类,是因为SQL Server尤其是32位的SQL Server对不同种类的内存的申请大小是不一样的,对Commit、Stolen和MemTOLeave等类型的内存是有限制的。因此会出现系统中还有空闲内存,但是SQL Server不会申请使用的现象。

2、各部分内存的大小限制

1)32位的Windows

在SQL Server启动时,会预先分配好MemToLeave区域的大小。默认大小为256MB+256(SQL Server配置的允许最大线程数)* 0.5MB=384MB,因此Buffer Pool中的最大值为2GB-384MB=1.664G。如果使用了AWE技术,可以将系统的扩展地址空间达到64GB,但由于AWE扩展出来的地址只能用Reserved/Commit方式申请,为此MemToLeave的内存还是384MB,Buffer Pool中的Stolen的最大内存为1.664G,剩余的内存都可以为Database Page页面使用。

2)64位的Windows

32位的SQL Server。由于64位的操作系统,核心态不再占用32位进程的虚拟地址空间,因此MemToLeave的大小还是为384MB,Buffer Pool可以达到3.664G。如果还开启了AWE,这3.664GB可以全部用于Buffer Pool中的Stolen,剩余的内存都可以给Database Page页面使用。不过这种情况很少见,哪里用64位操作系统的机器装32位的哦-_- 。

64位的SQL Server。所有的内存都无限申请的,有需要就申请。

3、SQL Server内存使用情况的分析

一般来说有两种方式,第一种就是用来分析系统内存情况时使用的用性能计数器来分析,第二种是使用动态管理视图(DMV,只适用于SQL Server2005和2008)

1)SQL Server性能计数器

SQLServer:Memory Manager:Total Server Memory(KB):SQL Server缓冲区提交的内存。不是SQL Server总的使用内存,只是Buffer Pool中的大小。

SQLServer:Memory Manager:Target Server Memory(KB):服务器可供SQL Server使用的内存量。一般是由SQL Server能访问到的内存量和SQL Server的sp_Configure配置中的Max Server Memory值中的较小值算得。

SQLServer:Memory Manger:Memory Grants Pending:等待内存授权的进程总数。如果该值不为0,说明当前有用户的内存申请由于内存压力被延迟,这意味着比较严重的内存瓶颈。

SQLServer:Buffer Manager:Buffer Cache Hit Ratio:数据从缓冲区中找到而不需要从硬盘中去取的百分比。SQL Server在运行一段时间后,该比率的变化应该很小,而且都应该在98%以上,如果在95%以下,说明有内存不足的问题。

SQLServer:Buffer Manager:Lazy Writes/Sec:每秒钟被惰性编辑器(Lazy writer)写入的缓冲数。当SQL Server感觉到内存压力的时候,会将最久没有使用的数据页面和执行计划从缓冲池中清理掉,做这个动作的就是Lazy Writer。

Page Life Expectancy:页面不被引用后,在缓冲池中停留的秒数。在内存没有压力的情况下,页面会一直待在缓冲池中,Page Life Expectancy会维持在一个比较高的值,如果有内存压力时,Page Life Expectancy会下降。所以如果Page Life Expectancy不能维持在一个值上,就代表SQLServer有内存瓶颈。

SQLServer:Buffer Manager:Database Pages :就是Database Cache的大小。

SQLServer:Buffer Manager:Free Pages:SQL Server中空闲可用的大小。

SQLServer:Buffer Manager:Stolen Pages:Buffer Pool中Stolen的大小。

SQLServer:Buffer Manager:Total Pages:Buffer Pool的总大小(等于Database Pages+Free Pages+Stolen Pages)。该值乘以8KB,应该等于Memory Manager:Total Server Memory的值。

从上面这些计数器中我们就能了解SQL Server的内存使用情况,结合前面说的系统层的计数器大概能看出是否存在内存瓶颈。

2)内存动态管理视图

在SQL Server 2005以后,SQL Server的内存管理是使用Memory Clerk的方式统一管理。所有的SQL Server的内存的申请或释放,都需要通过它们的Clerk,SQL Server也通过这些Clerk的协调来满足不同需求。通过查询这些DMV,可以得到比用性能计数器更加详细的内存使用情况。

我们可以通过下面的查询语句来检测SQL Server的Clerk的内存使用情况。

使用sys.dm_os_memory_clerks查看内存使用情况

SELECT type, Clerk的类型
sum(virtual_memory_reserved_kb) as vm_Reserved_kb, 保留的内存
sum(virtual_memory_committed_kb) as vm_Committed_kb, 提交的内存
sum(awe_allocated_kb) as awe_Allocated_kb, 开启AWE后使用的内存
sum(shared_memory_reserved_kb) as sm_Reserved_kb, 共享的保留内存
sum(shared_memory_committed_kb) as sm_Committed_kb, 共享的提交内存
sum(single_pages_kb) as SinlgePage_kb, Buffer Pool中的Stolen的内存
sum(multi_pages_kb) as MultiPage_kb MemToLeave的内存
FROM sys.dm_os_memory_clerks
GROUP BY type
ORDER BY type

从上面的查询语句,我们可以算出前面提到的内存大小

Reserved/Commit = sum(virtual_memory_reserved_kb) / sum(virtual_memory_committed_kb)

Stolen = sum(single_pages_kb) + sum(multi_pages_kb)

Buffer Pool = sum(virtual_memory_committed_kb) + sum(single_pages_kb)

MemToLeave = sum(multi_pages_kb)

通过上面的介绍我们可以知道SQL Server总体和各部分内存的使用情况,如果我想知道数据页的缓存中到底缓存了哪些数据,这些数据是属于哪个数据库的哪个表中的呢?执行计划又是缓存了哪些语句的执行计划呢?这也可以通过DMV查看的到。

查看内存中的数据页面缓存的是哪个数据库的哪个表格的数据

declare @name nvarchar(100)
declare @cmd nvarchar(1000)
declare dbnames cursor for
select name from master.dbo.sysdatabases
open dbnames
fetch next from dbnames into @name
while @@fetch_status = 0
begin
set @cmd = select b.database_id, db=db_name(b.database_id),p.object_id,p.index_id,buffer_count=count(*) from
这里的object_id代表是SQL Server中的对象号,index_id代表是索引号,buffer_count代表的是页面数
+ @name + .sys.allocation_units a,
+ @name + .sys.dm_os_buffer_descriptors b, + @name + .sys.partitions p
where a.allocation_unit_id = b.allocation_unit_id
and a.container_id = p.hobt_id
and b.database_id = db_id(”’ + @name + ”’)
group by b.database_id,p.object_id, p.index_id
order by b.database_id, buffer_count desc
exec (@cmd)
fetch next from dbnames into @name
end
close dbnames
deallocate dbnames
go

根据上面取出来的@object_id找出是哪个数据库的哪个表
SELECT s.name AS table_schema, o.name as table_name 使用的就是table_schema.table_name表
FROM sys.sysobjects AS o INNER JOIN
sys.schemas AS s ON o.uid = s.schema_id
WHERE (o.id = @object_id)
根据上面取出来的@object_id和@index_id找出索引的名称
SELECT id, indid, name as index_name index_name就是索引的名称
FROM sys.sysindexes
WHERE (id = @object_id) AND (indid = @index_id)
根据上面取出来的表名table_schema.table_name和索引的名称index_name,还可以找出该索引是建立在哪些字段上的
EXEC sp_helpindex table_schema.table_name

查看内存中缓存的执行计划,以及执行计划对应的语句:

输出可能较大,请小心使用
SELECT usecounts, refcounts, size_in_bytes, cacheobjtype, objtype, text
FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(plan_handle)
ORDER BY objtype DESC

写了这么多竟然发现大多数讲的还是数据收集的这一部分,相应的解决办法还没有讲到。。。由于文章太长,具体的解决方法将在下一篇讲解,下一篇将从Database Page、Stolen和Multi-Page三部分的具体瓶颈来讲解。

[转载]Android系统广播处理机制

mikel阅读(927)

[转载]Android系统广播处理机制 – 宁 静 致 远 – 博客园.

ndroid系统中的广播是广泛用于应用程序之间通信的一种手段,它类似于事件处理机制,不同的地方就是广播的处理是系统级别的事件处理过程(一般事件处理是控件级别的)。在此过程中仍然是离不开Intent对象,理解广播事件的

处理过程,灵活运用广播处理机制,在关键之处往往能实现特别的效果,举一个比较经典的例子,哪黑名单功能,当打进一个电话时,即产生了一个来电广播,则接收这种来电广播的BroadcastReceiver就会拿这个来电号码与黑名单中

号码进行比较,若匹配,则对此来电做相应处理,如挂电话或静音。在这个例子中就涉及到系统广播的发送、接收及对广播事件处理过程。

对比Android系统广播的相关知识,列出下面一张思维导图:

Android广播事件处理 (2)

实例代码:

待续……

[转载]超炫的3D特效程序管理功能android

mikel阅读(1061)

[转载]超炫的3D特效程序管理功能android – tankaixiong – 博客园.

tank我昨天我花了点时间重新整理了一下,加了很多注释希望让大家能够看的很明白

整理后在原来的基础上,实现一个超炫的3D特效程序管理功能,所以更有用途了,不仅仅只是显示图片了。

实现的效果:
用3D效果显示所有已安装的程序列表,点击某张图片时动态显示到最前一张,长按可以打开该程序。(如上篇博客展示的样子,这里不再贴出)

主要思路流程如下:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 实例化launcher列表,得到应用程序的信息列表(包括图片)
getLauncher();
final CoverFlow cf = new CoverFlow(this);//重写Garry的getChildStaticTransformation ,产生层叠和放大效果
//填充我的要的图片 http://www.cnblogs.com/tankaixiong/(原)
cf.setAdapter(new ImageAdapter(this));
//自定义图片的填充方式
ImageAdapter imageAdapter = new ImageAdapter(this);
cf.setAdapter(imageAdapter);
cf.setAnimationDuration(1500);
cf.setOnItemClickListener(this);
cf.setOnItemLongClickListener(lonClick);
setContentView(cf);
}

第一步:
创建一个实体类来保存程序信息:

LauncherItem

package com.Android.tank;
import Android.content.ComponentName;
import Android.graphics.drawable.Drawable;
public class LauncherItem {
Drawable icon;
String name;
ComponentName component;
LauncherItem(Drawable d, String s, ComponentName cn) {
icon = d;
name = s;
component = cn;
}
public Drawable getIcon() {
return icon;
}
public void setIcon(Drawable icon) {
this.icon = icon;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ComponentName getComponent() {
return component;
}
public void setComponent(ComponentName component) {
this.component = component;
}
};
第二步:存入
//这里保存从应用程序中获取到的信息LIST(包括图片的信息),你也可以自己定一个图片集合
List<LauncherItem> lvalue;
// 获得app 列表信息
public void getLauncher() {
lvalue = new ArrayList<LauncherItem>();
PackageManager pkgMgt = this.getPackageManager();//这个方法是关键
// to query all launcher & load into List<>
Intent it = new Intent(Intent.ACTION_MAIN);
it.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> ra = pkgMgt.queryIntentActivities(it, 0);//查询
//存入集合中 http://www.cnblogs.com/tankaixiong/(原)
for (int i = 0; i < ra.size(); i++) {
ActivityInfo ai = ra.get(i).activityInfo;
// String ainfo = ai.toString();
Drawable icon = ai.loadIcon(pkgMgt);
String label = ai.loadLabel(pkgMgt).toString();
ComponentName c = new ComponentName(ai.applicationInfo.packageName,
ai.name);
LauncherItem item = new LauncherItem(icon, label, c);
lvalue.add(item);
}
}
第三步:重写baseadapter
public class ImageAdapter extends BaseAdapter {
int mGalleryItemBackground;
private Context mContext;
public ImageAdapter(Context context) {
mContext = context;
TypedArray typedArray = obtainStyledAttributes(R.styleable.Gallery);
mGalleryItemBackground = typedArray.getResourceId(
R.styleable.Gallery_android_galleryItemBackground, 0);
}
// 第1点改进,返回一个很大的值,例如,Integer.MAX_VALUE
public int getCount() {
return resIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv = new ImageView(mContext);
iv.setImageDrawable(lvalue.get(position).icon);
iv.setImageBitmap(MyImgView.createReflectedImage(MyImgView
.drawableToBitmap(lvalue.get(position).icon)));//加入处理过的图片
iv.setLayoutParams(new Gallery.LayoutParams(80, 60));
return iv;
}
第四步:处理图片,产生特效
package com.android.tank;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuffXfermode;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
public class MyImgView {
/**
* 添加倒影,原理,先翻转图片,由上到下放大透明度
* @param originalImage
* @return
*/
public static Bitmap createReflectedImage(Bitmap originalImage) {
// The gap we want between the reflection and the original image
final int reflectionGap = 4;
int width = originalImage.getWidth();
int height = originalImage.getHeight();
// This will not scale but will flip on the Y axis
Matrix matrix = new Matrix();
matrix.preScale(1, -1);
// Create a Bitmap with the flip matrix applied to it.
// We only want the bottom half of the image
Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0,
height / 2, width, height / 2, matrix, false);
// Create a new bitmap with same width but taller to fit reflection
Bitmap bitmapWithReflection = Bitmap.createBitmap(width,
(height + height / 2), Config.ARGB_8888);
// Create a new Canvas with the bitmap that's big enough for
// the image plus gap plus reflection
Canvas canvas = new Canvas(bitmapWithReflection);
// Draw in the original image
canvas.drawBitmap(originalImage, 0, 0, null);
// Draw in the gap http://www.cnblogs.com/tankaixiong/(原)
Paint defaultPaint = new Paint();
canvas.drawRect(0, height, width, height + reflectionGap, defaultPaint);
// Draw in the reflection
canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
// Create a shader that is a linear gradient that covers the reflection
Paint paint = new Paint();
LinearGradient shader = new LinearGradient(0,
originalImage.getHeight(), 0, bitmapWithReflection.getHeight()
+ reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
// Set the paint to use this shader (linear gradient)
paint.setShader(shader);
// Set the Transfer mode to be porter duff and destination in
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
// Draw a rectangle using the paint with our linear gradient
canvas.drawRect(0, height, width, bitmapWithReflection.getHeight()
+ reflectionGap, paint);
return bitmapWithReflection;
}
//drawable 类型转化为bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap
.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
// canvas.setBitmap(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable
.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
}
第五步:
重写Garry 来达到层叠效果。
package com.android.tank;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
import android.widget.ImageView;
//自己定义的Gallery
public class CoverFlow extends Gallery {
private Camera mCamera = new Camera();
private int mMaxRotationAngle = 50;
private int mMaxZoom = -500;
private int mCoveflowCenter;
private boolean mAlphaMode = true;
private boolean mCircleMode = false;
public CoverFlow(Context context) {
super(context);
this.setStaticTransformationsEnabled(true);
}
public CoverFlow(Context context, AttributeSet attrs) {
super(context, attrs);
this.setStaticTransformationsEnabled(true);
}
public CoverFlow(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setStaticTransformationsEnabled(true);
}
public int getMaxRotationAngle() {
return mMaxRotationAngle;
}
public void setMaxRotationAngle(int maxRotationAngle) {
mMaxRotationAngle = maxRotationAngle;
}
public boolean getCircleMode() {
return mCircleMode;
}
public void setCircleMode(boolean isCircle) {
mCircleMode = isCircle;
}
public boolean getAlphaMode() {
return mAlphaMode;
}
public void setAlphaMode(boolean isAlpha) {
mAlphaMode = isAlpha;
}
public int getMaxZoom() {
return mMaxZoom;
}
public void setMaxZoom(int maxZoom) {
mMaxZoom = maxZoom;
}
private int getCenterOfCoverflow() {
return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2
+ getPaddingLeft();
}
private static int getCenterOfView(View view) {
return view.getLeft() + view.getWidth() / 2;
}
//重写Garray方法 ,产生层叠和放大效果
@Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
final int childCenter = getCenterOfView(child);
final int childWidth = child.getWidth();
int rotationAngle = 0;
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
if (childCenter == mCoveflowCenter) {
transformImageBitmap((ImageView) child, t, 0, 0);
} else {
rotationAngle = (int) (((float) (mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle);
// Log.d("test", "recanglenum:"+Math.floor ((mCoveflowCenter -
// childCenter) / childWidth));
if (Math.abs(rotationAngle) > mMaxRotationAngle) {
rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle
: mMaxRotationAngle;
}
transformImageBitmap((ImageView) child, t, rotationAngle,
(int) Math.floor((mCoveflowCenter - childCenter)/ (childWidth==0?1:childWidth)));
}
return true;
}
/**
* This is called during layout when the size of this view has changed. If
* you were just added to the view hierarchy, you're called with the old
* values of 0.
* @param w
*            Current width of this view.
* @param h
*            Current height of this view.
* @param oldw
*            Old width of this view.
* @param oldh
*            Old height of this view.
*/
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCoveflowCenter = getCenterOfCoverflow();
super.onSizeChanged(w, h, oldw, oldh);
}
/**
* Transform the Image Bitmap by the Angle passed
* @param imageView
*            ImageView the ImageView whose bitmap we want to rotate
* @param t
*            transformation
* @param rotationAngle
*            the Angle by which to rotate the Bitmap
*/
private void transformImageBitmap(ImageView child, Transformation t,
int rotationAngle, int d) {
mCamera.save();
final Matrix imageMatrix = t.getMatrix();
final int imageHeight = child.getLayoutParams().height;
final int imageWidth = child.getLayoutParams().width;
final int rotation = Math.abs(rotationAngle);
mCamera.translate(0.0f, 0.0f, 100.0f);
// As the angle of the view gets less, zoom in
if (rotation <= mMaxRotationAngle) {
float zoomAmount = (float) (mMaxZoom + (rotation * 1.5));
mCamera.translate(0.0f, 0.0f, zoomAmount);
if (mCircleMode) {
if (rotation < 40)
mCamera.translate(0.0f, 155, 0.0f);
else
mCamera.translate(0.0f, (255 - rotation * 2.5f), 0.0f);
}
if (mAlphaMode) {
((ImageView) (child)).setAlpha((int) (255 - rotation * 2.5));
}
}
mCamera.rotateY(rotationAngle);
mCamera.getMatrix(imageMatrix);
imageMatrix.preTranslate(-(imageWidth / 2), -(imageHeight / 2));
imageMatrix.postTranslate((imageWidth / 2), (imageHeight / 2));
mCamera.restore();
}
}

最后是表示层:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Gallery android:id="@+id/gallery" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_marginTop="30dp" />
<ImageSwitcher android:id="@+id/imageswitcher"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_marginTop="30dp" />
</LinearLayout>

好了,大概就这些了,整理这么多东东贴出可花了我不少时间呢,希望对大家有帮助!

这里提供源码下载的地址(请尊重tank的心血成果):

http://files.cnblogs.com/tankaixiong/MyApplicationMenu.rar

[转载]Android用GSon处理Json数据

mikel阅读(863)

[转载]Android用GSon处理Json数据 – Vincent.C – 博客园.

此篇接上篇 Android访问WCF(下篇)-客户端开发 将服务器获取的JSON数据通过GSON这个类库, 进行反序列化, 并通过UI显示出来.

如何在Android平台上用GSON反序列化JSON数据, 参考了这篇文章 http://benjii.me/2010/04/deserializing-json-in-android-using-gson/

一. 建立我们包装好的Http请求类文件 WebDataGetApi.java

package com.demo;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.protocol.HTTP;

import android.util.Log;

public class WebDataGetApi {

    private static final String TAG = "WebDataGetAPI";
    private static final String USER_AGENT = "Mozilla/4.5";

    protected String getRequest(String url) throws Exception {
        return getRequest(url, new DefaultHttpClient(new BasicHttpParams()));
    }

    protected String getRequest(String url, DefaultHttpClient client)
            throws Exception {
        String result = null;
        int statusCode = 0;
        HttpGet getMethod = new HttpGet(url);
        Log.d(TAG, "do the getRequest,url=" + url + "");
        try {
            getMethod.setHeader("User-Agent", USER_AGENT);
            // HttpParams params = new HttpParams();

            // 添加用户密码验证信息
            // client.getCredentialsProvider().setCredentials(
            // new AuthScope(null, -1),
            // new UsernamePasswordCredentials(mUsername, mPassword));

            HttpResponse httpResponse = client.execute(getMethod);
            // statusCode == 200 正常
            statusCode = httpResponse.getStatusLine().getStatusCode();
            Log.d(TAG, "statuscode = " + statusCode);
            // 处理返回的httpResponse信息
            result = retrieveInputStream(httpResponse.getEntity());
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new Exception(e);
        } finally {
            getMethod.abort();
        }
        return result;
    }

    /**
     * 处理httpResponse信息,返回String
     * 
     * @param httpEntity
     * @return String
     */
    protected String retrieveInputStream(HttpEntity httpEntity) {
        int length = (int) httpEntity.getContentLength();
        if (length < 0)
            length = 10000;
        StringBuffer stringBuffer = new StringBuffer(length);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(
                    httpEntity.getContent(), HTTP.UTF_8);
            char buffer[] = new char[length];
            int count;
            while ((count = inputStreamReader.read(buffer, 0, length - 1)) > 0) {
                stringBuffer.append(buffer, 0, count);
            }
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, e.getMessage());
        } catch (IllegalStateException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        return stringBuffer.toString();
    }
}

二. 建立JsonDataGetApi.java

package com.demo;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class JsonDataGetApi extends WebDataGetApi {
    private static final String BASE_URL = "http://10.0.2.2:82/AccountService/";
    private static final String EXTENSION = "Json/";;

    public JSONObject getObject(String sbj) throws JSONException, Exception {
        return new JSONObject(getRequest(BASE_URL + EXTENSION + sbj));
    }

    public JSONArray getArray(String sbj) throws JSONException, Exception {
        return new JSONArray(getRequest(BASE_URL + EXTENSION + sbj));
    }
}

三. 建立Android端Account模型Account.java

package com.demo;

import java.util.Date;

public class Account {

    public String Name;

    public int Age;

    public String Address;

    public Date Birthday;
}

四. 在我们的主Activity中调用刚才的方法, 在这一步中我们需要引入Google的gson 库gson-1.6.jar至我们的工程(下载地址)

package com.demo;

import java.util.Date;

import org.json.JSONArray;
import org.json.JSONObject;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class WebData extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        getJsonData();
    }

    public void getJsonData() {
        JsonDataGetApi api = new JsonDataGetApi();
        JSONArray jArr;
        JSONObject jobj;
        try {
            //调用GetAccountData方法
            jArr = api.getArray("GetAccountData");
            //从返回的Account Array中取出第一个数据
            jobj = jArr.getJSONObject(0);
            
          GsonBuilder gsonb = new GsonBuilder();
            //Json中的日期表达方式没有办法直接转换成我们的Date类型, 因此需要单独注册一个Date的反序列化类.
            //DateDeserializer ds = new DateDeserializer();
            //给GsonBuilder方法单独指定Date类型的反序列化方法
              //gsonb.registerTypeAdapter(Date.class, ds);
            
            Gson gson = gsonb.create();

            Account account = gson.fromJson(jobj.toString(), Account.class);

            Log.d("LOG_CAT", jobj.toString());
            ((TextView) findViewById(R.id.Name)).setText(account.Name);
            ((TextView) findViewById(R.id.Age)).setText(account.Age);
            ((TextView) findViewById(R.id.Birthday)).setText(account.Birthday
                    .toGMTString());
            ((TextView) findViewById(R.id.Address)).setText(account.Address);

        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(),
                    Toast.LENGTH_LONG).show();
            e.printStackTrace();
            TextView movie_Address = (TextView) findViewById(R.id.Address);
            movie_Address.setText(e.getMessage());
        }
    }
}

五.我们开始构建UI

打开layout下的main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView android:id="@+id/Name" android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <TextView android:id="@+id/Age" android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <TextView android:id="@+id/Birthday" android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <TextView android:id="@+id/Address" android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

在配置好RunConfiguration之后,我们开始运行程序,  查看Log发现有以下错误,

image

意思是说访问被禁止,也就是未授权访问,  其意思并不是我们的服务未授权, 因为Andriod具有很好的很好很好的安全机制, 我们要访问网络必须要经过授权才可以;

我们打开res目录下AndroidManifest.xml, 注意字体加粗放大的那句, 就是给我们的程序加入Internet的访问授权.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.demo"
      android:versionCode="1"
      android:versionName="1.0">
      
      <uses-permission android:name="android.permission.INTERNET"></uses-permission>

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".WebData"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

再次运行程序, 会发现显示如下:

image

image

从上图中的statuscode = 200来看,说明我们的请求已经成功, 问题出现在Json Parse(Json数据转换/反序列化/格式化)的过程中, 我们现在把从服务器传过来的数据拿出来看看, 在浏览器输入我们的服务地址: http://localhost:82/AccountService/Json/GetAccountData

[
 {
     "Address": "YouYi East Road",
     "Age": 56,
     "Birthday": "/Date(1298605481453+0800)/",
     "Name": "Bill Gates"
 },
 {
     "Address": "YouYi West Road",
     "Age": 57,
     "Birthday": "/Date(1298605481453+0800)/",
     "Name": "Steve Paul Jobs"
 },
 {
     "Address": "YouYi North Road",
     "Age": 65,
     "Birthday": "/Date(1298605481453+0800)/",
     "Name": "John D. Rockefeller"
 }
]

我们发现其中的Birthday的结果并非我们想象中yyyy-mm-dd HH:mm:ss类型, 究其原因可以查看MSDN文章《JavaScript 和 .NET 中的 JavaScript Object Notation (JSON) 简介

现在我们给我们的GsonBuilder指定Date的序列化方法, 先增加一个Date反序列化的类DateDeserializer.java

package com.demo;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

public class DateDeserializer implements JsonDeserializer<Date> {
    public Date deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {
        String JSONDateToMilliseconds = "\\/(Date\\((.*?)(\\+.*)?\\))\\/";
        Pattern pattern = Pattern.compile(JSONDateToMilliseconds);
        Matcher matcher = pattern.matcher(json.getAsJsonPrimitive()
                .getAsString());
        String result = matcher.replaceAll("$2");
        return new Date(new Long(result));
    }
}

其次修改Activity类中的GetDate方法如下, 注意其中加粗的部分.

    public void getJsonData() {
        JsonDataGetApi api = new JsonDataGetApi();
        JSONArray jArr;
        JSONObject jobj;
        try {
            //调用GetAccountData方法
            jArr = api.getArray("GetAccountData");
            //从返回的Account Array中取出第一个数据
            jobj = jArr.getJSONObject(0);
            
            GsonBuilder gsonb = new GsonBuilder();
            //Json中的日期表达方式没有办法直接转换成我们的Date类型, 因此需要单独注册一个Date的反序列化类.
            DateDeserializer ds = new DateDeserializer();
            //给GsonBuilder方法单独指定Date类型的反序列化方法
              gsonb.registerTypeAdapter(Date.class, ds);
            
            Gson gson = gsonb.create();

            Account account = gson.fromJson(jobj.toString(), Account.class);

            Log.d("LOG_CAT", jobj.toString());
            ((TextView) findViewById(R.id.Name)).setText(account.Name);
            ((TextView) findViewById(R.id.Age)).setText(String.valueOf(account.Age));
            ((TextView) findViewById(R.id.Birthday)).setText(account.Birthday
                    .toGMTString());
            ((TextView) findViewById(R.id.Address)).setText(account.Address);

        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(),
                    Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
    }
}

我们现在再运行程序 :

image

执行成功.

示例下载

[转载]ASP.NET中UrlEncode应该用Uri.EscapeDataString()

mikel阅读(1134)

[转载]ASP.NET中UrlEncode应该用Uri.EscapeDataString() – dudu – 博客园.

今天,茄子_2008反馈他博客中的“C++” 标签失效。检查了一下代码,生成链接时用的是HttpUtility.UrlEncode(url),从链接地址获取标签时用的是 HttpUtility.UrlDecode(url),从Encode到Decode,“C++”变成了“C  ”(加号变成空格)。这是大家熟知的问题,这里我们分析一下这个问题,并给出解决方法。

先看一下问题发生的过程:

1. 原始链接:

http://www.cnblogs.com/xd502djj/tag/C++/

2. HttpUtility.UrlEncode之后,得到:

http://www.cnblogs.com/xd502djj/tag/C%2b%2b/

3. Request.RawUrl,得到:

http://www.cnblogs.com/xd502djj/tag/C++/

4. HttpUtility.UrlDecode,得到:

http://www.cnblogs.com/xd502djj/tag/C /

上面第3步已经得到正确的结果,第4步的UrlDecode反而将加号变为了空格。

看来解决方法很简单,取消多此一举的UrlDecode,开始我们也是这么干的。过了一段时间,有用户反映“Windows Phone”的标签失效了,变成了“Windows+Phone”。我们一查,原来是在HttpUtility.UrlEncode时,空格被转换为加 号,需要调用UrlDecode将加号还原为空格,于是又把HttpUtility.UrlDecode加上(忘了之前的“C++”标签问题)。然 后,“C++”标签又失效…这样反反复复,看似Bug很多,工作很忙,实际上就是一个Bug…

终于有一天,我们说“再也不能这样过”,开始寻找解决方案:

既然HttpUtility.UrlEncode()不能用,那在.NET中找找有没有替代品。

先找到了HttpUtility.UrlPathEncode()。嘿,有用,轻松搞定“C++”与空格问题,但是…后来发现搞不定“C#”,它没有对“#”进行编码。

继续寻找…找到了Uri.EscapeUriString(),与HttpUtility.UrlPathEncode()同样的问题。

继续寻找…终于找到了…Uri.EscapeDataString(),搞定!请看下面的测试代码:

public void UrlEncodeTest() { string url = "C++ C#"; Console.WriteLine(HttpUtility.UrlEncode(url));//C%2b%2b+C%23 Console.WriteLine(HttpUtility.UrlPathEncode(url));//C++%20C# Console.WriteLine(Uri.EscapeUriString(url));//C++%20C# Console.WriteLine(Uri.EscapeDataString(url));//C%2B%2B%20C%23 }

注:运行环境.NET4。

[转载]Android---文本中缩略图点击弹出大图效果实现

mikel阅读(983)

[转载]Android—文本中缩略图点击弹出大图效果实现 – 莴笋炒肉 – 博客园.

很久没有写博客了,这两天一直忙于一个关于考试的项目,将其中的一些效果实现的经验写下来,希望给看到的人或者给有这方面需求的人帮助。

首先来张效果图,没有经过美工处理的 实现基本功能

其实做这个项目复习了很多内容,将之前单个项目中用到的某些功能综合到一起了,例如1、自定义标题栏2、Java和JavaScript的互调3、 Activity实现仿Dialog样式4、多线程实现考试倒计时5、退出Activity时保存配置信息(考试剩余时间)6、熟悉UI布局

上面这些效果中,讲讲通过Java和JavaScript互调实现点击文本中缩略图弹出一个大图。

最开始想实现这种效果的时候就想到了TextView控件,因为TextView通过Html这个类可以在文本中插入图片。但是有个问题困扰着我, 一个文本中的图片个数是不确定的,我怎样在一个TextView中添加多个图片(这个好解,通过TextView的append方法可以拼接任意张,因为 这里的缩略图都是相同的,但这却无法定位我点击了那个缩略图,应该弹出那个大图)。最后实在没办法,我想到了WebView这个控件,通过它加载一个本地 html页面,在其中通过给<image />对象添加onclick事件,传递一个imgSrc参数给Java方法,透过Java方法打开一个Activity来显示图片,ok,整个要实 现的效果就完成了。So Easy,虽然最后实现这个效果没多少代码,但是从TextView这个控件一路走来到WebView,也耗费了将近一天的功夫,但是这个过程却非常值得 享受,喔,我又懂得了更多。

下面通过代码一步一步来解析:首先是准备asset中的本地html文件。

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body onload="window.JsUseJave.onLoad()"> Write a recount for your newsletter using the notes given _____ <a onClick='setIv("/sdcard/IMG001.png")' > <img id="ig"/></a> <script language="javascript"> function load(src){ document.getElementById('ig').src=src; } function setIv(src){ window.JsUseJave.setImgSrc(src) } </script> </body> </html>

1、最开始的时候是想在进入Activity的时候就自动加载图片

wv.loadUrl("javascript:load('"+imgSrc+"'");

,但是发现一个问题在body的onload()中直接调用js:load(src)方法并没有执行。没办法只能用通过点击按钮给图片加载资源。后 来突然想到可以JavaScript和Java互调,就把这一步添加到JavaScriptInterface类中,这个类要自己定义

final class JsUseJaveInterface{ public void setImgSrc(String imgSrc){ Intent intent =new Intent(E6.this,ImageShow.class); Bundle bundle=new Bundle(); bundle.putString("imgSrc", imgSrc); intent.putExtras(bundle); startActivity(intent); } //登录加载图片 public void onLoad(){ wv.loadUrl("javascript:load('"+src+"')"); } }

设置JavaScript可调用Java

wv.addJavascriptInterface(new JsUseJaveInterface(), "JsUseJave");

在html文件body的onload事件中通过java转一步调用javascript方法中的load(src)事件。这样就能在加载Activity的时候将缩略图同时显示出来了。

2、有了前面的经验,点击缩略图弹出大图就好实现了。给<image/>添加点击事件间接的去调用Java中的一个方法重新打开一个Activity显示大图,就是上面的自定义的JsUseJavaInteface中的setImgSrc()方法。

**js调用Java中方法:window.JsUseJave.onLoad(),JsUseJave是wv.addJavascriptInterface(new JsUseJaveInterface(), “JsUseJave”);中的别名,onLoad()则是JsUseJaveInterface这个类中定义的一个方法。

[转载]理解Android系统的进程间通信原理(二)----RPC机制

mikel阅读(868)

[转载]理解Android系统的进程间通信原理(二)—-RPC机制 – 宁 静 致 远 – 博客园.

理解Android系统中的轻量级解决方案RPC的原理,需要先回顾一下JAVA中的RMI(Remote Method Invocation)这个易于使用的纯JAVA方案(用来实现分布式应用)。有关RMI的相关知识,可以通过下图来归纳:

RMI原理 (2)

Android中的RPC也是参考了JAVA中的RMI方案,这里我们再详细了解一下RPC的实现过程。

Android中的RPC机制是为了实现一个进程使用另一个进程中的远程对象,它使用了Android自己的AIDL(接口定义语言),使用户很方 便地定义出一个接口作为规范,通过一个远程Service为代理 ,客户端在绑定该远程Service过程中获取远程对象,进而使用该对象。可参考下图所示:

Android的RPC原理及应用 (2)

补充:RPC的另一个目的是对客户端只声明接口及方法,隐藏掉具体实现类,供客户端直接获取此接口实例。

实例代码:

待续