数据库用来存放数据,那么肯定需要存储空间,所以对磁盘空间的监视自然就很有必要了。
一. 磁盘可用空间
(1) DOS命令: fsutil volume diskfree
C:\windows\system32>fsutil volume diskfree C:
Total # of free bytes : 9789493248
Total # of bytes : 64424505344
Total # of avail free bytes : 9789493248
这里用到了fsutil,一个文件系统管理工具(file system utility),应该还有其他一些命令或者脚本也是可以的。
(2) WMI/WMIC: wmic logicaldisk
WMI是个
Windows系统的管理接口,在WMIC出现之前,如果要利用WMI管理系统,必须使用一些专门的WMI应用,例如SMS,或者使用WMI的脚本编程API,或者使用象CIM Studio之类的工具。如果不熟悉C++之类的编程语言或VBScript之类的脚本语言,或者不掌握WMI名称空间的基本知识,要用WMI管理系统是很困难的。WMIC改变了这种情况,它为WMI名称空间提供了一个强大的、友好的命令行接口。
C:\windows\system32>wmic logicaldisk get caption,freespace,size
Caption FreeSpace Size
C: 9789071360 64424505344
D: 189013438464 255331397632
这里通过wmic的get命令获取了logicaldisk 的几个参数列。
(3) 性能监视器
LogicalDisk: %Free Space
LogicalDisk: Free Megabytes
总大小 = LogicalDisk: Free Megabytes/ LogicalDisk: %Free Space
性能监视器虽然用于现场诊断还是挺方便的,但实现自动化监控,并不太好用。
(1) 扩展存储过程xp_cmdshell (还是在调用操作系统命令)
DECLARE @Drive TINYINT, @SQL VARCHAR(100) DECLARE @Drives TABLE ( Drive CHAR(1), Info VARCHAR(80) ) SET @Drive = 97 WHILE @Drive <= 122 BEGIN SET @SQL = 'EXEC XP_CMDSHELL ''fsutil volume diskfree ' + CHAR(@Drive) + ':''' INSERT @Drives ( Info ) EXEC(@SQL) UPDATE @Drives SET Drive = CHAR(@Drive) WHERE Drive IS NULL SET @Drive = @Drive + 1 END SELECT Drive, SUM(CASE WHEN Info LIKE 'Total # of bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS TotalMBytes, SUM(CASE WHEN Info LIKE 'Total # of free bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS FreeMBytes, SUM(CASE WHEN Info LIKE 'Total # of avail free bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS AvailFreeMBytes FROM( SELECT Drive, Info FROM @Drives WHERE Info LIKE 'Total # of %' ) AS d GROUP BY Drive ORDER BY Drive |
xp_cmdshell可以执行操作系统命令行,这段脚本用fsutil volume diskfree命令对26个字母的盘符遍历了一遍,不是很好,改用wmic会方便些,如下:
EXEC xp_cmdshell 'wmic logicaldisk get caption,freespace,size';
(2) 扩展存储过程xp_fixeddrives
--exec xp_fixeddrives IF object_id('tempdb..#drivefreespace') IS NOT NULL DROP TABLE #drivefreespace CREATE TABLE #drivefreespace(Drive CHAR(1), FreeMb bigint) INSERT #drivefreespace EXEC ('exec xp_fixeddrives') SELECT * FROM #drivefreespace Drive FreeMb C 9316 D 180013 |
总算不依赖操作系统命令了,不过,这个存储过程只能返回磁盘可用空间,没有磁盘总空间。
(3) DMV/DMF: sys.dm_os_volume_stats
SELECT DISTINCT @@SERVERNAME as [server] ,volume_mount_point as drive ,cast(available_bytes/ 1024.0 / 1024.0 / 1024.0 AS INT) as free_gb ,cast(total_bytes / 1024.0 / 1024.0 / 1024.0 AS INT) as total_gb FROM sys.master_files AS f CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id) ORDER BY @@SERVERNAME, volume_mount_point server drive free_gb total_gb … C:\ 9 59 … D:\ 175 237 |
从SQL Server 2008 R2 SP1开始,有了这个很好用的DMF: sys.dm_os_volume_stats,弥补了之前xp_fixeddrives没有磁盘总空间的不足。
不过,看它的参数就可以知道,没被任何数据库使用的磁盘,是查看不了的,所以xp_fixeddrives还有存在的必要。
二. 数据库可用空间
1. 文件可用空间查看
(1) 文件已用空间,当前大小(已分配空间),最大值,如下:
select @@SERVERNAME as server_name ,DB_NAME() as database_name ,case when data_space_id = 0 then 'LOG' else FILEGROUP_NAME(data_space_id) end as file_group ,name as logical_name ,physical_name ,type_desc ,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_Mb ,size/128.0 as allocated_size_mb ,case when max_size = -1 then max_size else max_size/128.0 end as max_size_Mb ,growth ,is_percent_growth from sys.database_files where state_desc = 'ONLINE' |
(2) 再算上磁盘的空闲空间,改动如下:
select @@SERVERNAME as server_name ,DB_NAME() as database_name ,case when data_space_id = 0 then 'LOG' else FILEGROUP_NAME(data_space_id) end as file_group ,name as logical_name ,physical_name ,type_desc ,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_mb ,size/128.0 as allocated_size_mb ,case when max_size = -1 then max_size else max_size/128.0 end as max_size_mb ,vs.available_bytes/1024.0/1024 as disk_free_mb ,growth ,CAST(is_percent_growth as int) as is_percent_growth from sys.database_files df cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs where state_desc = 'ONLINE' |
如果是SQL Server 2008 SP1以前的版本,可用xp_fixeddrives生成磁盘空闲空间表,再进行关联。
(3) 结合文件是否自增长,文件最大值,磁盘空间,算出文件可用空间比率,改动如下:
select @@SERVERNAME as server_name ,DB_NAME() as database_name ,case when data_space_id = 0 then 'LOG' else FILEGROUP_NAME(data_space_id) end as file_group ,name as logical_name ,physical_name ,type_desc ,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_mb ,size/128.0 as allocated_size_mb ,case when max_size = -1 then max_size else max_size/128.0 end as max_size_mb ,vs.available_bytes/1024.0/1024 as disk_free_mb ,case when growth = 0 then (size - FILEPROPERTY(name,'SpaceUsed'))*1.0/size when growth > 0 and max_size = -1 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,'SpaceUsed')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024) when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) >= 0 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,'SpaceUsed')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024) when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) < 0 then (max_size - FILEPROPERTY(name,'SpaceUsed'))*1.0/max_size else null end as free_space_percent ,growth ,CAST(is_percent_growth as int) as is_percent_growth from sys.database_files df cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs where state_desc = 'ONLINE' |
(4) 如果有多个数据库,注意fileproperty()和filegroup_name()函数,都只在当前数据库下生效,改动如下:
if object_id('tempdb..#tmp_filesize') is not null drop table #tmp_filesize GO create table #tmp_filesize ( server_name varchar(256), database_name varchar(256), file_group varchar(256), logical_name varchar(256), physical_name varchar(1024), type_desc varchar(128), used_size_mb float, allocated_size_mb float, max_size_mb float, disk_free_mb float, free_space_percent float, growth int, is_percent_growth int ) GO exec sp_msforeachdb 'use [?] insert into #tmp_filesize select @@SERVERNAME as server_name ,DB_NAME() as database_name ,case when data_space_id = 0 then ''LOG'' else FILEGROUP_NAME(data_space_id) end as file_group ,name as logical_name ,physical_name ,type_desc ,FILEPROPERTY(name,''SpaceUsed'')/128.0 as used_size_mb ,size/128.0 as allocated_size_mb ,case when max_size = -1 then max_size else max_size/128.0 end as max_size_mb ,vs.available_bytes/1024.0/1024 as disk_free_mb ,case when growth = 0 then (size - FILEPROPERTY(name,''SpaceUsed''))*1.0/size when growth > 0 and max_size = -1 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,''SpaceUsed'')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024) when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) >= 0 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,''SpaceUsed'')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024) when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) < 0 then (max_size - FILEPROPERTY(name,''SpaceUsed''))*1.0/max_size else null end as free_space_percent ,growth ,CAST(is_percent_growth as int) as is_percent_growth from sys.database_files df cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs where state_desc = ''ONLINE''' select * from #tmp_filesize |
2. 数据库可用空间告警
2.1 告警的格式
数据库可用空间告警,通常不告警某个文件,也不告警整个数据库,而是某个确切的文件组/表空间,日志文件是没有文件组的,所有可以把日志文件合并为LOG这个组。
(1) Oracle可以给表空间设置最大尺寸,表空间里的每个文件逐个使用,直到最后一个文件也没空间时,就会提示空间不足;
(2) SQL Server 无法对文件组设置最大尺寸,只可以给文件组里每个文件指定最大尺寸,所以要先统计:是否当前文件组下所有的文件都已经满了?
将同一个文件组/LOG下的所有文件都检查一下,如果所有文件都满了(以20%为例),那么就满足告警条件了,如下:
--#tmp_filesize 在上面的脚本里生成了
select server_name,
database_name,
file_group,
MAX(free_space_percent) as max_free_space_percent
from #tmp_filesize
group by server_name,database_name,file_group
having MAX(free_space_percent) <= 0.2 --20%
邮件告警的格式大致为:
邮件标题:主机名\实例名\数据库名\文件组名,@@servername已经包含了SQL Server实例名;
邮件内容:文件组 ”file group name” 空间不足,已低于20%。
2.2 告警后如何处理?
(1) 告警中的文件组里的文件,所在的磁盘还有空间吗?
exec xp_fixeddrives
如果当前磁盘没空间,可以给当前文件组在其他磁盘上添加新的文件,并关闭老的文件自增长或限制最大值;
如果所有磁盘都没空间,可以考虑删除磁盘上的其他文件,或者收缩数据库文件(数据/日志),或者磁盘扩展空间(加磁盘)。
(2) 如果磁盘有空间,文件是否关闭了自动增长?
可能是在创建文件时,给了文件比较大的size,如500G,并关闭了文件自动增长;
ALTER DATABASE test
ADD FILE
(
NAME = test_02,
FILENAME = 'D:\Program Files (x86)\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\test_02.ndf',
SIZE = 500 GB,
FILEGROWTH = 0
)
TO FILEGROUP [PRIMARY];
GO
(3) 如果磁盘有空间,自动增长也开了,是不是限制了文件最大值?
限制最大值和关闭自增长,应该都是不想单个文件变得太大,个人觉得一个文件控制在500G以内比较合理,这两种情况,都建议扩展一个新文件。
小结
如果没有监控工具,那么可选择系统视图,扩展存储过程,结合数据库邮件的方式,作自动检查,并告警文件组/日志空闲空间不足。大致步骤如下 :
(1) 部署数据库邮件;
(2) 部署作业:定时检查文件组/日志空闲空间,发邮件告警。
English » | | | | | | | | |
Java在中文环境中乱码无处不在,而且出现的时间和位置也包涵广泛,具体的解决方法也是千奇百怪。
但是如果能理清其中的脉络,理解字符处 理的过程,对于解决问题很有指导意义,不至于解决了问题也不知道为什么。
其实,原因不外乎出在String输入时和输出时。
首先,Java中的任何String都是以UNICODE格式存在的。
很多人因为在GBK环境中使用String,会误以为String是GBK格式,实际上Java的String类中并没有存储CharSet信息的字段, 所有String中的字符只会以UNICODE的2字节形式存在。
String在构造时会逐一把字符按指定编码(默认值为系统编码GBK),转换为UNICODE字符,存入一个Char(无符号16位)数组中。
如:
new String(bytes,"gbk");
并不是说,生成一个GBK编码的字符串,而是按GBK逐一辨认字节数组bytes中的字符转化为UNICODE。
假设,bytes本是按GB编码的,构造方法在发现一个最高位为0的byte就作为ascii字符处理,最高位为1就和后面的一个byte合成中文字符, 再转换编码。
可以看出,在这个过程中,编码选择错误就会导致程序按错误方法辨认bytes,乱码就出现了。
在这里产生的乱码,很多时候还可以通过.getByte()方法修复,还没有后面的严重。
如:
"中".getBytes("iso-8859-1");
因为iso-8859-1中没有中文,所以"中"的值被替换成63,显示'?',无法判断以前是什么值。
所以如下String将被破坏掉:
new String("中文".getBytes("iso-8859-1"),"iso-8859-1");
如果目标编码方式支持中文,就不会损坏String:
new String("中文".getBytes("utf-8"),"utf-8");
Java在显示字符时,还需要进行一次转换,把UNICODE字符转换成用于显示的字符编码形式。
很多时候,这个过程是自动的,会按系统的默认编码(一般是GBK)转换String。
如果和页面编码不一样,就会出现乱码,虽然在Java的程序中只有一种编码,输出却可以有不同的编码。
有时候,我们需要用 iso-8859-1格式分解String的中文,以便在不支持中文的系统中存储:
new String("中文".getBytes("GBK"),"iso-8859-1");
先通过GBK等支持中文的编码方式分解为byte数组,再做为iso-8859-1字符组成字符串,就避免了被替换为Char(63)。
=========================================================================
示例程序
public static void main(String[] args) { String str = "中国"; printBytes("中国的UNICODE编码:", str.getBytes(Charset.forName("unicode"))); printBytes("中国的GBK编码:", str.getBytes(Charset.forName("GBK"))); printBytes("中国的UTF-8编码:", str.getBytes(Charset.forName("UTF-8"))); } public static void printBytes(String title, byte[] data) { System.out.println(title); for (byte b : data) { System.out.print("0x" + toHexString(b) + " "); } System.out.println(); } public static String toHexString(byte value) { String tmp = Integer.toHexString(value & 0xFF); if (tmp.length() == 1) { tmp = "0" + tmp; } return tmp.toUpperCase(); } |
上例的输出结果为:
中国的UNICODE编码:
0xFE 0xFF 0x4E 0x2D 0x56 0xFD
中国的GBK编码:
0xD6 0xD0 0xB9 0xFA
中国的UTF-8编码:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
最近一直在做项目迁移的
工作,由传统的ASP.NET转到
Windows Azure,这里介绍一下Azure的
配置管理。在传统的WinForm或ASP.NET项目下,配置文件为
web.config(app.config),而Cloud Service项目的配置文件是*.cscfg。
一个环境一个配置文件,并且提供可视化编辑。
但这里的配置有一个缺点,目前Azure SDK2.0还不支持多级配置,传统配置下的appSettings和connectionStrings在这里只有合并了。在保证对现有系统最小影响的改动下,支持Azure的配置只需要引入一个对象CloudConfigurationManager,据MSDN介绍,CloudConfigurationManager可以智能识别当前运行的环境,读取配置对象,也就说:当你的应用运行在传统的本地IIS时,他会读取Web.config;反过来,当你的应用运行在Cloud上,它会读取cscfg。
既然有了类库的支持,我们对其封装一下即可。注意在Azure配置中,appSettings和connectionStrings是同一级的,用CloudConfigurationManager.GetSetting就可以读到,当然,这时appSettings和connectionStrings的所有配置Key不能有同名的。如果CloudConfigurationManager.GetSetting获取的Value为空,说明此Key有可能是App(Web).config下面的connectionStrings节点配置。
public static class SettingsManager { /// <summary> /// 获取Azure或App(Web).config下的配置节点及连接字符串 /// </summary> /// <param name="key"></param> /// <returns></returns> public static string GetSetting(string key) { string value = CloudConfigurationManager.GetSetting(key); if (string.IsNullOrEmpty(value)) { if (null != ConfigurationManager.ConnectionStrings[key]) value = ConfigurationManager.ConnectionStrings[key].ConnectionString; } return value; } } |
这样,一个简单的配置读取类就写好了,将系统中所有读取配置的方法统一改成SettingsManager.GetSetting(key)即可。本地开发时,可以抛弃Azure的模拟器(硬件要求高),从而选择我们较为熟悉本地IIS;Azure用于部署QA/生产环境/预部署,一种读取方式,适应两种场景。
Azure配置支持在线修改,避免使用远程桌面手动操作。
Selenium 2 最大的更新就是集成了WebDriver。这两者是什么关系呢?如果你搜索WebDriver,第一条结果是Selenium。其实WebDriver和Selenium可以说是在实现UI Automation的竞争对手。Selenium是运行在JavaScript的sandbox里面,所以很容易就支持不同的浏览器;而WebDriver则是直接操作浏览器本身,更接近用户的真实操作,但正因为如此,所以WebDriver在多浏览器/操作系统的支持上就要落后于Selenium。不过从Selenium 2开始,这两个项目合并了,可以继续用原来的Selenium,也可以考虑迁移到WebDriver。我个人认为WebDriver应该是以后的大趋势,还是值得迁移的。至于你信不信,我反正是信了。
作为一个轻量级的UI Automation框架,需要写一些驱动它的代码,大部分人会选择
JUnit,因为JUnit是
单元测试的事实标准;但是我会用
TestNG。这些UI Automation的东西,它们本身不是单元测试,而且也没有太多单元测试的风格。
从一段简单的测试开始
public class GoogleTest { @Test public void search(ITestContext context) { WebDriver driver = new FirefoxDriver(); driver.get("http://www.google.com"); WebElement element = driver.findElement(By.name("q")); element.sendKeys("magus"); element.submit(); Assert.assertTrue(driver.getTitle().contains("magus"), "Something wrong with title"); } } |
TestNG应用了Java的Annotations,只需要在测试方法上面打上@Test就可以标示出search是一个测试方法。用TestNG运行测试还需要一个testng.xml的文件,文件名其实可以随便起,没有关系的。
<suite name="Magus demo" verbose="2"> <test name="Search function"> <classes> <class name=" test.GoogleTest"> <methods> <include name="search" /> </methods> </class> </classes> </test> </suite> |
我想让测试更加灵活,1. 可以配置使用任意支持的浏览器进行测试;2. 配置所有
Google的URL;3. 配置搜索的关键字。修改后的代码:
public class GoogleTest { WebDriver driver; @Parameters({"browser"}) @BeforeTest public void setupBrowser(String browser){ if (browser.equals("firefox")){ driver = new FirefoxDriver(); } else { driver = new ChromeDriver(); } } @Parameters({ "url", "keyword" }) @Test public void search(String url, String keyword, ITestContext context) { driver.get(url); WebElement element = driver.findElement(By.name("q")); element.sendKeys(keyword); element.submit(); Assert.assertTrue(driver.getTitle().contains(keyword), "Something wrong with title"); } } |
testng.xml
<suite name="Magus demo" verbose="2"> <parameter name="browser" value="firefox" /> <parameter name="url" value="http://www.google.com" /> <parameter name="keyword" value="magus" /> <test name="Search function" preserve-order="true"> <classes> <class name="test.GoogleTest"> <methods> <include name="setupBrowser" /> <include name="search" /> </methods> </class> </classes> </test> </suite> |
利用TestNG的@Parameters标签,让测试方法从testng.xml里面读取参数,实现参数化。在testng.xml的配置中,test节点需要增加一个属性的配置: preserve-order=”true”。这个preserve-order默认是false,在节点下面的所有方法的执行顺序是无序的。把它设为true以后就能保证在节点下的方法是按照顺序执行的。TestNG的这个功能可以方便我们在testng.xml里面拼装测试。假设我们有很多独立的测试方法,例如
navigateCategory
addComment
addFriend
login
logout
就可以在testng.xml里面拼出不同的测试,例如
<test name="Add friend" preserve-order="true"> <classes> <class name="test.GoogleTest"> <methods> <include name="login" /> <include name="addFriend" /> <include name="logout" /> </methods> </class> </classes> </test> <test name="Add comment to category" preserve-order="true"> <classes> <class name="test.GoogleTest"> <methods> <include name="login" /> <include name="navigateCategory" /> <include name="addComment" /> <include name="logout" /> </methods> </class> </classes> </test> |
TestNG比JUnit更加适合做一些非单元测试的事情,不是说JUnit不好,而是不能把JUnit当成万能的锤子,到处钉钉子。WebDriver的API比Selenium的更加简洁,会是以后的大趋势。
之前有
IBM Rational Appscan使用详细说明的一篇
文章,主要是针对扫描过程中配置设置等.本文将介绍针对扫描结果的分析,也是一次完整的渗透
测试必须经历的环节.
扫描开始的时候,Appscan会询问是否保存扫描结果,同时下方有进度条显示扫描的进度.
在扫描过程中,如果遇到任何连接问题或其他任何问题,可以暂停扫描并在稍后继续进行.如第一篇文章中讲的扫描包括两个阶段-探索、测试.Appscan种的Scan Expert和HP WebInspect中的建议选项卡类似,Scan Expert分析扫描的配置,然后针对变化给出配置建议,目的是为了更好的执行一次扫描.可以选择忽略或者执行这些建议.
Appscan的窗口大概分三个模块,Application Links(应用链接), Security Issues(安全问题), and Analysis(分析),如下图所示:
Application Links Pane(应用程序结构)
这一块主要显示网站的层次结构,基于URL和基于内容形式的文件夹和文件等都会在这里显示,在旁边的括号里显示的数字代表存在的漏洞或者安全问题.通过右键单击文件夹或者URL可以选择是否忽略扫描此节点.Dashboard窗格会根据漏洞严重程序,高中低列出网站存在的问题情况,因此Dashboard将反映一个应用程序的整体实力。
Security Issues Pane(安全问题)
这个窗格主要显示应用程序中存在的漏洞的详细信息.针对没一个漏洞,列出了具体的参数.通过展开树形结构可以看到一个特定漏洞的具体情况,如下所示:
根据扫描的配置,Appscan会针对各种诸如
SQL注入的关键问题,以及像邮件地址模式发现等低危害的漏洞进行扫描并标识出来.因为扫描策略选择了默认,Appscan会展示出各种问题的扫描情况.右键单击某个特定的漏洞可以改变漏洞的的严重等级为非脆弱,甚至可以删除.
Analysis Pane(分析)
选择Security Issues窗格中的一个特定漏洞或者安全问题,会在Analysis窗格中看到针对此漏洞或者安全问题的四个方面:Issue information(问题信息), Advisory(咨询), Fix Recommendation(修复建议), Request/Response(请求/相应).
Issue information(安全问题信息)
Issue information 标签下给出了选定的漏洞的详细信息,显示具体的URL和与之相关的安全风险。通过这个可以让安全分析师需要做什么,以及确认它是一个有效的发现。
Advisory(咨询)
在此选项卡,你可以找到问题的技术说明,受影响的产品,以及参考链接。
Fix Recommendation(修复建议)
本节中会提到解决一个特定问题所需要的步骤.
Request/Response(请求/响应)
此标签显示发送给应用程序测试相关反应的具体请求的细节.在一个单一的测试过程中,根据安全问题的严重性会不止发送一个请求.例如,检查SQL盲注漏洞,首先AppScan中发送一个正常的请求,并记录响应。然后发送一个SQL注入参数,然后再记录响应.同时发送另外一个请求,来判断条件,根据回显的不同,判断是否存在脆弱性漏洞。在此选项卡,有以下一些标签.如图:
Show in Browser(在浏览器显示),让你在浏览器看到相关请求的反应,比如在浏览器查看跨站脚本漏洞.实际上会出现警从Appscan发出的弹窗信息.
Report False Positive(报告误报),如果发现误报,可以通过此标签发送给Appscan团队.
Manual
Test(手动测试),单击此项之后会打开一个新的窗口,允许您修改请求并发送来观察响应.这个功能类似Burp Suite中的"repeate"选项.
Delete Variant(变量删除),从结果中删除选中的变量.
Set as Non-vulnerable(非脆弱性设置),选取的变量将被视为非脆弱性.
Set as Error Page(设置为错误页面), 有时应用程序返回一个定制的错误页面,通过此选项可以设置错误页面,避免Appscan因为扫描响应为200而误报.
Understanding the Toolbar(了解工具栏)
Scan按钮,开始扫描探测,继续扫描探测.
Manual Explore(手动扫描)按钮可以用于如果只想扫描特定的URL或者网站的一部分,可以记录输入链接,然后点击"Continue with Full Scan(继续全面扫描)",Appscan就只会扫描手动扫描设置下的链接.
Scan Configuration(扫描配置) 按钮会打开配置向导.
通过点击report按钮,可以生成一份详细的扫描分析报告.
Scan Log(扫描日志)记录AppScan中进行的扫描的每一个动作。因此,使用此功能,您可以跟踪所有的活动。例如,扫描运行时,你可以查看此时AppScan中正在寻找什么。
Analyze JavaScript(分析Javascript)按钮执行的JavaScript分析,发现广泛的客户端的问题,如基于DOM的跨站脚本.
可以在View Application Data下查看其他各种结果。比如访问的网址,断开的链接,JavaScript,Cookies等.
以上是对Appscan中的工具功能进行简单的了解,继续进行结果分析,可能需要先解决高的严重性的漏洞或者安全问题.首先选择一个脆弱的网址或参数的分析,如下图所示:
在 分析( analysis)选项卡下会自动获得相关的强调细节,首先需要判断是否是一个脆弱性漏洞,或者是一个误报.这个判断完全取决与你的技术水平,如果确定是误报,可以右键进行删除.如果是正确的判断,可以继续分析下一个扫描结果,全部分析完成可以生成一个分析报告.
下面的一些提示将有对分析有所帮助:
Tips for Analysing(分析注意事项)
1.分析扫描结果的同时,如果发现不是你的应用程序有关的问题,可以点击右上角的Vulnerability-->State-->Noise.这个扫描将会完全从列表中删除此扫描结果.如果想显示,可以在View-->Show issues Marker as Noise(显示标记为杂讯的问题),将会显示带有删除线的灰色文本中的问题.
2.如果开发团队针对一个特定的漏洞进行了修复,不必要再次扫描整个应用程序,来进行重新测试该问题.只需要点击URL,选择"Retest the Issues Found",如果有发现新的问题,会自动添加到扫描结果中.
3.CVSS设置可以调整特定漏洞的严重性,想改变漏洞严重性,右键单击一个漏洞,Severity-->CVSS settings.
4.工具菜单中的"Manual Test(手动测试)"选项可以帮助进行手动发送攻击请求,而且可以保存当前扫描下的结果,编辑请求,发送后,点击"Save"保存当前的扫描测试.
5.Appscan扫描过程中会有很多测试,扫描结果中只显示发现的漏洞的测试,如果需要显示所有的测试(包括非脆弱的结果),需要选择Scan Configuration(扫描配置)-->Test 下的"Save Non-vulnerable Test Variant Information"选项.完成扫描之后可以在View-->Non-vulnerable Variants下查看到.
6.如果想扫描一个特定的URL或一个应用程序的特定部分,可以先针对整个应用程序进行探测而不进行测试.选择扫描配置向导下的"Start with Automatic Explore Only"选项.然后输入要扫描的网址,进行扫描.
7.当需要扫描一个正在使用的网站,有可能会导致服务瘫痪,需要确保开发团队有意识到这个后果.
8.Test Malware(恶意软件测试):这个会分析网站中的恶意的链接.可以选择Scan-->Test For Malware,如果有任何发现,也会被添加扫扫描结果中.
Generating Reports(生成报告)
在分析结束之后可以针对所有确定的结果进行生成报告.其中包括为了解决改问题需要遵循的补救措施的报告.报告是可以根据需求进行定制的,例如可以为不同的开发团队设置不同的模板.比如针对公司标志,封面页,报告标题等进行不同的定制。
在上图中,可以看到所有可选的参数.
Tools(工具)
本节介绍Tools中的Power Tools,该工具是为了更好的对结果进行分析.
Authentication Tester(认证测试)
帮助执行针对应用程序用户名和密码进行暴力猜解,结果取决于密码策略字典强大与否.
Connection Test(连接测试)
可以用来ping一个网站,仅此而已.
Encode/Decode(编码/解码)
分析扫描结果的同时,可能会遇到许多的地方需要进行编码和解码.
HTTP Request Editor(Http请求编辑器)
可以修改请求的值来测试应用程序针对请求返回的不同响应.
这篇文章是Rational Appscan使用中的一部分内容,重要的是牢记,工具值提供结(在某些情况下甚至都可能不提供你所有的结果),从安全分析师的观点,提升个人技术技能才是最重要的,从而可以判断发现的是否是误报还是真正存在漏洞.
开发环境:
Win XP + eclipse-jee-helios(版本号3.6) + ADT(版本10.0.1) +
Android SDK(版本10);
在Android软件的开发过程中,可以使用Junit测试框架。在Junit中可以得到组件,可以模拟发送事件和测试程序处理的正确性。
第一步:在新建项目中,创建待测试的业务类,在cn.hao.service包中,代码如下:
package cn.hao.service; //业务类,待测试的两个方法 public class PersonaService { public void save(String username){ String sub = username.substring(6); } public int add(int a,int b){ return a+b; } } |
说明:对于save()方法,如果参数为null,那么这个方法会发生错误;对add()方法,我们测试相加返回的相加结果是否正确。
在AndroidManifest.xml中加入如下代码:
<uses-library android:name="android.test.runner"/>
<instrumentation android:name="android.test.instrumentationTestRunner"
android:targetPackage="cn.hao.JunitTest" android:label="
Test for My App" />
引入的位置如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.hao.JunitTest" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name="cn.hao.test.MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="android.test.runner"/> </application> <instrumentation android:name="android.test.instrumentationTestRunner" android:targetPackage="cn.hao.JunitTest" android:label="App Test" /> </manifest> |
说明:在项目中使用单元测试,就是检查程序及处理结果的正确性。
第三步:新建一个类,测试业务类,代码如下:
package cn.hao.junit; import junit.framework.Assert; import cn.hao.service.PersonaService; import android.test.AndroidTestCase; public class PersonServiceTest extends AndroidTestCase { public void testSave() throws Exception { PersonaService service = new PersonaService();//new出测试对象 service.save(null); } public void testAdd() throws Exception { PersonaService service = new PersonaService(); int actual = service.add(1, 2); Assert.assertEquals(3, actual); } } |
注意:该类需继承单元测试框架父类android.test.AndroidTestCase类,测试方法最好是抛出异常给测试框架。方法Assert.assertEquals(3, actual)中参数3是期望(理论上)返回的果,actual是实际上返回的结果。
第四步:运行测试类
在大纲OutLine视图中,右击测试方法->Run As->Android Junit Test,会将项目自动部署到模拟器上,测试的结果会以颜色的形式显示,绿色表示方法正确,否则该方法不正确,Eclipse会给出详细的说明,根据帮助文档可以查看相应的错误信息。
如测试上述testSave()方法时,会给出如下提示:
当然,save()从第六位开始取子字符串,但是该方法现在的参数为null,会发生空指针异常。
软件安全的最大风险是检验工具及过程不透明的本质,以及不同的检验技术(例如自动化动态
测试)不能覆盖假阴性错误的潜在可能性。
尽管安全
软件开发生命周期(SDLC)有很多相关的最佳实践,但大多数组织依然有一种倾向,那就是主要依赖测试去构建安全的软件。当前的测试方法有一个最严重的副作用,即组织不太清楚哪些已经被其解决方案测试过,而(甚至更重要的是)还有哪些未被测试过。我们的研究表明,任何单一的自动化保证机制最多可以检验44%的安全需求。NIST 静态分析工具博览会发现,Tomcat中有26个已知的漏洞,但所有静态分析工具综合起来只报告了其中4个警告。因为依赖不透明的检验过程是一种普遍存在的习惯,甚至已经成为行业标准,因此许多组织在构建安全的软件时,满足于把测试作为最主要的手段。
举个例子,假设你雇用一家咨询公司为你的软件执行渗透测试。许多人将这种测试称为“黑盒”(基于同名的质保技术),测试人员没有详细的系统内部构件知识(比如系统代码)。执行测试之后,一成不变地生成一份报告,概括你应用中的几类漏洞。你修复了漏洞,然后提交应用做回归测试,下一份报告反馈说“已清除”——也就是说没有任何漏洞了。或者充其量仅仅告知你,你的应用在同一时间范围内不会被同样的测试人员以同样的方式攻破。但另一方面,它不会告诉你:
你的应用中还有哪些潜在的威胁?
你的应用中哪些威胁“其实不易受到攻击”?
你的应用中有哪些威胁未被测试人员评估?从运行期的角度来看,哪些威胁无法测试?
测试的时间及其他约束如何影响了结果的可靠性?例如,如果测试人员还有5天时间,他们还将执行哪些其他的
安全测试?
测试人员的技能水平有多高?你能否从不同的测试人员或者另一家咨询公司手中取得一组相同的结果?
以我们的经验来看,组织无法回答以上大多数问题。黑盒是两面的:一方面,测试人员不清楚应用的内部结构;而另一方面,申请测试的组织对自己软件的安全状况也缺乏了解。并不只是我们意识到了这个问题:Haroon Meer在44con上讨论了渗透测试的挑战。这些问题大多数都适用于任何形式的验证:自动化动态测试、自动化静态测试、手工渗透测试以及手工代码审查。实际上, 近期有一篇论文介绍了源代码审查中类似的挑战。
关于需求的实例
为了更好地说明这个问题,让我们看一些常见的高风险软件的安全需求,以及如何将常见的验证方法应用到这些需求上。
需求:使用安全的哈希算法(如SHA-2)和唯一的混淆值(salt value)去哈希(Hash)用户密码。多次迭代该算法。
在过去的一年里,LinkedIn、Last FM和Twitter发生了众所周知的密码泄露事件,对于此类缺陷,本条需求是具体地、合乎时宜的。
如何应用常见的验证方法:
自动化运行期测试:不可能访问已存的密码,因此无法使用此方法验证本需求
手工运行期测试:只有另一些开发导致已存密码转储时,才能使用此方法验证本需求。这并不是你所能控制的,因此你不能依靠运行期测试去验证本需求。
自动化静态分析:只有满足以下条件时,才可以用此方法验证本需求:
工具清楚身份认证是如何
工作的(例如,使用了标准的组件,就像Java Realms)
工具清楚应用程序使用了哪个特定的哈希算法
如果应用程序为每次哈希使用了唯一的混淆值,工具要清楚混淆算法和混淆值
实际上,认证有很多实现方法,指望静态分析方法全面地验证本需求是不切实际的。更为实际的方案是,使用工具简单地确认认证程序,并指出必须进行安全的哈希和混淆处理。另一个方案是,你来创建自定义规则,用以鉴定算法和哈希值,确认它们是否符合你专属的策略,尽管,在我们的经验中这种实践极为罕见。
手工代码审查:对于本需求,这是最可靠的常见验证方法。手工评估人员能够理解哪一段代码中发生了认证,验证哈希和混淆处理符合最佳实践。
SQL 注入是最具破坏性的应用漏洞之一。近期发现在
Ruby on Rails中有一个缺陷,在其技术栈上搭建的应用系统会受到SQL注入攻击。
如何应用常见的验证方法:
自动化运行期测试:虽然,运行期测试通过行为分析也许能够发现存在的SQL注入,但是,却不能证明没有SQL注入。因此,自动化运行期测试不能充分地验证本需求
手工运行期测试:与自动化运行期测试一样具有相同的局限性
自动化静态分析:通常能够验证本需求,特别是当你使用标准类库访问SQL数据库时。你是否将用户输入动态地拼接为SQL语句,还是使用正确地变量绑定,工具应该都可以分辨得出来。然而,这是有风险的,在以下场景中静态分析可能会漏掉SQL注入漏洞:
你在
数据库上使用存储过程,并且无法扫描数据库代码。在某些情况下,存储过程也易受到SQL注入
你使用了一种对象关系映射(ORM)类库,但你的静态分析工具不支持这种类库。对象关系映射也易受到注入。
你使用非标准的驱动或类库去连接数据库,并且驱动没有正确地实现常见地安全控制(比如预编译语句)
手工代码审查:与静态分析一样,手工代码审查能够确认没有SQL注入漏洞。然而,实际上产品应用中可能有几百或成千上万条SQL语句。手工审查每一条语句不仅非常耗时,而且容易出错。
需求:使用授权检查以确保用户无法查看其他用户的数据。
我们每年都能听到此类 漏洞新的事例。
如何应用常见的验证方法:
自动化运行期测试:通过访问两个不同用户数据的方式,使用一个用户的账号尝试访问另一个用户的数据,自动化工具能够在一定程度上完成本需求的测试。然而,这些工具不可能清楚一个用户账号的哪些数据是敏感的,也不了解把参数“data=account1”修改为“data=account2”表示违反了授权。
手工运行期测试:通常情况下,手工运行期测试是发现这类漏洞最有效的方法,因为人可以拥有必需的领域知识以探明这类攻击的位置。然而,在有些情况下,运行期测试人员可能无法全面掌握发现这类缺陷所必需的一切信息。例如,如果附加一个类似于“admin=true”的隐藏参数,使你可以不需授权检查就能访问其他用户的数据。
自动化静态分析:如果没有规则的定制,自动化工具通常发现不了这种类型的漏洞,因为它需要对领域的理解能力。例如,静态分析工具不清楚“data”参数表示条件信息,需要授权检查。
手工代码审查:手工代码审查能够揭露缺失授权的实体(译者注,比如代码),这是使用运行期测试难以发现的,比如添加一个“admin=true”的参数的影响。但是,实际上采用这种方式去验证是否做了授权检查费时费力。一处授权检查可能出现在许多不同部分的代码中,所以手工审查人员可能需要从头到尾追踪数条不同的执行路径,以检测是否做了授权。
对你的影响
验证的不透明的本质,意味着有效的软件安全需求的管理是必要的。对于已列出的需求,测试人员即可以明确他们已经评估一条具体的需求,也可以明确他们所用到的技术。评论家提出,渗透测试不应该遵循一张“类似于审计的检查表”,因为没有检查表可以覆盖模糊的范围和特定领域的漏洞。但是,要灵活地找到独特的问题,就不可避免地要确定已经充分理解了需求。这种情况与标准的软件质量保证(QA)非常相似:好的质保测试人员即能够验证功能需求,也能够思考盒子的边界,想办法去破坏功能。如果只是简单、盲目地测试,报告一些与功能需求无关的缺陷,就会显著降低质量保证的效用。那么为什么要接受较低标准的安全测试呢?
在你执行下一次安全验证活动之前,确保你有软件安全需求可用于衡量,并且你要明确属于验证范围内的需求。如果你雇佣手工渗透测试人员或源代码审查人员,他们就可以相对轻松地确定哪些需求是由他们来测试的。如果你使用某种自动化工具或服务,合作的供应商会表明,哪些需求无法用他们的工具或服务可靠地测试。你的测试人员、产品或服务不可能保证完全没有假阴性错误(例如,保证你的应用中不会受到SQL注入攻击),但同时也要理解,对它们做测试能够大大有助于增加你的自信心,有信心你的系统代码中不包含已知的、可预防的安全漏洞。
拿到一个发行版软件包后,通常要对软件包进行非对称加密验证(MD5)
首先查看公钥是否正常安装:
rpm -qa | grep gpg-pubkey 或者 rpm -qa gpg-pubkey
如果未正常安装,可先手动进行安装
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
或者使用安装介质的源,如:rpm --import /media/Rhel6.4/RPM-GPG-KEY-redhat-release
如果安装中提示错误:
[kevin@PandoraX Rhel6.4]$ rpm --import /media/Rhel6.4/RPM-GPG-KEY-redhat-release
error: cannot get exclusive lock on /var/lib/rpm/Packages
error: cannot open Packages index using db3 - Operation not permitted (1)
error: cannot open Packages database in /var/lib/rpm
error: /media/Rhel6.4/RPM-GPG-KEY-redhat-release: key 1 import failed.
error: cannot get exclusive lock on /var/lib/rpm/Packages
error: cannot open Packages database in /var/lib/rpm
error: /media/Rhel6.4/RPM-GPG-KEY-redhat-release: key 2 import failed.
很可能是由于权限问题造成,更新key需要root身份或者sudo身份进行操作
安装完成后可正常进行验证:
rpm -K vsftpd-2.2.2-11.el6.x86_64.rpm
vsftpd-2.2.2-11.el6.x86_64.rpm: rsa sha1 (md5) pgp md5 OK
验证通过
查看公钥信息rpm -qi gpg-pubkey-2fa658e0-45700c69
查看详细验证信息rpm -vK vsftpd-2.2.2-11.el6.x86_64.rpm
rpm -vvK vsftpd-2.2.2-11.el6.x86_64.rpm
yum源中的gpg校验
[base]
name=Red Hat Enterprise
Linux baseurl=file:///media/Rhel6.4/Server
enabled=1
gpgcheck=0 (0代表不进行校验,1为每次都进行校验)
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
编者按:在
数据库技术领域,Michael Stonebraker几乎是无人不知无人不晓的人物。现年70岁的Stonebraker不仅是Ingres和PostgreSQL的创始人,同时在Informix担任过技术总监。可以说,Stonebraker是关系型数据库技术从萌芽走向辉煌的见证人。他最新的项目VoltDB被视为是NewSQL数据库的代表,在他眼中,这种即拥有传统
SQL数据库血统,又能够适应
云计算时代分布式扩展的产品,才代表着数据库未来的发展方向。
在本文中,数据库老兵Michael Stonebraker阐述了他对SQL、NoSQL以及NewSQL技术的看法,并解读了为何NewSQL将对传统数据库市场带来最大的冲击。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
从已有的成功企业吸取经验,与最新的技术和趋势完美结合,这是一切初创企业走向成功的秘诀。
而NewSQL正好具备了这样的条件。NewSQL越来越受到了人们的关注,究其原因是它保留了过去30多年数据库技术的精华,同时将现代化的技术架构融入了进来。
那么是不是可以说“SQL已死”呢?
事实上,SQL技术非但没有消失,反而在大数据时代发挥了更重要的作用。当Facebook去年宣布推出Presto(海量数据查询引擎)时,我想起了关于NoSQL的一个梗:“Hive从什么时候就开始做SQLon Hadoop了?6年前?”没错,尽管NoSQL运动进展的火热,但不要忘记了,即使是最好的NoSQL平台也在很久以前就开始研究如何实现SQL了。
好的数据库设计师都明白一个道理,即数据库最大的商业价值就是让人与数据之间形成互动,而SQL是非常擅长实现这个目标的。经过了几十年的研究,调整,改进,生态系统建设,工具开发以及用户教育,SQL已经成为一个非常丰富且强大的数据库语言标准,它带动了价值上百亿美元的市场。无论是架构师还是DBA、开发人员都无法忽视它的价值。
但这并不意味着数据库领域就没有创新的空间,企业就应该永远锁定在遗留系统之上。
NoSQL运动的兴起让我们了解到,一个分布式,高容错,基于云的集群化数据库服务并不是天方夜谭。最早吃过NoSQL这个螃蟹的公司都是些不计代价来实现扩展性的公司,他们必须牺牲一定的互动性从而满足扩展需求。更关键的是,他们没有其他选择。当然,早期的用户没有多少有勇气做这种牺牲的。数据库市场需要一股新的力量,来帮助用户实现这一目标:能够快速地扩展从而获得驾驭快数据流的能力,提供实时的分析和实时的决策,具备云计算的能力,支持关键业务系统,还能够在更廉价的硬件设备上对历史数据分析性能提升100倍。
然而,实现这些目标并不需要我们重新定义已经成熟的SQL语言。NewSQL就是答案:它能够使用SQL语句来查询数据,同时具备现代化,分布式,高容错,基于云的集群架构。NewSQL结合了SQL丰富灵活的数据互动能力,以及针对大数据和快数据的实时扩展能力。
NoSQL厂商从来都不否认他们需要让自己的产品更成熟,他们也都了解SQL的价值。传统数据库厂商也面临着严峻的考验,尽管他们拥有良好的查询接口,但他们需要为自己的产品融入更多灵活、高性能的架构,从而满足客户在大数据时代的需求。
你好,今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多。我要讲讲回调(callbacks)。你知道什么时候用,怎么用这个吗?你真的理解了它在
java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因。这个背后的思想是控制反转( PS:维基百科的解释是控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。)这个范例描述了框架(framework)的
工作方式,也以“好莱坞原则:不要打电话给我们,我们会打给你("Hollywood principle - Don't call me, we will call you)”为人们所熟知。
简单的Java里的回调模式来理解它,具体的例子在下面:
1 interface CallBack { 2 void methodToCallBack(); 3 } 4 5 class CallBackImpl implements CallBack { 6 public void methodToCallBack() { 7 System.out.println("I've been called back"); 8 } 9 } 10 11 class Caller { 12 13 public void register(CallBack callback) { 14 callback.methodToCallBack(); 15 } 16 17 public static void main(String[] args) { 18 Caller caller = new Caller(); 19 CallBack callBack = new CallBackImpl(); 20 caller.register(callBack); 21 } 22 } |
你可能要问我,什么时候用这个或者会问直接调用和回调机制有什么不同呢?
答案是:好吧,这个例子仅仅向你展示了怎样在java环境中构造这样的回调函数。当然用那种方式使用它毫无意义。让我们现在更加深入具体地研究它。
在它之中的思想是控制反转。让我们用定时器作为现实中的例子。假设你知道,有一个特别的定时器支持每小时回调的功能。准确地说意思是,每小时,定时器会调用你注册的调用方法。
具体的例子:
我们想要每小时更新一次网站的时间,下面是例子的UML模型:
回调接口:
让我们首先定义回调接口:
1 import java.util.ArrayList; 2 import java.util.List; 3 4 // For example: Let's assume that this interface is offered from your OS to be implemented 5 interface TimeUpdaterCallBack { 6 void updateTime(long time); 7 } 8 9 // this is your implementation. 10 // for example: You want to update your website time every hour 11 class WebSiteTimeUpdaterCallBack implements TimeUpdaterCallBack { 12 13 @Override 14 public void updateTime(long time) { 15 // print the updated time anywhere in your website's example 16 System.out.println(time); 17 } 18 } |
在我们的例子中系统定时器支持回调方法:
1 // This is the SystemTimer implemented by your Operating System (OS) 2 // You don't know how this timer was implemented. This example just 3 // show to you how it could looks like. How you could implement a 4 // callback by yourself if you want to. 5 class SystemTimer { 6 7 List<TimeUpdaterCallBack> callbacks = new ArrayList<TimeUpdaterCallBack>(); 8 9 public void registerCallBackForUpdatesEveryHour(TimeUpdaterCallBack timerCallBack) { 10 callbacks.add(timerCallBack); 11 } 12 13 // ... This SystemTimer may have more logic here we don't know ... 14 15 // At some point of the implementaion of this SystemTimer (you don't know) 16 // this method will be called and every registered timerCallBack 17 // will be called. Every registered timerCallBack may have a totally 18 // different implementation of the method updateTime() and my be 19 // used in different ways by different clients. 20 public void oneHourHasBeenExprired() { 21 22 for (TimeUpdaterCallBack timerCallBack : callbacks) { 23 timerCallBack.updateTime(System.currentTimeMillis()); 24 } 25 } 26 } |
最后是我们虚拟简单的例子中的网站时间更新器:
1 // This is our client. It will be used in our WebSite example. It shall update 2 // the website's time every hour. 3 class WebSiteTimeUpdater { 4 5 public static void main(String[] args) { 6 SystemTimer SystemTimer = new SystemTimer(); 7 TimeUpdaterCallBack webSiteCallBackUpdater = new WebSiteTimeUpdaterCallBack(); 8 SystemTimer.registerCallBackForUpdatesEveryHour(webSiteCallBackUpdater); 9 } 10 } |