Archive

Posts Tagged ‘Java’

基于MINA构建简单高性能的NIO应用-优化指南

February 19th, 2010 Sparkle No comments

本文为Sparkle发于《程序员》2008年2月刊的文章,与《程序员》的协议,可以在个人博客中发布,转载请保留出处。

优化指南
MINA默认配置的性能并不是很高的,部分原因是MINA目前还保留初期版本的架构,另外一个原因是因为JVM的发展。

首先我们关闭默认的ThreadModel设置

IoAcceptor acceptor = ...;
IoServiceConfig acceptorConfig = acceptor.getDefaultConfig();
acceptorConfig.setThreadModel(ThreadModel.MANUAL);

ThreadModel是一个很简单的线程实现,用于IoService。但是它实在太弱,以至于在并发环境产生大量问题。在MINA 2.0中,ThreadModel直接被取消。你应该使用ExecutorFilter来实现线程。

然后我们增加I/O处理线程(Article by Sparkle)
每一个Acceptor/Connector都使用一个线程来处理连接,然后把连接发送给I/O processor进行读写操作,我们只可以修改I/O processor使用的线程数,用以下代码设置

IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());

当然是要将ExecutorFilter加入,上文已经很详细地描述了

acceptor.getDefaultConfig().getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());

笔者在开发过程中,多次遇到OutOfMemoryError,经过研究之后才发现原因。MINA默认是使用direct memory实现ByteBuffer池的方案(以下简称direct buffer),通过JNI在内存开辟一段空间来使用,该方案在早期的MINA版本中是一个非常好的特性,那是因为MINA开发初期,JVM并没有现在的强大,带有池效果的direct buffer性能比较好。但是当我们使用-Xms -Xmx等指令增加JVM可使用的内存,那仅仅增加了堆的内存空间,而direct memory的空间并没有增加,导致MINA实际使用的时候经常出现OutOfMemoryError。如果你的确想使用direct memory,可以通过-XX:MaxDirectMemorySize选项来设置。不过笔者不建议这样做,因为最新的测试表明,在现代的JVM里面,direct memory比堆的表现更差。这里可能有读者会觉得奇怪,为什么不用池,而要用堆呢,而且还需要gc。那是因为现在的JVM gc能力已经很强了,而且在并发环境里面,pool的同步也是一个性能的问题。我们可以通过这样的代码进行设置(Article by Sparkle)

ByteBuffer.setUseDirectBuffers(false);
ByteBuffer.setAllocator(new SimpleByteBufferAllocator());

MINA 2.0已经默认把直接内存分配改成堆,为了提供最好的性能和稳定性。

最后一条优化技巧就是,把你的应用部署在Linux上,并且打开Java NIO使用Linux epoll的功能。可能你还没听过epoll,但是你应该听过Lighttpd、Nginx、Squid等,得益于epoll,它们提供很高的网络性能,还占用非常少的系统资源。JDK6已经默认把epoll配置打开,因此笔者建议把你的应用部署在JDK6上面,也同时因为JDK6还有别的优化特性。如果你的应用必须部署在JDK5上,你也可以通过参数把epoll支持打开。

文章快速索引

  1. 前言
  2. 一个简单的例子
  3. MINA架构
  4. 优化指南
Categories: Java Tags: ,

基于MINA构建简单高性能的NIO应用-MINA架构

February 19th, 2010 Sparkle No comments

本文为Sparkle发于《程序员》2008年2月刊的文章,与《程序员》的协议,可以在个人博客中发布,转载请保留出处。

MINA架构

这里,我借用了一张Trustin Lee在Asia 2006的ppt里面的图片来介绍MINA的架构。

Remote Peer就是客户端,而下方的框是MINA的主要结构,各个框之间的箭头代表数据流向。
大家可以对比刚刚的例子来看这个架构图,IoService就是整个MINA的入口,负责底层的IO操作,客户端发过来的消息就是由它处理。刚刚我们使用的IoAcceptor就是一个IoService,之所以抽象成IoService,是因为MINA用同样的架构来处理服务器和客户端编程,IoService的另一个子类就是IoConnector,用于客户端。不过根据笔者的使用经验,使用非阻塞的模型进行客户端编程非常的不方便,你最好寻求其他的阻塞通讯框架。
IoService把数据转化成一个一个的事件,传递给IoFilterChain。你可以加入一连串的IoFilter,进行各种功能。笔者的建议是将一些功能性的,业务不相关的代码,用IoFilter来实现,使得整个应用结构更清晰,也方便代码重用。(Article by Sparkle)
被IoFilter处理过的事件,发送给 IoHandler,然后我们在这里实现具体的业务逻辑。这个部分很简单,如果你有Swing的使用经验的话,你会发现它跟Swing的事件非常相像,你要做的事情,仅仅是重载你需要的方法,然后编写具体的业务功能。在这其中,最重要的一个方法就是messageReceived了。
值得留意的是一个IoSession的类,每一个IoSession实例代表这一个连接,我们需要对连接进行的任何操作都通过这个类来实现。
从IoHandler通过调用IoSession.write等方法向客户端发送的消息,会通过跟输入数据相反的次序依次传递,直至由IoService负责把数据发送给客户端。
这就已经是MINA的全部,是不是很简单。

接下来,我会详细介绍我们编写具体代码的时候主要涉及到的三个类,IoHandler、IoSession和IoFilter。

IoHandler

public interface IoHandler {
  void sessionCreated(IoSession session) throws Exception;
  void sessionOpened(IoSession session) throws Exception;
  void sessionClosed(IoSession session) throws Exception;
  void sessionIdle(IoSession session, IdleStatus status) throws Exception;
  void exceptionCaught(IoSession session, Throwable cause) throws Exception;
  void messageReceived(IoSession session, Object message) throws Exception;
  void messageSent(IoSession session, Object message) throws Exception;
}

MINA的内部实现了一个事件模型,而IoHanlder则是所有事件最终产生响应的位置。每一个方法的名字很明确表明该事件的含义。messageReceived是接收客户端消息的事件,我们应该在这里实现业务逻辑。messageSent是服务器发送消息的事件,一般情况下我们不会使用它。sessionClosed是客户端断开连接的事件,可以在这里进行一些资源回收等操作。值得留意的是,客户端连接有两个事件,sessionCreated和sessionOpened,两者稍有不同,sessionCreated是由I/O processor线程触发的,而sessionOpened在其后,由业务线程触发的,由于MINA的I/O processor线程非常少,因此如果我们真的需要使用sessionCreated,也必须是耗时短的操作,一般情况下,我们应该把业务初始化的功能放在sessionOpened事件中。(Article by Sparkle)
细心的读者可能会发现,我们刚刚的例子继承的是IoHandlerAdapter,IoHandlerAdapter其实就是一个IoHanlder的空的实现,这样我们就可以不用重载不感兴趣的事件。

IoSession
IoSession是一个接口,MINA里很多的地方都使用接口,很好地体现了面向接口编程的思想。它提供了对当前连接的操作功能,还有用户定义属性的存储功能,这点非常重要。IoSession是线程安全的,也就是我们能够在多线程环境中随意操作IoSession,这点给开发带来很大的好处。我们来看看具体提供的方法,笔者列举一些比较常用和重要的方法

WriteFuture write(Object message)
CloseFuture close();
 
Object getAttribute(String key);
Object setAttribute(String key, Object value);
Object removeAttribute(String key);
Set getAttributeKeys();
 
boolean isConnected();
boolean isClosing();
SocketAddress getRemoteAddress();
boolean isIdle(IdleStatus status);

在这里,笔者把IoSession的方法大致分成三类
第一类,连接操作功能。
最主要的方法有两个,向客户端发送消息和断开连接。可以看的出,write接受的变量是一个Object,但是实际上应该传入什么类型呢?具体还得看你是否使用了ProtocolCodecFilter(下面会详细介绍),如果使用了ProtocolCodecFilter,那这个message将可能是一个String,或者是一个用户定义的JavaBean。默认的情况,message是一个ByteBuffer。ByteBuffer是MINA的一个类,跟java.nio.ByteBuffer类同名,MINA 2.0将会将它改成IoBuffer,以避免讨论上的误会。(Article by Sparkle)
另一个值得留意的是Future类,MINA是一个非阻塞的通信框架,其中一个明显的体现就是调用IoSession.write方法是不会阻塞的。用户调用了write方法之后,消息内容会发到底层等候发送,至于什么时候发出,就不得而知了。当然,实际上调用了write之后,数据几乎是立刻发出的,这得益与NIO的高性能。但是,如果我们必须确认了消息发出,然后进行某些处理,我们就需要使用Future类,以下是一个很常见的代码

IoSession session = ...;
WriteFuture future = session.write(...);
// Wait until the message is completely written out to the O/S buffer.
future.join();
if( future.isWritten() )  {
  // The message has been written successfully.
}  else  {
  // The messsage couldn't be written out completely for some reason.
  // (e.g. Connection is closed)
}

通过调用future.join,程序就会阻塞,直至消息处理结束。我们还能通过future.isWritten得知消息是否成功发送。
在这里,笔者顺便说一个实际使用的发现,消息发送是会自动合并的,简单来说,如果在很短的时间里,对同一个IoSession进行了两次write操作,客户端有可能只收到一条消息,而这条消息就是服务器发出的两条消息前后接起来。这样的设计可以在高并发的时候节省网络开销,而笔者的实际使用过程中,效果也相当好。但是如果这样行为会导致客户端工作不正常,你也可以通过参数关闭它。

第二类,属性存储操作。
通常来说,我们的系统是有用户状态的,我们就需要在连接上存储用户属性,IoSession的Attribute就是这样一个功能。例如两个连接同时连入服务器,一个连接是用户A,用户ID是13,另一个连接是用户B,用户ID是14,我们就可以在用户登录成功之后,调用IoSession.setAttribute(“login_id”,13),然后在其后的操作中,通过IoSession.getAttribute(“login_id”)获得当前登录用户ID,并进行相应的操作。简单来说,就是一个类似HttpSession的功能,当然具体的实现方法不一样。(Article by Sparkle)

第三类,连接状态。
这里就不多说了,从方法名上我们就能知道它具体的功能。

IoFilter
过滤器是MINA的一个很重要的功能。IoFilter也是一个接口,但是相对比较复杂,这里就不列举它的方法了。简单来说IoFilter就像ServletFilter,在事件被IoHandler处理之前或之后进行一些特定的操作,但是它比ServletFilter复杂,可以处理很多种事件,除了包括IoHandler的7个事件以外,还有一些内部的事件可以进行操作。
MINA提供了一些常用的IoFilter实现,例如有LoggingFilter(日志功能)、BlacklistFilter(黑名单功能)、CompressionFilter(压缩功能)、SSLFilter(SSL支持),这些过滤器比较简单,通过阅读它们的源代码,能够更进一步理解过滤器的实现。笔者在这里要重点介绍两个过滤器,ProtocolCodecFilter和ExecutorFilter

ProtocolCodecFilter
网络传输的内容其实本质是一个二进制流,但是我们的业务功能不会,或者说不应该去直接操作二进制流。MINA默认向IoHandler传入的message是一个ByteBuffer,如果我们直接在IoHandler操作ByteBuffer,会导致大量协议分析的代码和实际的业务代码混杂在一起。最适合的做法,就是在IoFilter把ByteBuffer转换成String或者JavaBean,ProtocolCodecFilter正是这样的一个功能的过滤器。
使用ProtocolCodecFilter很简单,我们只要把ProtocolCodecFilter加入到FilterChain就可以了,但是我们需要提供一个ProtocolCodecFactory。其实ProtocolCodecFilter仅仅是实现了过滤器部分的功能,它会将最终的转换工作,交给从ProtocolCodecFactory获得的Encode和Decode。如果我们需要编写自己的ProtocolCodec,就应该从ProtocolCodecFactory入手。MINA内置了几个ProtocolCodecFactory,比较常用的就是ObjectSerializationCodecFactory和TextLineCodecFactory。(Article by Sparkle)
ObjectSerializationCodecFactory是Java Object序列化之后的内容直接跟ByteBuffer互相转化,比较适合两端都是Java的情况使用。TextLineCodecFactory就是String跟ByteBuffer的转化,说白了就是文本,例如你要实现一个SMTP服务器,或者POP服务器,就可以使用它。而笔者的实际使用,大多数情况都是使用TextLineCodecFactory。
这里提及一下IoFilter的顺序问题,IoFilter是有加入顺序的,例如,先加入LoggingFilter再加入ProtocolCodecFilter,和先加入ProtocolCodecFilter再加入LoggingFilter的效果是不一样的,前者LoggingFilter写入日志的内容是ByteBuffer,而后者写入日志的是转换后具体的类,例如String。实际使用的时候,一定要处理好过滤器的顺序。

ExecutorFilter
另一个重要的过滤器就是ExecutorFilter。这里,我需要先说明一下MINA的线程工作模式,MINA默认是单线程处理所有客户端的消息,也就是说,即使你在一台8CPU的机器上面跑,可能也只用到一个CPU,另外,如果某次消息处理太耗时,就会导致其他消息等待,整体的吞吐量下降。很多朋友抱怨MINA的性能差,其实是因为他们没有加入ExecutorFilter的缘故。ExecutorFilter设计的很精巧,大家可以仔细阅读一下源代码,它会将同一个连接的消息合并起来按顺序调用,不会出现两个线程同时处理同一个连接的情况。(Article by Sparkle)
这里再次提及IoFitler的顺序问题,一般情况下,我们会将ExecutorFilter放在ProtocolCodecFilter之后,因为我们不需要多线程地执行ProtocolCodec操作,用单一线程来进行ProtocolCodec性能会比较高,而具体的业务逻辑可能还设计数据库操作,因此更适合放在不同的线程中运行。

文章快速索引

  1. 前言
  2. 一个简单的例子
  3. MINA架构
  4. 优化指南
Categories: Java Tags: ,

基于MINA构建简单高性能的NIO应用-一个简单的例子

February 19th, 2010 Sparkle No comments

本文为Sparkle发于《程序员》2008年2月刊的文章,与《程序员》的协议,可以在个人博客中发布,转载请保留出处。

一个简单的例子

MINA使用非常简单,笔者以前做过一段时间传统的Java Socket开发,不过一直对Java NIO不是很理解,但是MINA很快就上手了,MINA封装了NIO繁琐的部分,使你可以更专注于业务功能实现。话不多说,让我们来看一个简单的例子,一个很常见的例子,时间服务器。(Article by Sparkle)
我们的实现目标是一个能响应多个客户端的请求,然后返回服务器当前的系统时间的功能。传统的Java Socket程序,我们需要每accept一个客户端连接,就创建一个新的线程来响应,这会令到系统整体负载能力有较大的限制,而且我们必须手工编写连接管理等代码。让我们来看看MINA是怎么处理的。

首先我们从官方网站下载MINA 1.1,这里我们假设JDK为1.5以上的版本,如果你使用的是JDK 1.4,请下载MINA 1.0,MINA 1.0跟1.1几乎一样,但是强烈建议使用JDK 1.5以上以获得更好的性能。
解开压缩包之后,能看见很多jar包,这里暂不介绍每个包的具体作用,可以把所有包都导入项目。值得留意的是MINA使用了一个slf4j的日志库,该日志库大有取缔common-logging之势。

public class TimeServer {
  public static void main(String[] args) throws IOException {
    IoAcceptor acceptor = new SocketAcceptor();
 
    SocketAcceptorConfig cfg = new SocketAcceptorConfig();
    cfg.getFilterChain().addLast( "logger", new LoggingFilter() );
    cfg.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory()));
 
    acceptor.bind( new InetSocketAddress(8123), new TimeServerHandler(), cfg);
    System.out.println("Time server started.");
  }
}

这里是我们的主程序,非常简单。
首先我们需要一个IoAcceptor,这里我们选择了一个SocketAcceptor,也就是TCP协议。
然后,我们给应用加上日志过滤器和协议编码过滤器。(Article by Sparkle)
最后,我们把acceptor bind到本机的8123端口,并且使用TimeServerHandler来实现协议。

TimeServerHandler是我们实现具体业务功能的地方。

public class TimeServerHandler extends IoHandlerAdapter {
  public void messageReceived(IoSession session, Object msg) throws Exception {
    String str = (String) msg;
    if( "quit".equalsIgnoreCase(str) ) {
    session.close();
    return;
  }
 
  Date date = new Date();
  session.write( date.toString() );
  System.out.println("Message written...");
  }
 
  public void sessionCreated(IoSession session) throws Exception {
  System.out.println("Session created...");
  }
}

IoHandlerAdapter提供了7个事件方法,我们要做的事情仅仅是挑选我们需要做出响应的事件进行重载。在我这个例子了,我重载了两个方法。sessionCreated会在客户端连接的时候调用,通常我们会在这里进行一些初始化操作,我这里仅仅是打印一条信息。messageReceived就是整个Handler的中心部分,每一个从客户端发过来的消息都会转化成对该方法的调用。由于我们加入了协议编码过滤器,因此这里获得的Object msg是一个String,而不是默认的ByteBuffer(下文会详细介绍ProtocolCodecFilter)。这里我们实现了一个很简单的业务功能,如果用户输入的是quit,就断开连接,否则就输入当前时间。可以看出,IoSession封装了对当前连接的操作。

至此,我们就实现了一个时间服务器。

文章快速索引

  1. 前言
  2. 一个简单的例子
  3. MINA架构
  4. 优化指南
Categories: Java Tags: ,

基于MINA构建简单高性能的NIO应用-前言

February 19th, 2010 Sparkle No comments

本文为Sparkle发于《程序员》2008年2月刊的文章,与《程序员》的协议,可以在个人博客中发布,转载请保留出处。

前言

MINA是Trustin Lee最新制作的Java通讯框架。通讯框架的主要作用是封装底层IO操作,提供高级的操作API。比较出名的通讯框架有C++的ACE、Python的Twisted,而Java的通讯框架还有QuickServer、Netty2、Cindy、Grizzly等。

2004年6月,Trustin Lee发布了一个通讯框架Netty2,是Java界第一个事件模型架构的通讯框架,Cindy也从中借鉴了不少思想。由于Netty2的架构不是很好,Trustin Lee在2004年底加入Apache Directory组之后,重写了整个框架,取名为MINA。MINA是一个基于Java NIO的通讯框架,Java从1.4开始引入NIO,提供了一个非阻塞、高性能的IO底层。

目前使用MINA的产品并不是很多,比较出名的就有Apache Directory(作者就在为这个项目效力)、Openfire(Jive出品的一个XMPP产品)、red5(研究flash流媒体flv技术的朋友应该很清楚这个东西,adobe fms的竞争者,国内也有视频网站在使用)等等。 (Article by Sparkle)

笔者在07年初的时候,公司新项目需要用Java实现一个Socket Server,对比了Netty2、Cindy、QuickServer和MINA。当时Netty2已经停止开发,也找不到官方网站和代码,比较了另外三个框架之后,毅然选择了当时文档比较缺乏和使用群较少的MINA,一年以来的使用经验来看,感觉还是很不错的,MINA有着清晰的架构,很方便做自定义的扩充。在1.0发布之后,官方网站充实了很多,增加了不少文档,也听到越来越多的朋友开始使用MINA。后来专门针对JDK 1.5发布了1.1的版本,使用JDK内置的concurrent代替backport-util-concurrent。目前1.0和1.1同时存在,但已经不再增加新功能,仅仅发布bug fix的版本,新功能都在2.0中实现,2.0调整了架构,性能有更大的提升,目前还在开发中。

基本特性

  • 通过Java NIO支持TCP和UDP协议,另外还支持RS232和VM内通讯。由于MINA有清晰的架构,你也能很简单地实现一个底层网络协议。目前不支持阻塞IO,似乎还没有计划支持,当然你可以在其之上实现一个阻塞的模型,不过按照笔者的经验来说,非阻塞IO更适合Server端编程。
  • 一个类似ServletFilter的过滤器模型。这是笔者认为MINA的精髓所在,通过引入过滤器模型,可以将一些非业务的功能独立开来,层次更清晰,很有AOP的思想,可以很方便地进行日志、协议转换、压缩等等功能,还能在运行中动态增加或去掉功能。(Article by Sparkle)
  • 可以直接使用底层的ByteBuffer,也可以使用用户定义的消息Object和编码方式。
  • 高度可定制的线程模型,单线程、一个线程池,或者类似SEDA的多个线程池。
  • SSL支持,攻击防御和流量控制,mock测试友好,JMX支持,Spring集成,你还需要更多吗。

文章快速索引

  1. 前言
  2. 一个简单的例子
  3. MINA架构
  4. 优化指南
Categories: Java Tags: ,

如何用Java进行3DES加密解密

January 5th, 2008 Sparkle 1 comment

最近一个合作商提出使用3DES交换数据,本来他们有现成的代码,可惜只有.net版本,我们的服务器都是Linux,而且应用都是Java。于是对照他们提供的代码改了一个Java的版本出来,主要是不熟悉3DES,折腾了一天,终于搞定。

所谓3DES,就是把DES做三次,当然不是简单地DES DES DES就行了,中途有些特定的排列。这个我可不关心,呵呵,我的目的是使用它。

在网上搜索了一下3DES,找到很少资料。经过朋友介绍,找到GNU Crypto和Bouncy Castle两个Java扩充包,里面应该有3DES的实现吧。

从GNU Crypto入手,找到一个TripleDES的实现类,发现原来3DES还有一个名字叫DESede,在网上搜索TripleDES和DESede,呵呵,终于发现更多的资料了。

Java的安全API始终那么难用,先创建一个cipher看看算法在不在吧

Cipher cipher = Cipher.getInstance("DESede");

如果没有抛异常的话,就证明这个算法是有效的

突然想看看JDK有没有内置DESede,于是撇开Crypto,直接测试,发现可以正确运行。在jce.jar里面找到相关的类,JDK内置了。

于是直接用DES的代码来改&测试,最后代码变成这样

SecureRandom sr = new SecureRandom();
DESedeKeySpec dks = new DESedeKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));

需要留意的是,要使用DESede的Spec、Factory和Cipher才行

事情还没完结,合作商给过来的除了密钥之外,还有一个IV向量。搜索了一下,发现有一个IvParameterSpec类,于是代码变成这样

SecureRandom sr = new SecureRandom();
DESedeKeySpec dks = new DESedeKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey securekey = keyFactory.generateSecret(dks);
IvParameterSpec iv = new IvParameterSpec(PASSWORD_IV.getBytes());
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, securekey, iv, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));

但是,运行报错了

java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV

ECB是什么呢?我的代码完全没有写ECB什么的

又上网搜索,结果把DES的来龙去脉都搞清楚了

http://www.tropsoft.com/strongenc/des.htm

ECB是其中一种字串分割方式,除了DES以外,其他加密方式也会使用这种分割方式的,而Java默认产生的DES算法就是用ECB方法,ECB不需要向量,当然也就不支持向量了

除了ECB,DES还支持CBC、CFB、OFB,而3DES只支持ECB和CBC两种

http://www.tropsoft.com/strongenc/des3.htm

CBC支持并且必须有向量,具体算法这里就不说了。合作商给的.net代码没有声明CBC模式,似乎是.net默认的方式就是CBC的

于是把模式改成CBC

Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");

成功运行了

后话:

搜索的过程中,找到一个不错的讨论

http://www.lslnet.com/linux/dosc1/21/linux-197579.htm

在CBC(不光是DES算法)模式下,iv通过随机数(或伪随机)机制产生是一种比较常见的方法。iv的作用主要是用于产生密文的第一个block,以使最终生成的密文产生差异(明文相同的情况下),使密码攻击变得更为困难,除此之外iv并无其它用途。因此iv通过随机方式产生是一种十分简便、有效的途径。此外,在IPsec中采用了DES-CBC作为缺省的加密方式,其使用的iv是通讯包的时间戳。从原理上来说,这与随机数机制并无二致。

看来,向量的作用其实就是salt

最大的好处是,可以令到即使相同的明文,相同的密钥,能产生不同的密文

例如,我们用DES方式在数据保存用户密码的时候,可以另外增加一列,把向量同时保存下来,并且每次用不同的向量。这样的好处是,即使两个用户的密码是一样的,数据库保存的密文,也会不一样,就能降低猜测的可能性

另外一种用法,就是类似IPsec的做法,两部主机互传数据,保证两部机的时钟同步的前提下(可以取样到分钟或更高的单位避免偏差),用时钟的变化值作为向量,就能增加被sniffer数据的解密难度

Categories: Uncategorized Tags: ,

如何用Java进行DES加密解密

January 5th, 2008 Sparkle 2 comments

这篇其实是引子,直接贴代码,不多解释了

SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));
SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return new String(cipher.doFinal(Hex.decodeHex(str.toCharArray())));
Categories: Uncategorized Tags: ,

PageCacheFilter2

December 31st, 2007 Sparkle No comments
/**
 * 2006-5-1
 * @author Sparkle
 */
public class PageCacheFilter2 implements Filter {
	private Set<string> cacheUrlSet = new HashSet<string>();
	private Set<string> scacheUrlSet = new HashSet<string>();
	private String baseCachePath, contentType;
 
	public void init(FilterConfig config) throws ServletException {
		String cacheUrl = config.getInitParameter("cacheUrl");
		if (cacheUrl != null) {
			StringTokenizer tk = new StringTokenizer(cacheUrl);
			while (tk.hasMoreTokens()) {
				String str = tk.nextToken().trim().toLowerCase();
				if (str.endsWith("?")) {
					str = str.substring(0, str.length() - 1);
					scacheUrlSet.add(str);
				}
				cacheUrlSet.add(str);
			}
		}
		baseCachePath = config.getInitParameter("cachePath");
		contentType = config.getInitParameter("contentType");
	}
 
	private int urlHashCode(HttpServletRequest request) {
		String uri = request.getRequestURI();
		String qStr = request.getQueryString();
		if (qStr != null) {
			uri += ('?' + qStr);
		}
		return Math.abs(uri.hashCode());
	}
 
	private String cacheFilePath(HttpServletRequest request) {
		String uri = request.getRequestURI();
		int hash = urlHashCode(request);
		if (scacheUrlSet.contains(uri.toLowerCase())) {
			String entityId = request.getParameter("entityId");
			if (StringUtils.isNotBlank(entityId)) {
				return baseCachePath + "/article/" + subDir(entityId)
						+ '/' + entityId + '/' + hash + ".wml";
			}
		}
		return baseCachePath + "/other/" + subDir2(String.valueOf(hash))
				+ '/' + hash + ".wml";
	}
 
	private String subDir(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(0, length - 4);
		}
		return "0";
	}
 
	private String subDir2(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(length - 4, length);
		}
		return str;
	}
 
	private void clearCache(HttpServletRequest request, File cacheFile) {
		if (scacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			File cacheDir = cacheFile.getParentFile();
			for (File file : cacheDir.listFiles()) {
				file.delete();
			}
			cacheDir.delete();
		} else {
			cacheFile.delete();
		}
	}
 
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		if (!cacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			chain.doFilter(req, res);
			return;
		}
 
		HttpServletResponse response = (HttpServletResponse) res;
 
		File cacheFile = new File(cacheFilePath(request));
		if (cacheFile.isFile()) {
			if (request.getParameter("refresh") != null) {
				clearCache(request, cacheFile);
			} else {
				try {
					BufferedReader reader = new BufferedReader(
							new FileReader(cacheFile));
					response.setContentType(contentType);
					String line;
					PrintWriter printWriter = response.getWriter();
					while ((line = reader.readLine()) != null) {
						printWriter.println(line);
					}
					printWriter.close();
					return;
				} catch (IOException e) {
					// ignore
					response.reset();
					System.out.println("read cache file error");
				}
			}
 
		}
 
		StringWriter strWriter = new StringWriter();
		boolean[] support = new boolean[1];
		support[0] = true;
		chain.doFilter(req, new ContentResponseWrapper(response, strWriter,
				support));
		if (support[0] && !response.isCommitted()) {
			String content = strWriter.getBuffer().toString();
 
			PrintWriter printWriter = response.getWriter();
			printWriter.println(content);
			printWriter.close();
 
			File cacheDir = cacheFile.getParentFile();
			if (!cacheDir.exists()) {
				cacheDir.mkdirs();
			}
			PrintWriter writer = new PrintWriter(cacheFile);
			writer.println(content);
			writer.close();
		}
 
	}
 
	public void destroy() {
	}
 
}

代码点评:这段代码的作用是,对指定的url进行cache,用了ContentRewriteFilter一样的做法来获取内容,因为具体的细节有些区别,没有继承ContentRewriteFilter而是直接编写代码。先判断是否有cache文件,如果有就输出给用户,但是如果连接上有refresh参数的话,就会清掉cache文件,如果没有cache文件,就会获取Servlet or JSP输出的内容,原原本本保存到磁盘,供下次用户访问使用。如果设置URL的时候,最后一位是问号,就会进入scacheUrlSet,这个是用于文章类型,主要的不同点是有分页,如果要refresh的话,应该要把所有分页的cache文件都清除。至于类名后面有个2,是因为这个是第二个版本。

(最近在考虑用Squid+Etag来实现一样的功能)

Categories: Uncategorized Tags: ,