﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-苍穹之铁十字-随笔分类-Java</title><link>http://www.blogjava.net/kuxiaoku/category/21186.html</link><description>收藏</description><language>zh-cn</language><lastBuildDate>Sat, 21 Jul 2007 17:06:18 GMT</lastBuildDate><pubDate>Sat, 21 Jul 2007 17:06:18 GMT</pubDate><ttl>60</ttl><item><title>Java正则表达式详解</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/07/21/131668.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Sat, 21 Jul 2007 14:18:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/07/21/131668.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/131668.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/07/21/131668.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/131668.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/131668.html</trackback:ping><description><![CDATA[<strong>仙人掌工作室</strong><br>如果你曾经用过Perl或任何其他内建正则表达式支持的语言，你一定知道用正则表达式处理文本和匹配模式是多么简单。如果你不熟悉这个术语，那么&#8220;正则表达式&#8221;（Regular Expression）就是一个字符构成的串，它定义了一个用来搜索匹配字符串的模式。
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            许多语言，包括Perl、PHP、Python、JavaScript和JScript，都支持用正则表达式处理文本，一些文本编辑器用正则表达式实现高
            级&#8220;搜索-替换&#8221;功能。那么Java又怎样呢？本文写作时，一个包含了用正则表达式进行文本处理的Java规范需求（Specification
            Request）已经得到认可，你可以期待在JDK的下一版本中看到它。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            然而，如果现在就需要使用正则表达式，又该怎么办呢？你可以从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识，然后以Jakarta-ORO API为例介绍如何使用正则表达式。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong><font size="4">
            一、正则表达式基础知识</font></strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            我们先从简单的开始。假设你要搜索一个包含字符&#8220;cat&#8221;的字符串，搜索用的正则表达式就是&#8220;cat&#8221;。如果搜索对大小写不敏感，单词&#8220;catalog&#8221;、&#8220;Catherine&#8221;、&#8220;sophisticated&#8221;都可以匹配。也就是说： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_a.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            1.1 句点符号</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            假设你在玩英文拼字游戏，想要找出三个字母的单词，而且这些单词必须以&#8220;t&#8221;字母开头，以&#8220;n&#8221;字母结束。另外，假设有一本英文字典，你可以用正则表达式
            搜索它的全部内容。要构造出这个正则表达式，你可以使用一个通配符——句点符号&#8220;.&#8221;。这样，完整的表达式就是&#8220;t.n&#8221;，它匹配&#8220;tan&#8221;、
            &#8220;ten&#8221;、&#8220;tin&#8221;和&#8220;ton&#8221;，还匹配&#8220;t#n&#8221;、&#8220;tpn&#8221;甚至&#8220;t
            n&#8221;，还有其他许多无意义的组合。这是因为句点符号匹配所有字符，包括空格、Tab字符甚至换行符： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_b.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            1.2 方括号符号</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            为了解决句点符号匹配范围过于广泛这一问题，你可以在方括号（&#8220;[]&#8221;）里面指定看来有意义的字符。此时，只有方括号里面指定的字符才参与匹配。也就是
            说，正则表达式&#8220;t[aeio]n&#8221;只匹配&#8220;tan&#8221;、&#8220;Ten&#8221;、&#8220;tin&#8221;和&#8220;ton&#8221;。但&#8220;Toon&#8221;不匹配，因为在方括号之内你只能匹配单个字
            符： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_c.jpg" border="0"></td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            1.3 &#8220;或&#8221;符号</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            如果除了上面匹配的所有单词之外，你还想要匹配&#8220;toon&#8221;，那么，你可以使用&#8220;|&#8221;操作符。&#8220;|&#8221;操作符的基本意义就是&#8220;或&#8221;运算。要匹配
            &#8220;toon&#8221;，使用&#8220;t(a|e|i|o|oo)n&#8221;正则表达式。这里不能使用方扩号，因为方括号只允许匹配单个字符；这里必须使用圆括号&#8220;()&#8221;。圆括
            号还可以用来分组，具体请参见后面介绍。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_d.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            1.4 表示匹配次数的符号</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            表一显示了表示匹配次数的符号，这些符号用来确定紧靠该符号左边的符号出现的次数： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4n.jpg" border="0"></p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中，连字符
            （&#8220;-&#8221;）有着特殊的意义，它表示一个范围，比如从0到9。因此，匹配社会安全号码中的连字符号时，它的前面要加上一个转义字符&#8220;\&#8221;。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4a.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图一：匹配所有123-12-1234形式的社会安全号码</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            假设进行搜索的时候，你希望连字符号可以出现，也可以不出现——即，999-99-9999和999999999都属于正确的格式。这时，你可以在连字符号后面加上&#8220;？&#8221;数量限定符号，如图二所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4b.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图二：匹配所有123-12-1234和123121234形式的社会安全号码</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分&#8220;[0-9]{4}&#8221;，再加上字母部分&#8220;[A-Z]{2}&#8221;。图三显示了完整的正则表达式。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4c.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图三：匹配典型的美国汽车牌照号码，如8836KV</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            1.5 &#8220;否&#8221;符号 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#8220;^&#8221;符号称为&#8220;否&#8221;符号。如果用在方括号内，&#8220;^&#8221;表示不想要匹配的字符。例如，图四的正则表达式匹配所有单词，但以&#8220;X&#8221;字母开头的单词除外。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4d.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图四：匹配所有单词，但&#8220;X&#8221;开头的除外</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            1.6 圆括号和空白符号 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            假设要从格式为&#8220;June 26, 1951&#8221;的生日日期中提取出月份部分，用来匹配该日期的正则表达式可以如图五所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4e.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图五：匹配所有Moth DD,YYYY格式的日期</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            新出现的&#8220;\s&#8221;符号是空白符号，匹配所有的空白字符，包括Tab字符。如果字符串正确匹配，接下来如何提取出月份部分呢？只需在月份周围加上一个圆括号创建一个组，然后用ORO API（本文后面详细讨论）提取出它的值。修改后的正则表达式如图六所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4f.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图六：匹配所有Month DD,YYYY格式的日期，定义月份值为第一个组</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            1.7 其它符号</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            为简便起见，你可以使用一些为常见正则表达式创建的快捷符号。如表二所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            表二：常用符号 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4o.jpg" border="0"> </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            例如，在前面社会安全号码的例子中，所有出现&#8220;[0-9]&#8221;的地方我们都可以使用&#8220;\d&#8221;。修改后的正则表达式如图七所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4g.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">图七：匹配所有123-12-1234格式的社会安全号码</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong><font size="4">
            二、Jakarta-ORO库</font></strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            有许多源代码开放的正则表达式库可供Java程序员使用，而且它们中的许多支持Perl
            5兼容的正则表达式语法。我在这里选用的是Jakarta-ORO正则表达式库，它是最全面的正则表达式API之一，而且它与Perl
            5正则表达式完全兼容。另外，它也是优化得最好的API之一。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            Jakarta-ORO库以前叫做OROMatcher，Daniel Savarese大方地把它赠送给了Jakarta Project。你可以按照本文最后参考资源的说明下载它。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            我首先将简要介绍使用Jakarta-ORO库时你必须创建和访问的对象，然后介绍如何使用Jakarta-ORO API。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            ▲ PatternCompiler对象</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            首先，创建一个Perl5Compiler类的实例，并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现，允许你把正则表达式编译成用来匹配的Pattern对象。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_e.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            ▲ Pattern对象</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            要把正则表达式编译成Pattern对象，调用compiler对象的compile()方法，并在调用参数中指定正则表达式。例如，你可以按照下面这种方式编译正则表达式&#8220;t[aeio]n&#8221;： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_f.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            默认情况下，编译器创建一个大小写敏感的模式（pattern）。因此，上面代码编译得到的模式只匹配&#8220;tin&#8221;、&#8220;tan&#8221;、 &#8220;ten&#8221;和&#8220;ton&#8221;，但不匹配&#8220;Tin&#8221;和&#8220;taN&#8221;。要创建一个大小写不敏感的模式，你应该在调用编译器的时候指定一个额外的参数： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_g.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            创建好Pattern对象之后，你就可以通过PatternMatcher类用该Pattern对象进行模式匹配。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            ▲ PatternMatcher对象</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            PatternMatcher对象根据Pattern对象和字符串进行匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给
            PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现，它根据Perl
            5正则表达式语法进行模式匹配： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_h.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            使用PatternMatcher对象，你可以用多个方法进行匹配操作，这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean matches(String input, Pattern pattern)：当输入字符串和正则表达式要精确匹配时使用。换句话说，正则表达式必须完整地描述输入字符串。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean matchesPrefix(String input, Pattern pattern)：当正则表达式匹配输入字符串起始部分时使用。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean contains(String input, Pattern pattern)：当正则表达式要匹配输入字符串的一部分时使用（即，它必须是一个子串）。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            另外，在上面三个方法调用中，你还可以用PatternMatcherInput对象作为参数替代String对象；这时，你可以从字符串中最后一次匹配
            的位置开始继续进行匹配。当字符串可能有多个子串匹配给定的正则表达式时，用PatternMatcherInput对象作为参数就很有用了。用
            PatternMatcherInput对象作为参数替代String时，上述三个方法的语法如下： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean matches(PatternMatcherInput input, Pattern pattern) </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean matchesPrefix(PatternMatcherInput input, Pattern pattern) </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            &#183;	boolean contains(PatternMatcherInput input, Pattern pattern) </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong><font size="4">
            三、应用实例</font></strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            下面我们来看看Jakarta-ORO库的一些应用实例。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            3.1 日志文件处理</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            任务：分析一个Web服务器日志文件，确定每一个用户花在网站上的时间。在典型的BEA WebLogic日志文件中，日志记录的格式如下： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_i.jpg" border="0"></td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            分析这个日志记录，可以发现，要从这个日志文件提取的内容有两项：IP地址和页面访问时间。你可以用分组符号（圆括号）从日志记录提取出IP地址和时间标记。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            首先我们来看看IP地址。IP地址有4个字节构成，每一个字节的值在0到255之间，各个字节通过一个句点分隔。因此，IP地址中的每一个字节有至少一个、最多三个数字。图八显示了为IP地址编写的正则表达式： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4h.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图八：匹配IP地址</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            IP地址中的句点字符必须进行转义处理（前面加上&#8220;\&#8221;），因为IP地址中的句点具有它本来的含义，而不是采用正则表达式语法中的特殊含义。句点在正则表达式中的特殊含义本文前面已经介绍。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            日志记录的时间部分由一对方括号包围。你可以按照如下思路提取出方括号里面的所有内容：首先搜索起始方括号字符（&#8220;[&#8221;），提取出所有不超过结束方括号字符（&#8220;]&#8221;）的内容，向前寻找直至找到结束方括号字符。图九显示了这部分的正则表达式。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4i.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图九：匹配至少一个字符，直至找到&#8220;]&#8221;</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            现在，把上述两个正则表达式加上分组符号（圆括号）后合并成单个表达式，这样就可以从日志记录提取出IP地址和时间。注意，为了匹配&#8220;- -&#8221;（但不提取它），正则表达式中间加入了&#8220;\s-\s-\s&#8221;。完整的正则表达式如图十所示。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4j.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图十：匹配IP地址和时间标记</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            现在正则表达式已经编写完毕，接下来可以编写使用正则表达式库的Java代码了。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            为使用Jakarta-ORO库，首先创建正则表达式字符串和待分析的日志记录字符串： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_j.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            这里使用的正则表达式与图十的正则表达式差不多完全相同，但有一点例外：在Java中，你必须对每一个向前的斜杠（&#8220;\&#8221;）进行转义处理。图十不是
            Java的表示形式，所以我们要在每个&#8220;\&#8221;前面加上一个&#8220;\&#8221;以免出现编译错误。遗憾的是，转义处理过程很容易出现错误，所以应该小心谨慎。你可以首先
            输入未经转义处理的正则表达式，然后从左到右依次把每一个&#8220;\&#8221;替换成&#8220;\\&#8221;。如果要复检，你可以试着把它输出到屏幕上。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            初始化字符串之后，实例化PatternCompiler对象，用PatternCompiler编译正则表达式创建一个Pattern对象： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_k.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            现在，创建PatternMatcher对象，调用PatternMatcher接口的contain()方法检查匹配情况： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_l.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            接下来，利用PatternMatcher接口返回的MatchResult对象，输出匹配的组。由于logEntry字符串包含匹配的内容，你可以看到类如下面的输出： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_m.jpg" border="0"></td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            3.2 HTML处理实例一</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            下面一个任务是分析HTML页面内FONT标记的所有属性。HTML页面内典型的FONT标记如下所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <font color="red" face="Arial, serif" size="+2"> </font><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_n.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            程序将按照如下形式，输出每一个FONT标记的属性： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_o.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            在这种情况下，我建议你使用两个正则表达式。第一个如图十一所示，它从字体标记提取出&#8220;"face="Arial, Serif" size="+2" color="red"&#8221;。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4k.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图十一：匹配FONT标记的所有属性</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            第二个正则表达式如图十二所示，它把各个属性分割成名字-值对。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4l.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图十二：匹配单个属性，并把它分割成名字-值对</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            分割结果为： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_p.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            现在我们来看看完成这个任务的Java代码。首先创建两个正则表达式字符串，用Perl5Compiler把它们编译成Pattern对象。编译正则表达
            式的时候，指定Perl5Compiler.CASE_INSENSITIVE_MASK选项，使得匹配操作不区分大小写。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            接下来，创建一个执行匹配操作的Perl5Matcher对象。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_q.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            假设有一个String类型的变量html，它代表了HTML文件中的一行内容。如果html字符串包含FONT标记，匹配器将返回true。此时，你可以用匹配器对象返回的MatchResult对象获得第一个组，它包含了FONT的所有属性： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_r.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            接下来创建一个PatternMatcherInput对象。这个对象允许你从最后一次匹配的位置开始继续进行匹配操作，因此，它很适合于提取FONT标
            记内属性的名字-值对。创建PatternMatcherInput对象，以参数形式传入待匹配的字符串。然后，用匹配器实例提取出每一个FONT的属
            性。这通过指定PatternMatcherInput对象（而不是字符串对象）为参数，反复地调用PatternMatcher对象的contains
            ()方法完成。PatternMatcherInput对象之中的每一次迭代将把它内部的指针向前移动，下一次检测将从前一次匹配位置的后面开始。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            本例的输出结果如下： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_s.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <strong>
            3.3 HTML处理实例二</strong> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            下面我们来看看另一个处理HTML的例子。这一次，我们假定Web服务器从widgets.acme.com移到了newserver.acme.com。现在你要修改一些页面中的链接： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_t.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            执行这个搜索的正则表达式如图十三所示： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center"><img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4m.gif" border="0">
            </p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <p align="center">
            图十三：匹配修改前的链接</p>
            </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            如果能够匹配这个正则表达式，你可以用下面的内容替换图十三的链接： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_u.jpg" border="0"></td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            注意#字符的后面加上了$1。Perl正则表达式语法用$1、$2等表示已经匹配且提取出来的组。图十三的表达式把所有作为一个组匹配和提取出来的内容附加到链接的后面。 </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            现在，返回Java。就象前面我们所做的那样，你必须创建测试字符串，创建把正则表达式编译到Pattern对象所必需的对象，以及创建一个PatternMatcher对象：<img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_v.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            接下来，用com.oroinc.text.regex包Util类的substitute()静态方法进行替换，输出结果字符串： </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_w.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            Util.substitute()方法的语法如下： </td>
        </tr>
    </tbody>
</table>
<table align="center" height="17" width="620">
    <tbody>
        <tr>
            <td class="a14" height="13">
            <img  src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_x.jpg" border="0"> </td>
        </tr>
    </tbody>
</table>
<table align="center" width="620">
    <tbody>
        <tr>
            <td class="a14">
            这个调用的前两个参数是以前创建的PatternMatcher和Pattern对象。第三个参数是一个Substiution对象，它决定了替换操作如
            何进行。本例使用的是Perl5Substitution对象，它能够进行Perl5风格的替换。第四个参数是想要进行替换操作的字符串，最后一个参数允
            许指定是否替换模式的所有匹配子串（Util.SUBSTITUTE_ALL），或只替换指定的次数。 </td>
        </tr>
    </tbody>
</table>
<strong>【结束语】</strong>在
这篇文章中，我为你介绍了正则表达式的强大功能。只要正确运用，正则表达式能够在字符串提取和文本修改中起到很大的作用。另外，我还介绍了如何在Java
程序中通过Jakarta-ORO库利用正则表达式。至于最终采用老式的字符串处理方式（使用StringTokenizer，charAt，和
substring），还是采用正则表达式，这就有待你自己决定了。<img src ="http://www.blogjava.net/kuxiaoku/aggbug/131668.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-07-21 22:18 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/07/21/131668.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 位运算</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/06/16/124674.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Sat, 16 Jun 2007 10:13:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/06/16/124674.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/124674.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/06/16/124674.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/124674.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/124674.html</trackback:ping><description><![CDATA[<p><strong>移位运算符</strong></p>
<p>&nbsp;&nbsp;&nbsp; 包括：<br>&nbsp;&nbsp;&nbsp; &#8220;&gt;&gt; 右移&#8221;；&#8220;&lt;&lt; 左移&#8221;；&#8220;&gt;&gt;&gt; 无符号右移&#8221;</p>
<p>例子：<br>-5&gt;&gt;3=-1<br>1111 1111 1111 1111 1111 1111 1111 1011<br>1111 1111 1111 1111 1111 1111 1111 1111<br>其结果与 Math.floor((double)-5/(2*2*2)) 完全相同。</p>
<p>-5&lt;&lt;3=-40<br>1111 1111 1111 1111 1111 1111 1111 1011<br>1111 1111 1111 1111 1111 1111 1101 1000 <br>其结果与 -5*2*2*2 完全相同。</p>
<p>5&gt;&gt;3=0<br>0000 0000 0000 0000 0000 0000 0000 0101<br>0000 0000 0000 0000 0000 0000 0000 0000<br>其结果与 5/(2*2*2) 完全相同。</p>
<p>5&lt;&lt;3=40<br>0000 0000 0000 0000 0000 0000 0000 0101<br>0000 0000 0000 0000 0000 0000 0010 1000<br>其结果与 5*2*2*2 完全相同。</p>
<p>-5&gt;&gt;&gt;3=536870911&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>1111 1111 1111 1111 1111 1111 1111 1011<br>0001 1111 1111 1111 1111 1111 1111 1111</p>
<p>无论正数、负数，它们的右移、左移、无符号右移 32 位都是其本身，比如 -5&lt;&lt;32=-5、-5&gt;&gt;32=-5、-5&gt;&gt;&gt;32=-5。<br>一个有趣的现象是，把 1 左移 31 位再右移 31 位，其结果为 -1。<br>0000 0000 0000 0000 0000 0000 0000 0001<br>1000 0000 0000 0000 0000 0000 0000 0000<br>1111 1111 1111 1111 1111 1111 1111 1111</p>
<p><strong><br>位逻辑运算符</strong></p>
<p>&nbsp;&nbsp;&nbsp; 包括：<br>&nbsp;&nbsp;&nbsp; &amp; 与；| 或；~ 非（也叫做求反）；^ 异或</p>
<p>&nbsp;&nbsp;&nbsp; &#8220;&amp; 与&#8221;、&#8220;| 或&#8221;、&#8220;~ 非&#8221;是基本逻辑运算，由此可以演变出&#8220;与非&#8221;、&#8220;或非&#8221;、&#8220;与或非&#8221;复合逻辑运算。&#8220;^ 异或&#8221;是一种特殊的逻辑运算，对它求反可以得到&#8220;同或&#8221;，所以&#8220;同或&#8221;逻辑也叫&#8220;异或非&#8221;逻辑。</p>
<p>例子：<br>5&amp;3=1<br>0000 0000 0000 0000 0000 0000 0000 0101<br>0000 0000 0000 0000 0000 0000 0000 0011<br>0000 0000 0000 0000 0000 0000 0000 0001</p>
<p>-5&amp;3=1<br>1111 1111 1111 1111 1111 1111 1111 1011<br>0000 0000 0000 0000 0000 0000 0000 0011<br>0000 0000 0000 0000 0000 0000 0000 0011</p>
<p>5|3=7<br>0000 0000 0000 0000 0000 0000 0000 0101<br>0000 0000 0000 0000 0000 0000 0000 0011<br>0000 0000 0000 0000 0000 0000 0000 0111</p>
<p>-5|3=-5<br>1111 1111 1111 1111 1111 1111 1111 1011<br>0000 0000 0000 0000 0000 0000 0000 0011<br>1111 1111 1111 1111 1111 1111 1111 1011</p>
<p>~5=-6<br>0000 0000 0000 0000 0000 0000 0000 0101<br>1111 1111 1111 1111 1111 1111 1111 1010</p>
<p>~-5=4<br>1111 1111 1111 1111 1111 1111 1111 1011<br>0000 0000 0000 0000 0000 0000 0000 0100</p>
<p>5^3=6<br>0000 0000 0000 0000 0000 0000 0000 0101<br>0000 0000 0000 0000 0000 0000 0000 0011<br>0000 0000 0000 0000 0000 0000 0000 0110</p>
-5^3=-8<br>1111 1111 1111 1111 1111 1111 1111 1011<br>0000 0000 0000 0000 0000 0000 0000 0011<br>1111 1111 1111 1111 1111 1111 1111 1000<img src ="http://www.blogjava.net/kuxiaoku/aggbug/124674.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-06-16 18:13 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/06/16/124674.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java事件处理模式</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/04/15/110830.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Sun, 15 Apr 2007 15:05:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/04/15/110830.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/110830.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/04/15/110830.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/110830.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/110830.html</trackback:ping><description><![CDATA[<p>author：板桥里人</p>
<p>出处：JDON.COM</p>
<p>Java的事件模式是动态响应系统重要的基础，在图形界面领域的事件模式已经有很多文章介绍，但是在服务器端我们会碰到更多的事件模式，这里本人试图总结一下： </p>
<p>　　<strong>事件直接驱动模式</strong><br><br>　　事件模式的第一个要求就是性能的要求，需要直接而且快，Command模式是必须经常使用的，主要适合于迅速处理 前台的命令，Command模式往往是系统架构的重要部分，也是流程控制的主要模式。 </p>
<p>　　Command模式经常Java的Reflect一起使用，因为系统的事件处理系统是处于动态变化的，随着功能要求扩展，就可能有动态变化事件
处理响应系统，以Struts中action为例，我们知道，Structs的一个主要配置文件是struts-config.xml 如下： </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>＜struts-config＞<br>　　＜action-mappings＞<br>　　　　＜action path="/login" type="com.javapro.struts.LoginAction"/＞<br>　　　　＜action path="/logout" type="com.javapro.struts.LogoutAction"/＞<br>　　＜/action-mappings＞<br>＜/struts-config＞ </td>
        </tr>
    </tbody>
</table>
<p>　　它实际是个command和event的映射关系，通过这个配置文件，运行时动态装载相应的Action，完成Command模式， 我们检查LoginAction代码，就可以看出Command模式的基本特征： </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>public final class LoginAction extends Action { <br>　　public ActionForward execute(ActionMapping mapping, <br>　　　　ActionForm form, HttpServletRequest request, HttpServletResponse response) <br>　　　　throws Exception { <br>　　　　　　　　................. <br>　　} <br>} </td>
        </tr>
    </tbody>
</table>
<p>　　很明显，典型的Command模式需要有一个接口.接口中有一个统一的方法，这里统一的方法就是execute; </p>
<p>　　比如我们有个实时系统，客户段向服务器发出不同编码代号，意味着不同的请求，不同的请求有不同的Handler进行 处理，Handler接口是: </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>
            <p>public class Handler{ </p>
            <p>　　public byte[] handleRequest(); </p>
            <p>} </p>
            </td>
        </tr>
    </tbody>
</table>
<p>　　不同性质的处理过程继承这个Handler接口，如负责进入系统的处理过程 </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>
            <p>public class EnterHandler implements Handler{ </p>
            <p>　　public byte[] handleRequest(){ <br>　　//具体业务处理 <br>　　...... <br>　　} </p>
            <p>} </p>
            </td>
        </tr>
    </tbody>
</table>
<p>　　调用Handler时是: </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>//从cache中获取这个requestId对应的Handler <br>Handler handler = (Handler)cache.get(new Integer(reqId)); <br>//调用handler的统一方法handleRequest() <br>byte[] outInf = handler.handleRequest(); </td>
        </tr>
    </tbody>
</table>
<p>　　以上是常用的一个事件驱动模式。它的特点是靠一个事件直接启动对应的事件处理器。 </p>
<p>　　Chain of
Responsibility职责链模式也应该属于这类，当事件到达后，让这个事件在我们提供的一批处理器中逐个挑选适合的处理器进行处理，这个模式缺点
是显然的，性能丧失在逐个挑选 上，一般不推荐使用，这个模式适合在我们无法预知发生的事件内容时使用，因为不知道发生事件的具体情况，
我们就无法在程序运行前事先为其指派相应的处理器，只能靠运行时，事件自己去摸索&#8220;撞运气&#8221;。 <br></p>
<br>
<p><span class="txt"><strong>监控式事件模式</strong><br><br>　　监控式事件模式就不同于事件直接驱动模式，它是借助第三者来监控和触发事件，这类事件的特点是： 有一个观察者置身事外在定期独立运行着，我们将我们要监听的事件向这个观察者注册，这样观察者就 代替我们来监听这个事件，应用客户端通过观察者来获得事件状况。
<p>　　应用客户端有三种与观察者交互的方式：1.直接融合 2.推方式 3.拉方式。 </p>
<p>　　直接融合就是说应用客户端自己就是观察者，两者融合，这样无疑应用客户端获得的触发时间是最快的。 </p>
<p>　　推方式就是观察者一旦侦测到事件发生，立即将事件Push推到应用客户端；拉方式类似收取邮件，应用客户端在需要时才从观察者拉取事件。 </p>
<p>　　JDK 1.4的None Blocking I/O是监控式事件模式的典型实现，Selector显然是一个监控I/O的第三者，当有外部事件进来，通过 调用Slector.select方法可以获得外部事件，从而进行处理，可参考我的本栏文章。 </p>
<p>　　监控式事件模式适合使用在触发性质的场合，比如数据库后端触发器 界面触发 I/O触发 状态改变触发等。 </p>
<p>　　我们以一个信件触发为例，这其实是个Observer应用例子: </p>
<p>　　比如用户提请服务器计算一个数据，如果用户同时要求将计算结果向自己信箱发一封，那么我们看如何设计？按照通常思维，这是一个次序问题，先在内
存中计算数据，然后将结果发送到他的信箱，最后返回结果到用户端，我们知道信件的发送是耗时的，因此，有可能网络的原因造成信件发送很慢，这是用户就一直
等不到他的计算结果，很显然，我们使用监控式事件模式来解决，让发信的事件由监控者去完成，只要需要时触发就可以了： </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>
            <p>public class Computer extends Observable{ </p>
            <p>　　public Computer(){ <br>　　　　//将sendMailObserver设定为本类的观察者。 <br>　　　　addObserver(new sendMailObserver()); <br>　　} </p>
            <p>　　....... </p>
            <p><br>　　public void compute(String input,boolean needEmail,String email){ </p>
            <p>　　　　//计算操作 <br>　　　　......... </p>
            <p>　　　　if (needEmail){ </p>
            <p>　　　　　　//设置变化点 <br>　　　　　　 setChanged(); <br>　　　　　　//如果需要发送email，我们把email地址作为参数传送过去 <br>　　　　　　 notifyObservers(email); </p>
            <p>　　　　}　　　 <br>　　} </p>
            <p>｝</p>
            </td>
        </tr>
    </tbody>
</table>
<p>　　我们再来看监控观察者代码是如何写的？ </p>
<table bgcolor="#ffffff" border="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>
            <p>public class sendMailObserver implements Observer{ </p>
            <p>　　public void update(Observable obj,Object email){ </p>
            <p>　　　　if (email instanceof String){ </p>
            <p>　　　　　　sendMail(email); </p>
            <p>　　　　} </p>
            <p>　　} </p>
            <p>} <br></p>
            </td>
        </tr>
    </tbody>
</table>
<p>　　这样服务器在执行compute方法时，就没有发送邮件的等待，一直接继续执行。 <br><br>　　监控式事件模式和事件直接驱动模式可以在一个系统一起使用，外界信号通过事件直接驱动模式启动系统处理模块， 系统处理模块处理过程中，可以通过监控式事件模式来触发其它后台任务。这样一个架构非常适合实时处理系统。 </p>
<p>　　既然事件处理模式是众多应用系统的基本模式，那么应该可以形成一个框架标准，JMX的notification Model就是这样一个架构设计。 </p>
<p>　　<strong>JMX Notification Model</strong><br><br>　　我们知道，JMX是提供了一种对MBean资源执行控制和配
置的管理机制，但这只是复杂的，分布式的系统中的一部分， 还有需要资源能够感应状态改变或者特定事件变化的机制，这就是JMX
Notification Model。 在JMX Notification
Model中均可以实现"事件直接驱动模式"和"监控式事件模式"，这取决于你的应用需求。 </p>
<p>　　JMX Notification
Model允许MBean通过调用notifications广播事件，接受者只要注册为一个listerner， JMX的 MBean
notification model 将会激活这个listerner注册一次，然后将一直接受到 来自广播者发出的各种事件。 </p>
<p>　　事件模式有三个角色，第一个是事件发出者producer 然后是事件接受者Consumer,第三个 是要传输的事件。JMX notification model也是这样分别依靠下列组件来实现这三个角色： </p>
<p>　　A. NotificationBroadcaster接口, 事件广播发出者， 这个接口允许监听者在需要发出的notification中注册他们感兴趣的事件。 <br><br>　　B. 通用事件(Notification)，这是我们要传输的事件。 Notification事件能被直接使用，也能成为子类，这些都依赖于随同事件传输的信息。 通过使用通用事件类型，监听者将能接受来自广播者所有类型的事件。 <br>　　<br>　　C. NotificationListener接口, 事件监听者或者接受者， 用于接受来自广播者的任何notification信号。 <br><br>　　D. NotificationFilter接口, 这个接口为notification的监听者提供一个对发出事件的过滤器。 <br><br>　　E. NotificationEmitter 接口, 扩展了NotificationBroadcaster接口，当删除监听者时允许更多的控制功能。 </p>
<p>　　只要是MBean，就既可以成为notification的发布广播者，也可以成为notification的监听者接受者，或者同时两者都可以。 </p>
<p>　　Attribute Change Notifications <br><br>　　Attribute Change Notifications是一种特殊的notification, 任何时候MBean属性attribute 被修改，外界能够被通知到。 </p>
<p>　　在JMX架构中，MBean能够在属性attribute变化发生时，发出通知，关于诊断属性变化的机制以及触发 通知事件并不属于JMX规定部分，每个MBean可以有自己独立的实现方式。 </p>
<p>　　Timer Service <br><br>　　Timer Service触发器是在规定的日期和事件发出通知，它能够一一个恒定的间隙不断重复发出通知， 通知可以发往所有注册为接受timer通知的对象，Timer Service也是一个可管理的MBean，允许应用系统设置 一个可配置的调度程度。 </p>
<p>　　Monitoring <br><br>　　通过使用monitoring
service，一个或多个MBean属性将按规定间隔时间被监视，
对于被观察的Mbean,监视器monitor将从它上面获得一个值，叫derived gauge，这个derived gauge可以是
被观察属性的原值，也可以是一个数字性属性连续被观察值之差。 </p>
<p>　　当derived gauge值满足一系列条件时，每个monitor server将会发出一个特定类型的通知。 这些条件都是在monitor被初始化时设定的，也可以通过monitor MBean的管理接口动态设定。 </p>
<p>　　根据MBean内部属性值类型有三种monitor: </p>
<p>　　A.CounterMonitor - 使用Java的整数类型来观察属性，有一个行为特征： <br><br>　　　a. 总是大于或等于零. <br><br>　　　b. 能自增. <br><br>　　　c. 能回滚. </p>
<p>　　B.GaugeMonitor - 使用java的整数或浮点类型观察属性。象gauge(测量仪器) 要么上升　要么下降减少。 </p>
<p>　　C StringMonitor - 使用String类型观察属性. </p>
<p>　　<strong>事件处理架构</strong><br><br>　　JMS是基于Socket的一种消息处理框架，原理类似于监控式事件模式，但是JMS已经把这种模式上升到架构的高度。不同JVM间也依靠JMS消息可以实现事件系统（注意是系统，不简单是一个小事件了）的触发和激活。 </p>
<p>　　<img src="http://www.yesky.com/20030303/jt-030303-jmsarchitecture.jpg" height="349" width="599"><br><br>　　从上面JMS的架构图可以看出事件三个角色Producer和Consumer以及事件信息本身Message．JMS就是在Producer和Consumer之间建立一个连接Connection． </p>
<p>　　JMS可实现同步或异步的事件触发机制，分别是通过Poin to
Point(拉方式)和Pubilsh/Subscibe(推方式)具体完成，在分布式计算环境中，异步机制是非常重要，可以起到解耦作用，因为分布环境
中单点错误或通讯问题是经常发生的，整个分布式系统不能总是依靠同步机制来可靠地传递事件或notification． </p>
<p>　　由此可见，事件处理模式从Java诸多架构到我们具体应用程序，随处可见，根据不同的应用需求选择不同的事件处理模式，才能真正挖掘Java的潜在性能。 </p>
</span></p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/110830.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-04-15 23:05 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/04/15/110830.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>存取程序状态的几种方法——Java I/O应用杂谈</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94830.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:48:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94830.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94830.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94830.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94830.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94830.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: jungleford如是说&nbsp;&nbsp;&nbsp;&nbsp;已经有一个多月没有搭理blog了，原因很多，譬如实验室的项目正在收工，巨忙；譬如找工作及其相关的事情；而且二月份大部分时间是陪老爹老妈，家里拨号的速度可想而知&#8230;&#8230;但主要还是没有找到一个合适的topic，或者说这段时间懒了（临毕业前期综合症），净在看《汉武大帝》和历史方面的书，还...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94830.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94830.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:48 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94830.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于集合框架的思考</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94829.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:47:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94829.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94829.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94829.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94829.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94829.html</trackback:ping><description><![CDATA[		<font class="contentwithhtml">
<font size="2">&nbsp;<strong><u><font color="#cc0000"><span class="style4">jungleford如是说<br></span><br></font></u></strong>&nbsp;&nbsp;&nbsp;&nbsp;
对于Java集合框架（Java Collections
Framework，JCF），Java玩家大概都不会陌生，在C++里面相似的概念是标准模板库（Standard Template
Library，STL），主要是对一些数据结构和相关算法的封装。考虑到这是一个Java初学者将会经常接触的工具，所以有了以下的一些文字。主要是参
考了<a href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/java-onlinecourse-bytitle/4475AC55462B0B9448256B3B001F279A?OpenDocument" target="_blank">IBM developerWorks上的一篇教程</a>，它可能解释得更加清晰，这里算是浓缩了一下吧，真正的来龙去脉可以看看JDK文档里的&#8220;<a href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/index.html" target="_blank">The Collections Framework</a>&#8221;，说明更为详细。</font>
</font>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style4">问题的源头</span>
<br>
</font>
</u>
</strong>
</font>
</font>
</p>
<ul>
    <font class="contentwithhtml">
    <font size="2">
    <li>
    <strong>集合：对象的容器与数据结构</strong>
    </li>
    </font>
    </font>
</ul>
<font class="contentwithhtml">
<font size="2">&nbsp;&nbsp;&nbsp;&nbsp;
回忆一下我们在程序设计里头可能会面对一些什么，无非是两类：基本类型和复合类型，后者常见的组织方式就是类。和基本类型不同，类对象通常是需要以动态方
式分配的，譬如在内存的堆空间里new一个对象，这个我们一写OO的程序就必然会用到。同时我们面对的不仅仅是单个的基本类型或对象，对多个这样的数据我
们通常采用的组织方式是什么？不错，是数组，这是伴随程序设计的一个古老概念。数组的优点显而易见，像根据下标检索元素这样的操作不费吹灰之力，但缺点也
很明显：空间固定而不能动态增长（像Java这样的强类型语言对数组越界是及其敏感的），插入或删除元素比较费劲。因此数组不是解决一切集合问题的方便工
具。我们可能需要一些新的工具，研究这些工具常常就是研究<span class="style5"><font color="#cc0000">数据结构</font></span>，特别的，数组本身就是一种线性有序的数据结构。<br>&nbsp;&nbsp;&nbsp;&nbsp;数据结构的数学基础是<span class="style5"><font color="#cc0000">集合论</font></span>，
为什么这么说呢？上面说过，现在我们要研究的不是单个的基本类型或对象，多个对象的整体不就是集合吗？从OO的角度上看，集合也是一种对象，但它是一种特
殊的对象：对象的容器（注意，我们这里没有继续讨论基本类型的集合，因为基本类型和存储分配方式与对象有着本质的差别）。集合论的一个根本问题就是：给定
一个元素，集合必须能够回答该元素是或者不是属于这个集合。还有一个问题也很重要，就是：如果元素是属于一个集合，那该元素在集合中的地位应该是唯一的，
或者说它是<span class="style5"><font color="#cc0000">唯一确定</font></span>的。当然还有其它问题，譬如查找、遍历、排序等等，这和具体的集合类型相关，后面将会讲到。 </font>
</font>
<ul>
    <font class="contentwithhtml">
    <font size="2">
    <li>
    <strong>无序集、有序集、映射</strong>
    </li>
    </font>
    </font>
</ul>
<font class="contentwithhtml">
<font size="2">&nbsp;&nbsp;&nbsp;&nbsp;谈到集合的类型，我们在高中所学的集合概念是其中的一种，叫做&#8220;<span class="style5"><font color="#cc0000">无序集</font></span>&#8221;，也就是说集合的各个元素都是平等的，没有先后的区别，于是在无序集当中就决不允许出现一模一样的元素，否则当取到这个元素的时候就不知道应该取哪一个，这就违反了上面的&#8220;唯一确定&#8221;原则。<br>&nbsp;&nbsp;&nbsp;&nbsp;等到我们上了大学，开始知道了另一种集合类型，叫做&#8220;<span class="style5"><font color="#cc0000">有序集</font></span>&#8221;
（或者叫&#8220;线性表&#8221;，区别于以后碰到的像&#8220;树&#8221;，&#8220;图&#8221;这样的非线性的数据结构），如果是计算机专业的，大概学过离散数学当中的&#8220;代数结构&#8221;，那你就更清
楚的知道，&#8220;有序集&#8221;其实是一种&#8220;二元关系&#8221;，确切的说是&#8220;偏序关系&#8221;，它是可以包含相同元素的，因为两个的相同元素的&#8220;序号&#8221;可以不同，这样根据&#8220;序
号&#8221;仍可以&#8220;唯一确定&#8221;一个元素，数组就是一种有序集，有序集的另一个特点就是任意两个元素可以确定他们的顺序。<br>&nbsp;&nbsp;&nbsp;&nbsp;无序集，有序集，难道还有第三种可能？呵呵，它还是出现在我们的高中代数课本里，叫做&#8220;<span class="style5"><font color="#cc0000">映射</font></span>&#8221;。
映射也是集合？其实自从康托尔以来，集合论就认为&#8220;万物皆集合&#8221;（但也就是这个断言贾铝思下垡院蟮霓限尉车兀行巳た梢钥纯绰匏鼗蚋绲露囊恍┙崧郏
?lt;a href="http://www.google.com/" target="_blank"&gt;google&#8220;集合论
悖论&#8221;）。映射其实是一种&#8220;元素对&#8221;的集合，就像<span class="style6"><font style="background-color: #cccccc;">f(a)=b, f(c)=d, ...</font></span>等效于集合（无序集）<span class="style6"><font style="background-color: #cccccc;">{(a, b), (c, d), ...}</font></span>，
在&#8220;映射&#8221;中可以看作是(原象, 象)的集合，换一种说法就是(关键字key,
值value)的集合。所以我们可以在笛卡儿正交坐标平面上画出漂亮的函数图像，因为在集合论看来，函数（映射）就是二维平面上的一个个点，明白了？这样
一来上面的&#8220;有序集&#8221;也好理解了，偏序关系<span class="style6"><font style="background-color: #cccccc;">a&gt;b&gt;c&gt;d&gt;...</font></span>（不知道&#8220;偏序关系&#8221;就把它们看作是数组<span class="style6"><font style="background-color: #cccccc;">x[1]=a, x[2]=b, x[3]=c, x[4]=d ...</font></span>好了）等效于无序集<span class="style6"><font style="background-color: #cccccc;">{(1, a), (2, b), (3, c), (4, d), ...}</font></span>，于是乎，所有的集合都等效于无序集！所以高中只教了我们一种集合，呵呵&#8230;&#8230; </font>
</font>
<p>
<font class="contentwithhtml">
<font size="2">
<font size="-1">
<strong>
<u>
<font color="#cc0000">
<span class="style4">JCF的全家福<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;好啦好啦，这些我们都知道，又不是在上数学课，说了这么多废话，怎么还没扯到正题上来？JCF的影子我还没看见呢！列位看官别急，这就给您道来。其实上面的概念对理解JCF非常重要。<br>&nbsp;&nbsp;&nbsp;&nbsp;JCF是个颇有点规模的家族，看看它的类层次关系图就知道了，下面这张图（图1）摘自著名的<a href="http://www.mindview.net/Books/TIJ/" target="_blank">Thinking in Java</a>：<br><br><a name="fig1"></a>
<font size="-1">
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;哇，这么多接口和类，真有点让人无从下手的感觉。其实我们真正需要记住的只是这么一个超超easy的结构（图2）：<br><br><a name="fig2"></a><img src="http://www.newsmth.org/pc/pcdownload.php?fid=14822" align="bottom"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图2<br><br>&nbsp;&nbsp;&nbsp;&nbsp;这张图看起来舒服多了吧？但它又能说明什么问题呢？它怎么就能够把握整个JCF呢？我们把<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html" target="_blank">Collection</a>接口置于最顶上，意思是想说：Collection其实是整个JCF家族中的&#8220;祖宗&#8221;，几乎所有的JCF成员都源自该接口，或者和它有密切的关系，Collection提供关于集合的一些通用操作的接口，包括插入（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#add%28java.lang.Object%29" target="_blank">add</a>()方法）、删除（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#remove%28java.lang.Object%29" target="_blank">remove</a>()方法）、判断一个元素是不是其成员（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#contains%28java.lang.Object%29" target="_blank">contains</a>()方法）、遍历（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#iterator%28%29" target="_blank">iterator</a>()方法）等等。注意了，前面的&#8220;废话&#8221;在这里将得到体现：<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Set.html" target="_blank">Set</a>接口体现的是&#8220;无序集&#8221;的概念，它是不允许有重复元素出现的；<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html" target="_blank">List</a>接口代表&#8220;有序集&#8221;；而<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target="_blank">Map</a>接口则是&#8220;映射&#8221;（在早期的Java版本中并不叫Map，我们在后面会看到），其实<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.Entry.html" target="_blank">Map.Entry</a>接口就是代表一个&#8220;元素对&#8221;我们可以通过Map的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html#entrySet%28%29" target="_blank">entrySet</a>()
方法得到这样一个由&#8220;元素对&#8221;组成的Set对象。我们注意到Set和List都是从&#8220;祖宗&#8221;Collection派生的，而Map不是，毕竟对一对元素的
操作与对单个元素的操作还是有区别的，但是如果你仔细对照一下Collection和Map的源代码，以及它们的直接后代<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractCollection.html" target="_blank">AbstractCollection</a>和<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractMap.html" target="_blank">AbstractMap</a>的源代码，你将会发现很多相似的地方，所以我们仍然可以把Map看成是和Collection有着血缘关系的接口，而和Set，List一起处于并列的位置。<br>&nbsp;&nbsp;&nbsp;&nbsp;有了&#8220;无序集&#8221;，&#8220;有序集&#8221;和&#8220;映射&#8221;，我们就可以定义各种各样的抽象数据结构了，譬如图1所示的向量，链表，堆栈，哈希表，平衡二叉树等。但我们需要记住的，仅仅是<a href="http://jungleford.3322.org/java/jcf.htm#fig2">图2</a>，置于其它的成员，在用到的时候查一下API手册不就行了？不过一般初学者还是比较容易用到一些类，像Vector、ArrayList、HashMap，我在这里列了一张表，显示了常见的JCF成员及其关系： </font>
</font>
</font>
</font></p>
<table border="1" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td colspan="5" valign="top" width="484">
            <p align="center">集合框架的祖宗： <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html" target="_blank">Collection</a></p>
            </td>
            <td valign="top" width="118">
            <p align="center">历史集合 </p>
            </td>
            <td colspan="4" valign="top" width="398">
            <p align="center">新集合 </p>
            </td>
        </tr>
        <tr>
            <td colspan="2" valign="top" width="195">
            <p align="center">无序集： <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Set.html" target="_blank">Set</a></p>
            </td>
            <td colspan="3" valign="top" width="289">
            <p align="center">有序集： <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html" target="_blank">List</a></p>
            </td>
            <td valign="top" width="118">
            <p align="center">映射：<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Dictionary.html" target="_blank">Dictionary</a></p>
            </td>
            <td colspan="4" valign="top" width="398">
            <p align="center">映射：<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target="_blank">Map</a></p>
            </td>
        </tr>
        <tr>
            <td rowspan="2" valign="top" width="101">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractSet.html" target="_blank">AbstractSet</a>
            </p>
            </td>
            <td rowspan="2" valign="top" width="94">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedSet.html" target="_blank">SortedSet</a>
            </p>
            </td>
            <td colspan="2" valign="top" width="147">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractList.html" target="_blank">AbstractList</a>
            </p>
            </td>
            <td valign="top" width="143">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractSequentialList.html" target="_blank">AbstractSequentialList</a>
            </p>
            </td>
            <td rowspan="3" valign="top" width="118">
            <p align="center">
            <br>
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Hashtable.html" target="_blank">Hashtable</a>
            </p>
            </td>
            <td colspan="3" valign="top" width="321">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/AbstractMap.html" target="_blank">AbstractMap</a>
            </p>
            </td>
            <td valign="top" width="77">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedMap.html" target="_blank">SortedMap</a>
            </p>
            </td>
        </tr>
        <tr>
            <td valign="top" width="78">
            <p align="center">历史集合 </p>
            </td>
            <td valign="top" width="69">
            <p align="center">新集合 </p>
            </td>
            <td rowspan="2" valign="top" width="143">
            <p align="center">
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedList.html" target="_blank">LinkedList</a>
            </p>
            </td>
            <td rowspan="2" valign="top" width="100">
            <p align="center">
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/WeakHashMap.html" target="_blank">WeakHashMap</a>
            </p>
            </td>
            <td rowspan="2" valign="top" width="112">
            <p align="center">
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/IdentityHashMap.html" target="_blank">IdentityHashMap</a>
            </p>
            </td>
            <td rowspan="2" valign="top" width="109">
            <p align="center">
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashMap.html" target="_blank">HashMap</a>
            </p>
            </td>
            <td rowspan="2" valign="top" width="77">
            <p align="center">
            <br>
            <br>
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeMap.html" target="_blank">TreeMap</a>
            </p>
            </td>
        </tr>
        <tr>
            <td valign="top" width="101">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashSet.html" target="_blank">HashSet</a>
            </p>
            </td>
            <td valign="top" width="94">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html" target="_blank">TreeSet</a>
            </p>
            </td>
            <td valign="top" width="78">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html" target="_blank">Vector</a>
            </p>
            </td>
            <td valign="top" width="69">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html" target="_blank">ArrayList</a>
            </p>
            </td>
        </tr>
        <tr>
            <td valign="top" width="101">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedHashSet.html" target="_blank">LinkedHashSet</a>
            </p>
            </td>
            <td valign="top" width="94">&nbsp;</td>
            <td valign="top" width="78">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Stack.html" target="_blank">Stack</a>
            </p>
            </td>
            <td valign="top" width="69">&nbsp;</td>
            <td valign="top" width="143">&nbsp;</td>
            <td valign="top" width="118">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html" target="_blank">Properties</a>
            </p>
            </td>
            <td valign="top" width="100">&nbsp;</td>
            <td valign="top" width="112">&nbsp;</td>
            <td valign="top" width="109">
            <p align="center">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedHashMap.html" target="_blank">LinkedHashMap</a>
            </p>
            </td>
            <td valign="top" width="77">&nbsp;</td>
        </tr>
    </tbody>
</table>
<p>
<font class="contentwithhtml"><font class="contentwithhtml">
<font size="2">
<font size="-1">
<font color="#cc0000">
<span class="style4">
<font color="#000000">
<font size="3">&nbsp;</font>&nbsp;&nbsp;&nbsp;&nbsp;可能有的概念您还不是太了解，譬如什么叫&#8220;历史集合&#8221;，Hashtable、HashMap、TreeMap三者之间有什么区别和联系，怎样实现对一个特定集合的快速遍历、元素查找或者排序，没关系，我们在下面将逐一进行研究。 <br><br></font>
<strong>
<u>细节考虑：目标与效率<br></u>
</strong>
</span>
<br>
</font>&nbsp;&nbsp;&nbsp;&nbsp;有了JCF的层次还不够，重要的是对集合所容纳的对象的具体操作，以前我们学数据结构的时候可能老师总会让你计算一个算法的时间复杂度，可能你会对这个O(f(n))很不耐烦，但事实上算法效率是一个重要的因素。</font>
</font>
</font>
</font></p>
<ul>
    <font class="contentwithhtml">    <font class="contentwithhtml">
    <font size="2">
    <font size="-1">
    <strong>1. 侧重点：遍历 vs. 查找</strong>
    </font>
    </font>
    </font>
    </font></ul>
    <p>
    <font class="contentwithhtml"><font class="contentwithhtml">
    <font size="2">
    <font size="-1">&nbsp;&nbsp;&nbsp;&nbsp;
    对集合的有两个主要的应用：我需要知道集合有哪些元素；根据条件找到一个特定的元素。在算法上通常称为&#8220;遍历&#8221;和&#8220;查找&#8221;。不要以为我们生活中不常用哦！
    譬如CCTV的&#8220;幸运52&#8221;里面，李咏让参赛者报出一款PDA的准确价位，他会怎么做？&#8220;2000&#8221;&#8220;高了&#8221;&#8220;1000&#8221;&#8220;低了&#8221;&#8220;1500&#8221;&#8220;低
    了&#8221;&#8230;&#8230;直到答对为止。可能有很多人都会选择这个策略，无论他是不是计算机专业出身的，也不知道他是不是了解&#8220;数据结构&#8221;和&#8220;折半查找&#8221;，更不用说他是不
    是知道还有比在无初始代价下O(log
    n)的时间复杂度更快的算法了，但我们经常会自然而然地用这样的方法，这和一个人的行业无关，除非这个人的rp超强，呵呵&#8230;&#8230;<br>&nbsp;&nbsp;&nbsp;&nbsp;又讲了一堆
    题外话了，遍历和修改似乎是一对矛盾，一个可以高效率插入删除元素的数据结构通常遍历的性能并不是最优。于是JCF在这里根据用户的目标实现了两种定制的
    数据结构：哈希表（包括HashSet和HashMap）和平衡二叉树（包括TreeSet和TreeMap）。由于可排序性是一种独特的要求，所以引入
    了SortedSet和SortedMap，它们分别是AbstractSet和AbstractMap的子接口，而TreeSet和TreeMap又分
    别是他们的一种实现。熟悉数据结构的人可能比较了解，哈希表在进行插入、删除、查找这样的操作是很快的，其时间复杂度是常数级O(1)；平衡二叉树虽然插
    入、删除操作比较麻烦（需要O(log
    n)的代价），但进行遍历和排序却很快。选择完全在于用户的侧重点，但由于类型转换的方便性，通常我们用哈希表构造一个集合以后，再把它转换成相应的树集
    进行遍历，以获得较好的效果。<br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
        <tbody>
            <tr>
                <td><font size="-1"><span class="style7"><strong><font color="#990000">Set</font></strong></span> set1 = <span class="style12"><strong><font color="#ff0000">new</font></strong></span><span class="style7"><strong><font color="#990000">HashSet</font></strong></span>();<br>set1.add(elem1);<font color="#006600"><span class="style10">// 通过插入元素构造集合</span><br></font>set1.add(elem2);<br>set1.add(elem3);<br><span class="style7"><strong><font color="#990000">Set</font></strong></span> set2 = <span class="style12"><strong><font color="#ff0000">new</font></strong></span><span class="style7"><strong><font color="#990000">TreeSet</font></strong></span>(set);<br><span class="style7"><strong><font color="#990000">Iterator</font></strong></span> all = set2.iterator();<br><span class="style12"><strong><font color="#ff0000">while</font></strong></span> (all.hasNext())<br>{<font color="#006600"><span class="style10">// 遍历集合</span><br></font>all.next();<br>...<br>}</font></td>
            </tr>
        </tbody>
    </table>
    <br></font>
    </font>
    </font>
    </font></p>
    <ul>
        <font class="contentwithhtml">    <font class="contentwithhtml">
        <font size="2">
        <font size="-1">
        <strong>2. 历史实现 vs. 新实现</strong>
        </font>
        </font>
        </font>
        </font></ul>
        <font class="contentwithhtml"><font class="contentwithhtml">
        <font size="2">
        <font size="-1">&nbsp;&nbsp;&nbsp;&nbsp;
        历史实现（Legacy Implementations）是JCF的一个术语，准确的意义不是很清楚，但大致可以认为在Java 2（JDK
        1.2）出现以前的老版本中JCF的一个雏形框架。在Java
        2以后，JCF才开始完善健壮起来，新实现中出现了一些新的类用于替代老版本中的成员，但由于种种原因，老版本中很多类都代表了传统数据结构的精髓部分，
        以及一些安全原因，所以仍然被我们使用着。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Enumeration.html" target="_blank">Enumeration</a> vs. <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html" target="_blank">Iterator</a><br>&nbsp;&nbsp;&nbsp;&nbsp;Enumeration是一个传统的集合遍历工具，在新的JCF中使用的是Iterator，Iterator同样具有遍历功能，还包含一个<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Iterator.html#remove%28%29" target="_blank">remove</a>()方法来删除当前得到的元素。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Dictionary.html" target="_blank">Dictionary</a> vs. <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html" target="_blank">Map</a><br>&nbsp;&nbsp;&nbsp;&nbsp;Dictionary
        是一个现在已经被标记为deprecated的类，实现了老版本中的映射功能，现在已经完全被Map取代。它们的区别是：Dictionary的key和
        value不能为null，但Map却允许空的关键字和值，这一点直接影响到它们的后代：Hashtable和HashMap。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html" target="_blank">Vector</a> vs. <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html" target="_blank">ArrayList</a><br>&nbsp;&nbsp;&nbsp;&nbsp;Vector
        和ArrayList是数组在JCF中的体现，还记得前面讲过的数组的缺点么？Vector和ArrayList就是一种可以动态增长的数组。
        Vector是历史实现，它和ArrayList的主要区别在于，Vector是同步集合（或者说是线程安全的），但ArrayList并不是同步的，由
        于同步需要花一定的代价，所以ArrayList看起来要比Vector的存取访问效率更高。关于同步我们下面还将要谈到。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Hashtable.html" target="_blank">Hashtable</a> vs. <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashMap.html" target="_blank">HashMap</a><br>&nbsp;&nbsp;&nbsp;&nbsp;Hashtable
        是Dictionary的子类，属于历史实现，而HashMap是Map的子类，是新实现。它们的区别除了上面所说的key和value是否可以为空之
        外，也有同步的差别，Hashtable是同步的，但HashMap不是。HashMap的一个典型的例子就是我们在JSP里面经常用到的内置对象
        session。不过也不要因为Hashtable是&#8220;老前辈&#8221;而瞧不起它哦，它的一个著名的子类<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html" target="_blank">Properties</a>我们可是经常会用到的。 </font>
        </font>
        </font>
        </font>
        <ul>
            <font class="contentwithhtml">    <font class="contentwithhtml">
            <font size="2">
            <font size="-1">
            <strong>3. 同步 vs. 不同步</strong>
            </font>
            </font>
            </font>
            </font></ul>
            <font class="contentwithhtml"><font class="contentwithhtml">
            <font size="2">
            <font size="-1">&nbsp;&nbsp;&nbsp;&nbsp;
            从上面的描述中我们似乎可以得出这么一个印象：历史实现好像都是同步的，但新实现中却没有。需要同步操作的理由是，可能存在多个线程对同一个集合进行操作
            的情况：譬如一个线程正在对某集合进行遍历，但与此同时，另一个线程又在对该集合进行插入或删除，那么第一个线程的遍历结果将是不可预测的，对于同步集
            合，它将会抛出一个<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ConcurrentModificationException.html" target="_blank">ConcurrentModificationException</a>异常，JCF把这种机制成为&#8220;fail-fast&#8221;。我们对比一下Vector和ArrayList的源代码就可以发现Vector的很多方法都是有synchronized关键字修饰的，但ArrayList没有。 </font>
            </font>
            </font>
            </font>
            <ul>
                <font class="contentwithhtml">    <font class="contentwithhtml">
                <font size="2">
                <font size="-1">
                <strong>4. 容易遗忘的工具：</strong>
                <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html" target="_blank">
                <strong>Collections</strong>
                </a>
                <strong>和</strong>
                <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Arrays.html" target="_blank">
                <strong>Arrays</strong>
                </a>
                </font>
                </font>
                </font>
                </font></ul>
                <font class="contentwithhtml"><font class="contentwithhtml">
                <font size="2">
                <font size="-1">&nbsp;&nbsp;&nbsp;&nbsp;在<a href="http://jungleford.3322.org/java/jcf.htm#fig1">图1</a>中
                右下角落里有两个类叫做Collections（注意，不是Collection！）和Arrays，这是JCF里面功能强大的工具，但初学者往往会忽
                视。按JCF文档的说法，这两个类提供了封装器实现（Wrapper Implementations）、数据结构算法和数组相关的应用。<br>&nbsp;&nbsp;&nbsp;&nbsp;想必大家不会忘记上面谈到的&#8220;折半查找&#8221;、&#8220;排序&#8221;等经典算法吧，Collections类提供了丰富的静态方法帮助我们轻松完成这些在数据结构课上烦人的工作：<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#binarySearch%28java.util.List,%20java.lang.Object%29" target="_blank">binarySearch</a>：折半查找。<br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#sort%28java.util.List%29" target="_blank">sort</a>：排序，这里是一种类似于快速排序的方法，效率仍然是O(n * log n)，但却是一种稳定的排序方法。<br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#reverse%28java.util.List%29" target="_blank">reverse</a>：将线性表进行逆序操作，这个可是从前数据结构的经典考题哦！<br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#rotate%28java.util.List,%20int%29" target="_blank">rotate</a>：以某个元素为轴心将线性表&#8220;旋转&#8221;——哇，这个功能太酷了！<br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#swap%28java.util.List,%20int,%20int%29" target="_blank">swap</a>：交换一个线性表中两个元素的位置。<br>&#8230;&#8230;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Collections还有一个重要功能就是&#8220;封装器&#8221;（Wrapper），它提供了一些方法可以把一个集合转换成一个特殊的集合：<br><br>unmodifiableXXX：转换成只读集合，这里XXX代表六种基本集合接口：Collection、List、Map、Set、SortedMap和SortedSet。如果你对只读集合进行插入删除操作，将会抛出<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/UnsupportedOperationException.html" target="_blank">UnsupportedOperationException</a>异常。<br>synchronizedXXX：转换成同步集合。<br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singleton%28java.lang.Object%29" target="_blank">singleton</a>：创建一个仅有一个元素的集合，这里singleton生成的是单元素Set，<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singletonList%28java.lang.Object%29" target="_blank">singletonList</a>和<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#singletonMap%28java.lang.Object,%20java.lang.Object%29" target="_blank">singletonMap</a>分别生成单元素的List和Map。<br>空集：由Collections的静态属性<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_SET" target="_blank">EMPTY_SET</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_LIST" target="_blank">EMPTY_LIST</a>和<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collections.html#EMPTY_MAP" target="_blank">EMPTY_MAP</a>表示。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;此外，我们知道把集合转换成对象数组可以用Collection的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Collection.html#toArray%28%29" target="_blank">toArray</a>()方法，我们也可以方便地把一个对象数组转换成一个线性表（可不要告诉我你是一个一个地add哦）：Arrays.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Arrays.html#asList%28java.lang.Object%5B%5D%29" target="_blank">asList</a>()。 </font>
                </font>
                </font>
                </font>
                <ul>
                    <font class="contentwithhtml">    <font class="contentwithhtml">
                    <font size="2">
                    <font size="-1">
                    <strong>5. 泛型</strong>
                    </font>
                    </font>
                    </font>
                    </font></ul>
                    <font class="contentwithhtml"><font class="contentwithhtml">
                    <font size="2">
                    <font size="-1">&nbsp;&nbsp;&nbsp;&nbsp;
                    目前我们了解的JCF的一个重要特征是：所有加入到集合当中的对象都将在表面上失去它们自己的特性，而看上去仅仅只是一个Object对象而已，除非你把
                    它强制类型转换成它们原来的对象。这一点很自然，集合嘛，对象的容器，它容纳的是各种各样的对象，而不仅仅是某种特定类型的对象。J2SE
                    5.0出现以后，JCF开始表现出一些泛型的特性，譬如我们经常碰到这样的应用，就是把集合转换成特定的数组，虽然Collection有toArray
                    ()的方法，但可惜的是，这个数组的所有元素都是Object类型的，我们通常的做法是用一个for循环把数组的每个元素都进行强制类型转换，虽然可行，
                    但看上去很笨拙，如果有了泛型，我们就可以预先指定要得到的类型，然后一次toArray就可以得到我们期望的数组，里面的元素全部都是指定类型了。惭
                    愧，我对5.0还不是太了解，具体可以参考<a href="http://java.sun.com/j2se/1.5.0/docs/guide/collections/index.html" target="_blank">J2SE 5.0的JCF文档</a>。 </font>
                    </font>
                    </font>
                    </font>
                    <p>
                    <font class="contentwithhtml"><font class="contentwithhtml">
                    <font size="2">
                    <font size="-1">
                    <strong>
                    <u>
                    <font color="#cc0000">
                    <span class="style4">小结<br></span>
                    <br>
                    </font>
                    </u>
                    </strong>&nbsp;&nbsp;&nbsp;&nbsp;
                    我这里走马观花一样把JCF的一些主要概念罗嗦了一下，Java的老手们可能比较厌烦，新手们可能更觉得像回顾了一下高中的数学课和大学的数据结构，呵
                    呵。这只是一个小小的例子，可见基础知识对现实当中的应用还是蛮有指导意义的。大师们看数学，觉得那是很唯美很艺术的一样东西，西方一直都把数学区别于其
                    它自然科学，而认为它更靠近于哲学，像我等这样整天还在为找工作烦得要死的俗人还是没法入道啊，sigh&#8230;&#8230;</font>
                    </font>
                    </font>
                    </font></p>
                    <p class="style4">
                    <font class="contentwithhtml"><font class="contentwithhtml">
                    <font size="2">
                    <font size="-1">
                    <strong>
                    <u>
                    <font color="#cc0000">参考资料</font>
                    </u>
                    </strong>
                    </font>
                    </font>
                    </font>
                    </font></p>
                    <ul>
                        <font class="contentwithhtml">    <font class="contentwithhtml">
                        <font size="2">
                        <font size="-1">
                        <li>
                        <font size="-1">IBM developerWorks教程：<a href="http://www-900.ibm.com/developerWorks/cn/cnedu.nsf/java-onlinecourse-bytitle/4475AC55462B0B9448256B3B001F279A?OpenDocument" target="_blank"> Java 集合框架</a></font>
                        </li>
                        <li>
                        <font size="-1">J2SE Documentation: </font>
                        <a href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/index.html" target="_blank">The Collections Framework </a>
                        </li>
                        <li>
                        <a href="http://java.sun.com/docs/books/tutorial/collections/index.html" target="_blank">The J<font size="-1">ava Tutorial</font></a>
                        </li>
                        <li>
                        <font size="-1">
                        <a href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/reference.html" target="_blank">JCF API</a>
                        </font>
                        </li>
                        </font>
                        </font>
                        </font>
                        </font></ul>
                        <font class="contentwithhtml"> </font><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94829.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:47 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94829.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java调试的变迁：从System.out.println到log4j</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94827.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:45:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94827.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94827.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94827.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94827.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94827.html</trackback:ping><description><![CDATA[		<font class="contentwithhtml">
<font size="2">&nbsp;<strong><u><font color="#cc0000"><span class="style2">jungleford如是说<img style="width: 89px; height: 61px;" src="http://www.newsmth.org/pc/pcdownload.php?fid=14824" align="bottom" height="61" width="89"><br></span><br></font></u></strong>&nbsp;&nbsp;&nbsp;&nbsp;用惯了<a href="http://msdn.microsoft.com/visualc/" target="_blank">VC</a>的人刚接触<a href="http://java.sun.com/" target="_blank">Java</a>大概很不习惯代码的调试，的确，在<a href="http://www.microsoft.com/" target="_blank">M$</a>的大部分IDE都做得相当出色，包括像VJ++这样一直被Java程序员称为是&#8220;垃圾&#8221;的类库（记得以前在<a href="http://bbs.ustc.edu.cn/" target="_blank">瀚海星云</a>的<a href="http://bbs.ustc.edu.cn/cgi-bin/bbsdoc?board=Java" target="_blank">Java版</a>提有关VJ问题的人是有可能被封的，^_^），它的开发工具在调试上都相当容易。Java也有命令行方式的调试和IDE的调试，但现在的像<a href="http://www.borland.com/jbuilder/" target="_blank">JB</a>这
样的玩意又是个庞然大物，低配置的机器可能就是个奢望，不像VC那样。怎么办呢，高手们说，&#8220;我的jdb用得贼熟练&#8221;，那我会报以景仰的目光，像我这样的
菜鸟基本上就没使过jdb，还是老老实实在代码里面System.out.println(...)。直到1996年一个叫做&#8220;欧洲安全电子市场&#8221;
（E.U. <a href="http://www.semper.org/" target="_blank">SEMPER</a>）的项目启动，&#8220;调试&#8221;不再是一件&#8220;体力活&#8221;，而是一种软件设计的艺术，这个项目组开发的日志管理接口后来成为<a href="http://www.apache.org/" target="_blank">Apache</a><a href="http://jakarta.apache.org/" target="_blank">Jakarta</a>项目中的一员，它就是现在我们所熟悉的<a href="http://logging.apache.org/" target="_blank">log4j</a>。下面的文字将概要介绍与Java日志记录相关的一些技术，目的不是让您放弃老土的System.out.println(...)，而是说，在Java的世界里可以有许多种选择，你今天觉得掌握了一件高级武器，明天可能就是&#8220;过时&#8221;的了，呵呵。</font>
</font>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">始祖：System.out.println(...)<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;为什么还是要一再提到它？毕竟我们的习惯不是那么容易改变的，而且<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html" target="_blank">System</a>.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html#out" target="_blank">out</a>（别忘了还有System.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html#err" target="_blank">err</a>）是一个直接和控制台打交道的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io/PrintStream.html" target="_blank">PrintStream</a>对象，是终端显示的基础，高级的Logger要在终端显示日志内容，就必然会用到这个。一个小规模的程序调试，恰当地使用System.out.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/io/PrintStream.html#println%28java.lang.Object%29" target="_blank">println</a>(...)我认为仍然是一种最方便最有效的方法，所以我们仍把它放在最开始，以示不能&#8220;数典忘祖&#8221; :)</font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">不常用的关键字：assert<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;assert对多数人来讲可能还比较陌生，它也是一个调试工具，好像是<a href="http://java.sun.com/j2se/1.4.2/" target="_blank">J2SE 1.4</a>才加进来的东东，一种常见的用法是：
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1"><span class="style3"><strong><font color="#ff0000">assert</font></strong></span> (布尔表达式);</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;当表达式为true时没有任何反映，如果为false系统将会抛出一个<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/AssertionError.html" target="_blank">AssertionError</a>。如果你要使用assert，在编译时必须加上&#8220;<strong>-source 1.4</strong>&#8221;的选项，在运行时则要加上&#8220;<strong>-ea</strong>&#8221;选项。<br></font></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">后生可畏：Java Logging API一瞥<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(...)对于较高要求的用户是远远不够的，它还不是一个日志系统，一个比较完善的日志系统应当有输出媒介、优先级、格式化、日志过滤、日志管理、参数配置等功能。伴随J2SE 1.4一起发布的Java日志包<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/package-summary.html" target="_blank">java.util.logging</a>适时地满足了我们的初步需求，在程序中按一定格式显示和记录丰富的调试信息已经是一件相当easy的事情。</font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>1. 日志记录器：Logger</strong>
<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html" target="_blank">Logger</a>是
一个直接面向用户的日志功能调用接口，从用户的角度上看，它完成大部分日志记录工作，通常你得到一个Logger对象，只需要使用一些简单方法，譬如
info，warning，log，logp，logrb等就能完成任务，简单到和System.out.println(...)一样只用一条语句，但
后台可能在向控制台，向文件，向数据库，甚至向网络同时输出该信息，而这个过程对用户是完全透明的。<br>&nbsp;&nbsp;&nbsp;&nbsp;在使用Logger之前，首先需要通过<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getLogger%28java.lang.String%29" target="_blank">getLogger</a>()或<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getAnonymousLogger%28%29" target="_blank">getAnonymousLogger</a>()静态方法得到一个Logger对象（想想看，这里是不是设计模式当中的&#8220;工厂方法&#8221;的一个实实在在的应用？可以参考一下Logger的源代码，你就明白<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/LogManager.html" target="_blank">LogManager</a>是&#8220;工厂类&#8221;而Logger是&#8220;产品类&#8221;，凡事都要学以致用嘛，呵呵）。这里我们需要了解的是Logger的&#8220;名字空间&#8221;（<strong>namespace</strong>）
的概念：通常我们调试时需要清楚地知道某个变量是出现在什么位置，精确到哪个类的哪个方法，namespace就是这么个用处。我们用getLogger
()得到Logger时需要指定这个Logger的名字空间，通常是一个包名，譬如&#8220;com.jungleford.test&#8221;等，如果是指定了
namespace，那么将在一个全局对象LogManager中注册这个namespace，Logger会基于namespace形成层次关系，譬如
namespace为&#8220;com.jungleford&#8221;的Logger就是namespace为&#8220;com.jungleford.test&#8221;的
Logger的父，后者调用<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#getParent%28%29" target="_blank">getParent</a>()
方法将返回前者，如果当前没有namespace为&#8220;com.jungleford&#8221;的Logger，则查找namespace为&#8220;com&#8221;的
Logger，要是按照这个链找不到就返回根Logger，其namespace为""，根Logger的父是null。从理论上说，这个
namespace可以是任意的，通常我们是按所调试的对象来定，但如果你是使用getAnonymousLogger()方法产生的Logger，那它
就没有namespace，这个&#8220;匿名Logger&#8221;的父是根Logger。<br>&nbsp;&nbsp;&nbsp;&nbsp;得到一个Logger对象后就可以记录日志了，下面是一些常用的方法： <br>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#finest%28java.lang.String%29" target="_blank">finest</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#finer%28java.lang.String%29" target="_blank">finer</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#fine%28java.lang.String%29" target="_blank">fine</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#info%28java.lang.String%29" target="_blank">info</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#config%28java.lang.String%29" target="_blank">config</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#warning%28java.lang.String%29" target="_blank">warning</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#severe%28java.lang.String%29" target="_blank">severe</a>：简洁的方法，输出的日志为指定的级别。关于日志级别我们在后面将会详细谈到。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29" target="_blank">log</a>：不仅可以指定消息和级别，还可以带一些参数，甚至可以直接是一个LogRecord对象（这些参数是LogRecord对象的重要组成部分）。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#logp%28java.util.logging.Level,%20java.lang.String,%20java.lang.String,%20java.lang.String%29" target="_blank">logp</a>：更加精细了，不但具有log方法的功能，还可以不使用当前的namespace，定义新的类名和方法名。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#entering%28java.lang.String,%20java.lang.String%29" target="_blank">entering</a>、<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#exiting%28java.lang.String,%20java.lang.String%29" target="_blank">exiting</a>：这两个方法在调试的时候特别管用，用来观察一个变量变化的情况，就如同我们在VC的调试状态下watch一个变量，然后按F10，呵呵。</font></td>
        </tr>
    </tbody>
</table>
<br></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>2. 输出媒介控制：Handler</strong>
<br>&nbsp;&nbsp;&nbsp;&nbsp;日志的意义在于它可以以多种形式输出，尤其是像文件这样可以长久保存的媒介，这是System.out.println(...)所无法办到的。Logging API的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html" target="_blank">Handler</a>类提供了一个处理日志记录（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/LogRecord.html" target="_blank">LogRecord</a>，它是对一条日志消息的封装对象）的接口，包括几个已实现的API： <br>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/ConsoleHandler.html" target="_blank">ConsoleHandler</a>：向控制台输出。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/FileHandler.html" target="_blank">FileHandler</a>：向文件输出。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/SocketHandler.html" target="_blank">SocketHandler</a>：向网络输出。</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;这三个输出控制器都是<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/StreamHandler.html" target="_blank">StreamHandler</a>的子类，另外Handler还有一个MemoryHandler的子类，它有特殊的用处，我们在后面将会看到。在程序启动时默认的Handler是ConsoleHandler，不过这个是可以配置的，下面会谈到logging配置文件的问题。<br>&nbsp;&nbsp;&nbsp;&nbsp;此外用户还可以定制自己输出控制器，继承Handler即可，通常只需要实现Handler中三个未定义的抽象方法： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#publish%28java.util.logging.LogRecord%29" target="_blank">publish</a>：主要方法，把日志记录写入你需要的媒介。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#flush%28%29" target="_blank">flush</a>：清除缓冲区并保存数据。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Handler.html#close%28%29" target="_blank">close</a>：关闭控制器。</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;通过重写以上三个方法我们可以很容易就实现一个把日志写入数据库的控制器。<br></font></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>3. 自定义输出格式：Formatter</strong>
<br>&nbsp;&nbsp;&nbsp;&nbsp;除了可以指定输出媒介之外，我们可能还希望有多种输出格式，譬如可以是普通文本、HTML表格、XML等等，以满足不同的查看需求。Logging API中的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html" target="_blank">Formatter</a>就是这样一个提供日志记录格式化方法接口的类。默认提供了两种Formatter：<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/SimpleFormatter.html" target="_blank">SimpleFormatter</a>：标准日志格式，就是我们通常在启动一些诸如<a href="http://jakarta.apache.org/tomcat/" target="_blank">Tomcat</a>、<a href="http://www.jboss.org/" target="_blank">JBoss</a>之类的服务器的时候经常能在控制台下看到的那种形式，就像这样： <br>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1">2004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol init<br>信息: Initializing Coyote HTTP/1.1 on http-8080<br><br>2004-12-20 23:08:56 org.apache.coyote.http11.Http11Protocol init<br>信息: Initializing Coyote HTTP/1.1 on http-8443 </font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/XMLFormatter.html" target="_blank">XMLFormatter</a>：XML形式的日志格式，你的Logger如果add了一个new XMLFormatter()，那么在控制台下就会看到下面这样的形式，不过更常用的是使用上面介绍的FileHandler输出到XML文件中： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1">&lt;?xml version="1.0" encoding="GBK" standalone="no"?&gt;<br>&lt;!DOCTYPE log SYSTEM "logger.dtd"&gt;<br>&lt;log&gt;<br>&lt;record&gt;<br>&nbsp;&nbsp;&lt;date&gt;2004-12-20T23:47:56&lt;/date&gt;<br>&nbsp;&nbsp;&lt;millis&gt;1103557676224&lt;/millis&gt;<br>&nbsp;&nbsp;&lt;sequence&gt;0&lt;/sequence&gt;<br>&nbsp;&nbsp;&lt;logger&gt;Test&lt;/logger&gt;<br>&nbsp;&nbsp;&lt;level&gt;WARNING&lt;/level&gt;<br>&nbsp;&nbsp;&lt;class&gt;Test&lt;/class&gt;<br>&nbsp;&nbsp;&lt;method&gt;main&lt;/method&gt;<br>&nbsp;&nbsp;&lt;thread&gt;10&lt;/thread&gt;<br>&nbsp;&nbsp;&lt;message&gt;warning message&lt;/message&gt;<br>&lt;/record&gt;</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;与Handler类似，我们也可以编写自己的格式化处理器，譬如API里没有将日志输出为我们可通过浏览器查看的HTML表格形式的Formatter，我们只需要重写3个方法： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1"><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#format%28java.util.logging.LogRecord%29" target="_blank">format</a>：格式化LogRecord中包含的信息。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#getHead%28java.util.logging.Handler%29" target="_blank">getHead</a>：输出信息的头部。<br><br><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Formatter.html#getTail%28java.util.logging.Handler%29" target="_blank">getTail</a>：输出信息的尾部。</font></td>
        </tr>
    </tbody>
</table>
<br></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<font size="-1">
<strong>4. 定义日志级别：Level</strong>
<br>&nbsp;&nbsp;&nbsp;&nbsp;大家可能都知道Windows的&#8220;事件查看器&#8221;，里面有三种事件类型：&#8220;信息&#8221;、&#8220;警告&#8221;、&#8220;错误&#8221;。这其实就是日志级别的一种描述。Java日志级别用<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html" target="_blank">Level</a>类表示，一个日志级别对应的是一个整数值，范围和整型值的范围是一致的，该整数值愈大，说明警戒级别愈高。Level有9个内置的级别，分别是： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="40%">
    <tbody>
        <tr>
            <th>
            <font size="-1">类型</font>
            </th>
            <th>
            <font size="-1">对应的整数</font>
            </th>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#OFF" target="_blank">OFF</a>
            </font>
            </td>
            <td>
            <font size="-1">最大整数（<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html" target="_blank">Integer</a>.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html#MAX_VALUE" target="_blank">MAX_VALUE</a>）</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#SEVERE" target="_blank">SEVERE</a>
            </font>
            </td>
            <td>
            <font size="-1">1000</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#WARNING" target="_blank">WARNING</a>
            </font>
            </td>
            <td>
            <font size="-1">900</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#INFO" target="_blank">INFO</a>
            </font>
            </td>
            <td>
            <font size="-1">800</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#CONFIG" target="_blank">CONFIG</a>
            </font>
            </td>
            <td>
            <font size="-1">700</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINE" target="_blank">FINE</a>
            </font>
            </td>
            <td>
            <font size="-1">500</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINER" target="_blank">FINER</a>
            </font>
            </td>
            <td>
            <font size="-1">400</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#FINEST" target="_blank">FINEST</a>
            </font>
            </td>
            <td>
            <font size="-1">300</font>
            </td>
        </tr>
        <tr>
            <td>
            <font size="-1">
            <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Level.html#ALL" target="_blank">ALL</a>
            </font>
            </td>
            <td>
            <font size="-1">最小整数（Integer.<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Integer.html#MIN_VALUE" target="_blank">MIN_VALUE</a>）</font>
            </td>
        </tr>
    </tbody>
</table>
<font size="-1">
<br>&nbsp;&nbsp;&nbsp;&nbsp;你也可以定义自己的日志级别，但要注意的是，不是直接创建Level的对象（因为它的构造函数是protected的），而是通过继承Level的方式，譬如： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <span class="style3">
            <font color="#ff0000" size="-1">
            <strong>class</strong>
            </font>
            </span>
            <font size="-1"> AlertLevel <span class="style3"><strong><font color="#ff0000">extends</font></strong></span> java.util.logging.Level<br>{<br>&nbsp;&nbsp;<span class="style3"><strong><font color="#ff0000">public</font></strong></span> AlertLevel()<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;super(<span class="style4"><font color="#ff00ff">"ALERT"</font></span>, <span class="style5"><font color="#0000ff">950</font></span>);<br>&nbsp;&nbsp;}<br>}<br>...<br><span class="style6"><strong><font color="#990000">Logger</font></strong></span> logger = <span class="style6"><strong><font color="#990000">Logger</font></strong></span>.getAnonymousLogger();<br>logger.log(<span class="style3"><strong><font color="#ff0000">new</font></strong></span> AlertLevel(), <span class="style4"><font color="#ff00ff">"A dangerous action!"</font></span>);</font>
            </td>
        </tr>
    </tbody>
</table>
<font size="-1">
<br>&nbsp;&nbsp;&nbsp;&nbsp;上面定义了一个高于WARNING但低于SEVERE的日志级别。<br>&nbsp;&nbsp;&nbsp;&nbsp;于是可能有朋友会兴冲冲地用以下的语句来记录一个事件： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <span class="style6">
            <font color="#990000" size="-1">
            <strong>Logger</strong>
            </font>
            </span>
            <font size="-1"> logger = <span class="style6"><strong><font color="#990000">Logger</font></strong></span>.getAnonymousLogger();<br>logger.fine(<span class="style4"><font color="#ff00ff">"Everything seems ok."</font></span>);<br><span class="style7"><font color="#006600">//或者是<br>//logger.log(Level.FINE, "Everything seems ok.");</font></span></font>
            </td>
        </tr>
    </tbody>
</table>
<font size="-1">
<br>&nbsp;&nbsp;&nbsp;&nbsp;但是一程序运行，奇怪了，怎么没有打印出任何消息呢？下一小节我们就来谈这个问题。</font>
</font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<font size="-1">
<strong>5. 日志过滤器：Filter</strong>
<br>&nbsp;&nbsp;&nbsp;&nbsp;
所谓过滤器是控制哪些日志该输出哪些不该输出的一种组件。上面你写的那条日志没有能在控制台显示出来，是因为logging
API预先设定的缺省级别是INFO，也就是说只有级别不低于INFO（即其整数值不小于800）的日志才会被输出，这个就是Filter的功能。所以我
们可以看到SEVERE、WARNING、INFO以及上面我们定义的ALERT消息，但看不到FINE、FINER和FINEST消息。当然，你尽可以
用Logger的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#setLevel%28java.util.logging.Level%29" target="_blank">setLevel</a>方法或者修改配置文件的方法（什么是配置文件，我们后面将会看到）来重新定义Logger输出的最低级别。<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Filter.html" target="_blank">Filter</a>不仅仅可以按日志级别过滤，你也可以定义自己的Filter，实现其中的<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Filter.html#isLoggable%28java.util.logging.LogRecord%29" target="_blank">isLoggable</a>方法，随便按照LogRecord携带的任何信息进行过滤，譬如（顺便复习一下匿名类，呵呵）： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <span class="style6">
            <font color="#990000" size="-1">
            <strong>Logger</strong>
            </font>
            </span>
            <font size="-1"> logger = <span class="style6"><strong><font color="#990000">Logger</font></strong></span>.getAnonymousLogger();<br>logger.setFilter(<span class="style3"><strong><font color="#ff0000">new</font></strong></span><span class="style6"><strong><font color="#990000">Filter</font></strong></span>()<br>{<br>&nbsp;&nbsp;<span class="style3"><strong><font color="#ff0000">public</font></strong></span><span class="style3"><strong><font color="#ff0000">boolean</font></strong></span> isLoggable(<span class="style6"><strong><font color="#990000">LogRecord</font></strong></span> rec)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#006600"><span class="style7">//从LogRecord里得到过滤信息</span><br></font>&nbsp;&nbsp;}<br>});</font>
            </td>
        </tr>
    </tbody>
</table>
<strong>
<br>6. 预定义参数<br></strong>&nbsp;&nbsp;&nbsp;&nbsp;LogManager是一个实现了Singleton模式的全局对象（由于是一个唯一的对象，LogManager需要是线程安全的），它管理着程序启动以后所有已注册（包层次）或匿名的Logger，以及相关配置信息。这里的配置信息通常是从<strong>&lt;JAVA_HOME&gt;\jre\lib\logging.properties</strong>文件得到的。logging.properties对于logging API来说是一个很重要的文件，它的内容一般是：<br>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1">
            <p>############################################################<br># Default Logging Configuration File<br>#<br># You can use a different file by specifying a filename<br># with the java.util.logging.config.file system property. <br># For example java -Djava.util.logging.config.file=myfile<br>############################################################</p>
            <p>############################################################<br># Global properties<br>############################################################</p>
            <p># "handlers" specifies a comma separated list of log Handler <br># classes. These handlers will be installed during VM startup.<br># Note that these classes must be on the system classpath.<br># By default we only configure a ConsoleHandler, which will only<br># show messages at the INFO and above levels.<br>handlers= java.util.logging.ConsoleHandler</p>
            <p># To also add the FileHandler, use the following line instead.<br>#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler</p>
            <p># Default global logging level.<br># This specifies which kinds of events are logged across<br># all loggers. For any given facility this global level<br># can be overriden by a facility specific level<br># Note that the ConsoleHandler also has a separate level<br># setting to limit messages printed to the console.<br>.level= INFO</p>
            <p>############################################################<br># Handler specific properties.<br># Describes specific configuration info for Handlers.<br>############################################################</p>
            <p># default file output is in user's home directory.<br>java.util.logging.FileHandler.pattern = %h/java%u.log<br>java.util.logging.FileHandler.limit = 50000<br>java.util.logging.FileHandler.count = 1<br>java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter</p>
            <p># Limit the message that are printed on the console to INFO and above.<br>java.util.logging.ConsoleHandler.level = INFO<br>java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter<br></p>
            <p>############################################################<br># Facility specific properties.<br># Provides extra control for each logger.<br>############################################################</p>
            <p># For example, set the com.xyz.foo logger to only log SEVERE<br># messages:<br>com.xyz.foo.level = SEVERE</p>
            </font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;你可以通过修改这个配置文件来改变运行时Logger的行为，譬如：.level定义的是上面所说的默认输出的最低日志级别；XXXHandler相关属性定义了各种输出媒介等等。<br>&nbsp;&nbsp;&nbsp;&nbsp;这里比较有意思的是关于日志文件，也就是FileHandler，当然，你可以在程序中创建一个FileHandler，然后添加到logger中： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><span class="style6"><font color="#990000" size="-1"><strong>FileHandler</strong></font></span><font size="-1"> fhd = <span class="style3"><strong><font color="#ff0000">new</font></strong></span><span class="style6"><strong><font color="#990000">FileHandler</font></strong></span>(<span class="style4"><font color="#ff00ff">"%h/java%u.log"</font></span>, <span class="style5"><font color="#0000ff">5000</font></span>, <span class="style5"><font color="#0000ff">1</font></span>, <span class="style3"><strong><font color="#ff0000">true</font></strong></span>);<br>fhd.setLevel(<span class="style6"><strong><font color="#990000">Level</font></strong></span>.ALL);<br>fhd.setFormatter(<span class="style3"><strong><font color="#ff0000">new</font></strong></span><span class="style6"><strong><font color="#990000">XMLFormatter</font></strong></span>());<br>logger.addHandler(fhd);</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;这段代码等价于上面logging.properties中的文字段： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1">java.util.logging.FileHandler.pattern = %h/java%u.log<br>java.util.logging.FileHandler.limit = 50000<br>java.util.logging.FileHandler.count = 1<br>java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;这里的pattern代表用转义字符定义的一个日志文件名： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="50%">
    <tbody>
        <tr>
            <th><font size="-1">转义字符串</font></th><th><font size="-1">含义</font></th>
        </tr>
        <tr>
            <td><font size="-1">%t</font></td>
            <td><font size="-1">临时目录</font></td>
        </tr>
        <tr>
            <td><font size="-1">%h</font></td>
            <td><font size="-1">用户目录，即系统属性&#8220;user.home&#8221;对应的值</font></td>
        </tr>
        <tr>
            <td><font size="-1">%g</font></td>
            <td><font size="-1">一个随机生成的数字，可以重复</font></td>
        </tr>
        <tr>
            <td><font size="-1">%u</font></td>
            <td><font size="-1">一个随机生成的非重复数字</font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;以上面的&#8220;%h/java%u.log&#8221;为例，在Windows 2000下代表日志文件可能就是：C:\Documents and Settings\Administrator\java<strong>x</strong>.log。这里x代表一个不重复的数字，如果是第一次，那么就是java0.log；如果在该目录下已经存在了一个java0.log的文件，那么logger就产生一个java1.log的新的日志文件。<br>&nbsp;&nbsp;&nbsp;&nbsp;当然，你可以在别的地方使用自己写的配置文件，不过在启动程序时候需要指定<strong>java.logging.config.file</strong>属性： </font>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td><font size="-1">java -Djava.logging.config.file=...</font></td>
        </tr>
    </tbody>
</table>
<br><strong>7. 资源与本地化</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;Logger里还有个方法叫<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#logrb%28java.util.logging.Level,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String%29" target="_blank">logrb</a>，
可能初学者不太会用到。如果你安装的JDK是国际版的，那么你将会看到在中文Windows平台下日志输出的INFO、WARNING显示的是&#8220;信息&#8221;、
&#8220;警告&#8221;等中文字样。因为logrb是一个和Java i18n/l10n相关的方法，你可以定义自己的&#8220;资源包&#8221;（Resource
Bundle），然后在logrb方法中指定相应的资源名称，那么在输出日志中你就能看到用自己定义的本地语言、时间等显示的信息。如果你对
i18n/l10n感兴趣，可以参考<a href="http://java.sun.com/j2se/1.4.2/docs/guide/intl/index.html" target="_blank">Java Localization文档</a>。<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;了解以上组件后，我们回顾一个完整的日志处理的工作过程：<br>&nbsp;&nbsp;&nbsp;&nbsp;
程序启动日志服务，创建Logger对象，LogManager按照namespace的层次结构组织Logger，在同一个namespace里子
Logger将继承父Logger的属性；同时，LogManager从logging.properties中读取相应的属性对Logger进行初始
化，如果在程序中设置了属性则使用新的配置。当应用程序产生一条日志，Logger将创建一个LogRecord对象，该对象封装了一条日志的全部信息。
Logger需要根据当前设置的Filter来判断这条日志是否需要输出，并将有用的日志传给相应的Handler处理，而Handler根据当前设置的
Formatter和Resource
Bundle将日志消息转换成一定的显示格式，然后输出到预定的媒介（控制台、文件等）中去。整个过程大致如图1所示： <br></font>
</font>
</p>
<p align="center">
<font class="contentwithhtml">
<font size="2">
<img src="http://www.newsmth.org/pc/pcdownload.php?fid=14825" align="bottom">
</font>
</font>
</p>
<p align="center">
<font class="contentwithhtml">
<font size="2">图1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;前面我们在介绍Handler的时候提到过一个特殊的类叫<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/MemoryHandler.html" target="_blank">MemoryHandler</a>，
这里我们要了解一下&#8220;Handler链&#8221;的概念，日志在输出之前可能经过多个Handler的处理，MemoryHandler在这种情况下就是一个中间
角色，它维持一个内存中的日志缓冲区，当日志没有填满缓冲区时就将全部日志送到下一个Handler，否则新进来的日志将会覆盖最老的那些日志，因此，使
用MemoryHandler可以维护一定容量的日志，另外，MemoryHandler也可以不需要使用Formatter来进行格式化，从而具有较高
的效率。一个使用Handler链的例子如图2所示： </font>
</font>
</p>
<center>
<font class="contentwithhtml">
<font size="2">
<img src="http://www.newsmth.org/pc/pcdownload.php?fid=14826" align="bottom">
<br>图2</font>
</font>
</center>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">青出于蓝：Apache Jakarta log4j日志工具包<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;
应付日常的日志需求，J2SE的Logging
API可以说已经做得相当出色了，但追求完美的开发人员可能需要可扩展性更好的专业日志处理工具，log4j正是当前比较流行的一个工具包，它提供更多的
输出媒介、输出格式和配置选择，你会发现原来在J2SE里一些仍需要自己手工构建的功能在log4j当中都已经为你实现了。关于log4j我可能谈得不会
太多，可以看看文后所附的&#8220;<a href="http://www.smth.org/pc/pcmanage.php?userid=jungleford&amp;act=edit&amp;nid=105614#ref">参考资料</a>&#8221;，网上也有很详细的介绍，我在这里做的是一个对比，因为log4j和J2SE 1.4 Logging API的用法是很相似的，一些名称不同的组件你会发现他们所处的地位其实是一样的： <br>
<table bgcolor="#cccccc" border="1" cellpadding="0" cellspacing="0" width="60%">
    <tbody>
        <tr>
            <th>&nbsp;</th><th><font size="-1">J2SE 1.4中的类</font></th><th><font size="-1">log4j中的类</font></th>
        </tr>
        <tr>
            <th><font size="-1">日志记录器</font></th>
            <td><font size="-1">Logger</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Logger.html" target="_blank">Logger</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">日志管理器</font></th>
            <td><font size="-1">LogManager</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/LogManager.html" target="_blank">LogManager</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">日志对象</font></th>
            <td><font size="-1">LogRecord</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/spi/LoggingEvent.html" target="_blank">LoggingEvent</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">输出媒介控制</font></th>
            <td><font size="-1">Handler</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Appender.html" target="_blank">Appender</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">格式化</font></th>
            <td><font size="-1">Formatter</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Layout.html" target="_blank">Layout</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">级别</font></th>
            <td><font size="-1">Level</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/Layout.html" target="_blank">Level</a></font></td>
        </tr>
        <tr>
            <th><font size="-1">过滤器</font></th>
            <td><font size="-1">Filter</font></td>
            <td><font size="-1"><a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/spi/Filter.html" target="_blank">Filter</a></font></td>
        </tr>
    </tbody>
</table>
<font size="-1"><br>&nbsp;&nbsp;&nbsp;&nbsp;log4j可以做到更精细更完善的控制，譬如J2SE里没有现成向数据库里写日志的方法，但log4j却有<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/jdbc/JDBCAppender.html" target="_blank">JDBCAppender</a>，它甚至还能向GUI图形界面（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/lf5/LF5Appender.html" target="_blank">LF5Appender</a>，一种以JTree方式显示的层次结构）、Windows NT事件查看器（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/nt/NTEventLogAppender.html" target="_blank">NTEventLogAppender</a>）、UNIX的syslogd服务（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/SyslogAppender.html" target="_blank">SyslogAppender</a>）、电子邮箱（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/SMTPAppender.html" target="_blank">SMTPAppender</a>）、Telnet终端（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/TelnetAppender.html" target="_blank">TelnetAppender</a>）、JMS消息（<a href="http://logging.apache.org/log4j/docs/api/org/apache/log4j/net/JMSAppender.html" target="_blank">JMSAppender</a>）
输出日志，牛吧；J2SE里默认只能用%JAVA_HOME%\jre\lib\logging.properties做配置文件，但log4j却可以在
代码中设置其它路径下的properties文件或XML格式的配置文件。log4j的其它方面同样很丰富，总之，log4j的最大的特点就是&#8220;灵活&#8221;，
无论是Appender、Layout还是Configurator，你可以把日志轻松地弄成几乎任何你想要的形式。</font></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">框架与标准：JSR议案<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;从时间顺序上讲，log4j要比J2SE Logging API来得早，很多概念都是log4j先有的，但成为一个标准，则是在<a href="http://www.jcp.org/en/jsr/detail?id=47" target="_blank">JSR 47</a>的形成。可能有人还不太了解<a href="http://www.jcp.org/en/jsr/overview" target="_blank">JSR</a>，这还要谈到<a href="http://www.jcp.org/en/home/index" target="_blank">JCP</a>，即&#8220;Java Community Process&#8221;，它是一个于1998年成立的旨在为Java技术制定民间标准的开放组织，你可以通过<a href="http://www.jcp.org/en/participation/membership" target="_blank">http://www.jcp.org/en/participation/membership</a>申
请成为它的付费或免费会员，JCP的主要工作就是制定和发布JSR（Java Specification
Requests），JSR对于Java的意义就相当于RFC对于网络技术的意义，由于JCP会员们的集思广益，使得JSR成为Java界的一个重要标
准。JSR 47即&#8220;Logging API Specification&#8221;，制定了调试和日志框架，J2SE Logging
API正是该框架的一个实现。由于种种原因，在JSR
47出来以前，log4j就已经成为一项成熟的技术，使得log4j在选择上占据了一定的优势，但不能因此就说JSR
47是过时的规范，标准总是在发展的嘛！</font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">
<strong>
<u>
<font color="#cc0000">
<span class="style2">并不是全部：其它日志处理工具<br></span>
<br>
</font>
</u>
</strong>&nbsp;&nbsp;&nbsp;&nbsp;除了J2SE Logging API和log4j，日志处理方面还有别的技术：Jakarta的<a href="http://jakarta.apache.org/commons/" target="_blank">commons</a>组件项目中的<a href="http://jakarta.apache.org/commons/logging/" target="_blank">JCL</a>（Jakarta
Commons
Logging）是一个不错的选择，它有点类似于GSS-API（通用安全服务接口）中的思想，其日志服务机制是可以替换的，也就是说既可以用J2SE
Logging API也可以用log4j，但JCL对开发人员提供一致的接口，这一点相当重要，组件可重用正是Jakarta
Commons项目追求的一个目标；<a href="http://www.ibm.com/" target="_blank">IBM</a>的<a href="http://www.alphaworks.ibm.com/tech/loggingtoolkit4j" target="_blank">JLog</a>也是在J2SE Logging API之前推出的一个工具包，但JLog是一个商业产品。<br>&nbsp;&nbsp;&nbsp;&nbsp;至于日志API的应用那可就多了，现在哪个大一点的工具或平台不用到日志模块呢？Tomcat、JBoss&#8230;&#8230;<br></font>
</font>
</p>
<p>
<font class="contentwithhtml">
<font size="2">&nbsp;&nbsp;&nbsp;&nbsp;说了这么多，我们无非需要知道的一件事就是，&#8220;调试&#8221;也是一门学问。在我们一个劲地用System.out.println(...)而且用得很爽的时候，也应该想想看，如何让这样一条菜鸟语句也能变得人性化和丰富多彩。</font>
</font>
</p>
<p>
<span class="style2">
<font class="contentwithhtml">
<font size="2">
<a name="ref">
</a>
<strong>
<u>
<font color="#cc0000">参考资料</font>
</u>
</strong>
</font>
</font>
</span>
<font class="contentwithhtml">
<font size="2">
</font>
</font>
</p>
<ul>
    <font class="contentwithhtml">
    <font size="2">
    <li>
    <font size="-1">
    <a href="http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/index.html" target="_blank">Java Logging Documentation</a>
    </font>
    </li>
    <li>
    <font size="-1">
    <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/package-summary.html" target="_blank">Java Logging APIs</a>
    </font>
    </li>
    <li>
    <font size="-1">J2SE进阶, by <a href="http://www.javaresearch.org/" target="_blank"><em>www.javaresearch.org</em></a></font>
    </li>
    <li>
    <font size="-1">
    <a href="http://logging.apache.org/log4j/docs/manual.html" target="_blank">Short introduction to log4j</a>, by <em>Ceki G&#252;lc&#252;</em></font>
    </li>
    <li>
    <font size="-1">
    <a href="http://logging.apache.org/log4j/docs/api/index.html" target="_blank">log4j APIs</a>
    </font>
    </li>
    <li>
    <font size="-1">
    <a href="http://logging.apache.org/log4j/docs/faq.html" target="_blank">FAQ about log4j</a>
    </font>
    </li>
    </font>
    </font>
</ul><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94827.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:45 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94827.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一种Java日志系统框架的设计与实现</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94825.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:36:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94825.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94825.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94825.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94825.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94825.html</trackback:ping><description><![CDATA[<h3>
<span lang="EN-US">1<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">简介</span>
</h3>
<p class="MsoListBullet">
<span style="font-family: 宋体;">日志系统是一种不可或缺的跟踪调试工具，特别是在任何无人职守的后台程序以及那些没有跟踪调试环境的系统中有着广泛的应用。</span>
<span style="font-family: 宋体;">长期以来，</span>
<span style="font-family: 宋体;">日志系统作为一种应用程序服务，对于跟踪调试、程序状态记录、崩溃数据恢复都有非常现实的意义。这种服务通常以两种方式存在：</span>
</p>
<p class="MsoListBullet">
<span style="font-family: verdana;" lang="EN-US">1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志系统作为服务进程存在。</span>
<span style="font-family: verdana;" lang="EN-US">Windows</span>
<span style="font-family: 宋体;">中的的事件日志服务就属于这种类型，该类型的日志系统通常通过消息队列机制将所需要记录的日志由日志发送端发送给日志服务。日志发送端和日志保存端通常不在同一进程当中，日志的发送是异步过程。这种日志服务通常用于管理员监控各种系统服务的状态。</span>
</p>
<p class="MsoListBullet">
<span style="font-family: verdana;" lang="EN-US">2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志系统作为系统调用存在。</span>
<span style="font-family: verdana;" lang="EN-US">Java</span>
<span style="font-family: 宋体;">世界中的日志系统和</span>
<span style="font-family: verdana;" lang="EN-US">Unix</span>
<span style="font-family: 宋体;">环境下诸多守护进程所使用的日志系统都属于这种类型。日志系统的代码作为系统调用被编译进日志发送端，日志系统的运行和业务代码的运行在同一进程空间。日志的发送多数属于同步过程。这种日志服务由于能够同步反映处系统运行状态，通常用于调试跟踪和崩溃恢复。</span>
</p>
<p class="MsoListBullet">
<span style="font-family: 宋体;">本文建立的日志系统基本属于第二种类型，但又有所不同。该日志系统将利用</span>
<span style="font-family: verdana;" lang="EN-US">Java</span>
<span style="font-family: 宋体;">线程技术实现一个既能够反映统一线程空间中程序运行状态的同步日志发送过程，又能够提供快速的日志记录服务，还能够提供灵活的日志格式配置和过滤机制。</span>
</p>
<h4>
<span lang="EN-US">1.1<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">系统调试的误区</span>
</h4>
<p class="MsoListBullet">
<span style="font-family: 宋体;">在控制台环境上调试</span>
<span style="font-family: verdana;" lang="EN-US">Java</span>
<span style="font-family: 宋体;">程序时，此时往控制台或者文本文件输出一段文字是查看程序运行状态最简单的做法，但这种方式并不能解决全部的问题。有时候，对于一个我们无法实时查看系统输出的系统或者一个确实需要保留我们输出信息的系统，良好的日志系统显得相当必要。</span>
</p>
<p class="MsoListBullet">
<span style="font-family: 宋体;">因此，不能随意的输出各种不规范的调试信息，这些随意输出的信息是不可控的，难以清除，可能为后台监控、错误排除和错误恢复带来相当大的阻力。</span>
</p>
<h4>
<span lang="EN-US">1.2<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">日志系统框架的基本功能</span>
</h4>
<p>
<span style="font-family: 宋体;">一个完备的日志系统框架通常应当包括如下基本特性：</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">所输出的日志拥有自己的分类。这样在调试时便于针对不同系统的不同模块进行查询，从而快速定位到发生日志事件的代码。</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志按照某种标准分成不同级别。分级以后的日志，可以用于同一分类下的日志筛选。</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">支持多线程。日志系统通常会在多线程环境中使用，特别是在</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">系统当中，因此作为一种系统资源，日志系统应当保证是线程安全的。</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">支持不同的记录媒介。不同的工程项目往往对日志系统的记录媒介要求不同，因此日志系统必须提供必要的开发接口，以保证能够比较容易的更换记录介质。</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">高性能。日志系统通常要提供高速的日志记录功能以应对大系统下大请求流量下系统的正常运转。</span>
</p>
<p>
<span style="font-family: wingdings;" lang="EN-US">n<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">稳定性。日志系统必须是保持高度的稳定性，不能因为日志系统内部错误导致主要业务代码的崩溃。</span>
</p>
<h4>
<span lang="EN-US">1.3<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">常用日志系统简介</span>
</h4>
<p>
<span style="font-family: 宋体;">在</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">世界中，以下三种日志框架比较优秀：</span>
</p>
<p>
<strong>
<span lang="EN-US">1)<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</strong>
<strong>
<span lang="EN-US">Log4J</span>
</strong>
</p>
<p>
<span style="font-family: 宋体;">最早的</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">日志框架之一，由</span>
<span lang="EN-US">Apache</span>
<span style="font-family: 宋体;">基金会发起，提供灵活而强大的日志记录机制。但是其复杂的配置过程和内部概念往往令使用者望而却步。</span>
</p>
<p>
<strong>
<span lang="EN-US">2)<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</strong>
<strong>
<span lang="EN-US">JDK1.4 Logging Framework</span>
</strong>
</p>
<p>
<span style="font-family: 宋体;">继</span>
<span lang="EN-US">Log4J</span>
<span style="font-family: 宋体;">之后，</span>
<span lang="EN-US">JDK</span>
<span style="font-family: 宋体;">标准委员会将</span>
<span lang="EN-US">Log4J</span>
<span style="font-family: 宋体;">的基本思想吸收到</span>
<span lang="EN-US">JDK</span>
<span style="font-family: 宋体;">当中，在</span>
<span lang="EN-US">JDK1.4</span>
<span style="font-family: 宋体;">中发布了第一个日志框架接口，并提供了一个简单实现。</span>
</p>
<p>
<strong>
<span lang="EN-US">3)<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</strong>
<strong>
<span lang="EN-US">Commons Logging Framwork</span>
</strong>
</p>
<p>
<span style="font-family: 宋体;">该框架同样是</span>
<span lang="EN-US">Apache</span>
<span style="font-family: 宋体;">基金会项目，其出现主要是为了使得</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">项目能够在</span>
<span lang="EN-US">Log4J</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">JDK1.4 l Logging Framework</span>
<span style="font-family: 宋体;">的使用上随意进行切换，因此该框架提供了统一的调用接口和配置方法。</span>
</p>
<h3>
<span lang="EN-US">2<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">系统设计</span>
</h3>
<p>
<span style="font-family: 宋体;">由于</span>
<span lang="EN-US">Log4J</span>
<span style="font-family: 宋体;">得到广泛应用，从使用者的角度考虑，本文所设计的框架，采用了部分</span>
<span lang="EN-US">Log4J</span>
<span style="font-family: 宋体;">的接口和概念，但内部实现则完全不同。使用</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">实现日志框架，关键的技术在于前面提及的日志框架特性的内部实现，特别是：日志的分类和级别、日志分发框架的设计、日志记录器的设计以及在设计中的高性能和高稳定性的考虑。</span>
</p>
<h4>
<span lang="EN-US">2.1<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">系统架构</span>
</h4>
<span style="font-family: 宋体;">日志系统框架可以分为日志记录模块和日志输出模块两大部分。日志记录模块负责创建和管理日志记录器</span>
<span lang="EN-US">(Logger)</span>
<span style="font-family: 宋体;">，每一个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象负责按照不同的级别</span>
<span lang="EN-US">(LoggerLevel)</span>
<span style="font-family: 宋体;">接收各种记录了日志信息的日志对象</span>
<span lang="EN-US">(LogItem)</span>
<span style="font-family: 宋体;">，</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象首先获取所有需要记录的日志，并且同步地将日志分派给日志输出模块。日志输出模块则负责日志</span>
<span style="font-family: 宋体;">输出器</span>
<span lang="EN-US">(Appender)</span>
<span style="font-family: 宋体;">的创建和管理，以及日志的输出。系统中允许有多个不同的日志</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">，日志</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">负责将日志记录到存储介质当中。系统结构如下图</span>
<span lang="EN-US">1</span>
<span style="font-family: 宋体;">所示：<br></span>
<p style="text-align: center;" align="center">
<span lang="EN-US">
<img src="http://www.ncu983.com/youj/images/image001.gif" height="266" width="282">
<br>
<br>
</span>
</p>
<p style="text-align: center;" align="center">
<span style="font-family: 宋体;">图</span>
<span lang="EN-US">1</span>
<span style="font-family: 宋体;">，日志系统结构图</span>
</p>
<p>
<span style="font-family: 宋体;">下图</span>
<span lang="EN-US">2</span>
<span style="font-family: 宋体;">使用</span>
<span lang="EN-US">UML</span>
<span style="font-family: 宋体;">类图给出了日志系统的架构：</span>
</p>
<p style="text-align: center;" align="center">
<span lang="EN-US">
<img src="http://www.ncu983.com/youj/images/image002.gif" height="203" width="262">
<br>
<br>
</span>
</p>
<p style="text-align: center;" align="center">
<span style="font-family: 宋体;">图</span>
<span lang="EN-US">2</span>
<span style="font-family: 宋体;">，日志系统框架架构图</span>
</p>
<p>
<span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<span style="font-family: 宋体;">在图</span>
<span lang="EN-US">2</span>
<span style="font-family: 宋体;">给出的架构中，</span>
<span style="font-family: 宋体;">日志记录器</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">是整个日志系统框架的用户使用接口，程序员可以通过该接口记录日志，为了实现对日志进行分类，系统设计允许存在多个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象，每一个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">负责一类日志的记录，</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">类同时实现了对其对象本身的管理。</span>
<span lang="EN-US">LoggerLevel</span>
<span style="font-family: 宋体;">类定义了整个日志系统的级别，在客户端创建和发送日志时，这些级别会被使用到。</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象在接收到客户端创建和发送的日志消息时，同时将该日志消息包装成日志系统内部所使用的日志对象</span>
<span lang="EN-US">LogItem</span>
<span style="font-family: 宋体;">，日志对象除了发送端所发送的消息以外，还会包装诸如发送端类名、发送事件、发送方法名、发送行号等等。这些额外的消息对于系统的跟踪和调试都非常有价值。包装好的</span>
<span lang="EN-US">LogItem</span>
<span style="font-family: 宋体;">最终被发送给</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">，由这些</span>
<span style="font-family: 宋体;">输出</span>
<span style="font-family: 宋体;">器负责将日志信息写入最终媒介，</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">的类型和个数均不固定，所有的</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">通过</span>
<span lang="EN-US">AppenderManager</span>
<span style="font-family: 宋体;">进行管理，通常通过配置文件即可方便扩展出多个</span>
<span style="font-family: 宋体;">输出器</span>
<span style="font-family: 宋体;">。</span>
</p>
<h4>
<span lang="EN-US">2.2<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">日志记录部分的设计</span>
</h4>
<p>
<span style="font-family: 宋体;">如前文所述，日志记录部分负责接收日志系统客户端发送来的日志消息、日志对象的管理等工作。下面详细描述了日志记录部分的设计要点：</span>
</p>
<p>
<span lang="EN-US">1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志记录器的管理</span>
</p>
<p>
<span style="font-family: 宋体;">系统通过保持多个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象的方式来进行日志记录的分类。每一个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象代表一类日志分类。因此，</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象的名称属性是其唯一标识，通过名称属性获取一个</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象：</span>
</p>
<p class="a">
<strong>
<span lang="EN-US">Logger logger = Logger.getLogger(&#8220;LoggerName&#8221;);</span>
</strong>
</p>
<p>
<span style="font-family: 宋体;">一般的，使用类名来作为日志记录器的名称，这样做的好处在于能够尽量减少日志记录器命名之间的冲突（因为</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">类使用包名），同时能够将日志记录分类得尽可能的精细。因此，假定有一</span>
<span lang="EN-US">UserManager</span>
<span style="font-family: 宋体;">类需要使用日志服务，则更一般的使用方式为：</span>
</p>
<p class="a">
<strong>
<span lang="EN-US">Logger logger = Logger.getLogger(UserManager.class);</span>
</strong>
</p>
<p>
<span lang="EN-US">2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志分级的实现</span>
</p>
<p>
<span style="font-family: 宋体;">按照日志目的不同，将日志的级别由低到高分成五个级别：</span>
</p>
<p>
<span lang="EN-US">DEBUG &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - </span>
<span style="font-family: 宋体;">表示输出的日志为一个调试信息</span>
</p>
<p>
<span lang="EN-US">INFO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - </span>
<span style="font-family: 宋体;">表示输出的日志是一个系统提示</span>
</p>
<p>
<span lang="EN-US">WARN &nbsp; - </span>
<span style="font-family: 宋体;">表示输出的日志是一个警告信息</span>
</p>
<p>
<span lang="EN-US">ERROR - </span>
<span style="font-family: 宋体;">表示输出的日志是一个系统错误</span>
</p>
<p>
<span lang="EN-US">FATAL &nbsp;&nbsp; - </span>
<span style="font-family: 宋体;">表示输出的日志是一个导致系统崩溃严重错误</span>
</p>
<p>
<span style="font-family: 宋体;">这些日志级别定义在</span>
<span lang="EN-US">LoggerLevel</span>
<span style="font-family: 宋体;">接口中，被日志记录器</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">在内部使用。而对于日志系统客户端则可使用</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">类接口对直接调用并输出这些级别的日志，</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">的这些接口描述如下：</span>
</p>
<p class="a">
<span lang="EN-US">public void debug(String msg);&nbsp; //</span>
<span style="font-family: 宋体;">输出调试信息</span>
</p>
<p class="a">
<span lang="EN-US">public void info(String msg); &nbsp; //</span>
<span style="font-family: 宋体;">输出系统提示</span>
<span lang="EN-US">
</span>
</p>
<p class="a">
<span lang="EN-US">public void warn(String msg);&nbsp;&nbsp; //</span>
<span style="font-family: 宋体;">输出警告信息</span>
</p>
<p class="a">
<span lang="EN-US">public void fatal(String msg);&nbsp; //</span>
<span style="font-family: 宋体;">输出系统错误</span>
</p>
<p class="a">
<span lang="EN-US">public void error(String msg);&nbsp; //</span>
<span style="font-family: 宋体;">输出严重错误</span>
</p>
<p>
<span style="font-family: 宋体;">通过对</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象上这些接口的调用，直接为日志信息赋予了级别属性，这样为后继的按照不同级别进行输出的工作奠定了基础。</span>
</p>
<p>
<span lang="EN-US">3.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志对象信息的获取</span>
</p>
<p>
<span style="font-family: 宋体;">日志对象上包含了一条日志所具备的所有信息。通常这些信息包括：输出日志的时间、</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">类、类成员方法、所在行号、日志体、日志级别等等。在</span>
<span lang="EN-US">JDK1.4</span>
<span style="font-family: 宋体;">中可以通过在方法中抛出并且捕获住一个异常，则在捕捉到的异常对象中已经由</span>
<span lang="EN-US">JVM</span>
<span style="font-family: 宋体;">自动填充好了系统调用的堆栈，在</span>
<span lang="EN-US">JDK1.4</span>
<span style="font-family: 宋体;">中则可以使用</span>
<span lang="EN-US">java.lang.StackTraceElement</span>
<span style="font-family: 宋体;">获取到每一个堆栈项的基本信息，通过对日志客户端输出日志方法调用层数的推算，则可以比较容易的获取到</span>
<span lang="EN-US">StackTraceElement</span>
<span style="font-family: 宋体;">对象，从而获取到输出日志时的</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">类、类成员方法、所在行号等信息。在</span>
<span lang="EN-US">JDK1.3</span>
<span style="font-family: 宋体;">或者更早的版本中，相应的工作则必须通过将异常的堆栈信息输出到字符串中，并分析该字符串格式得到。</span>
</p>
<h4>
<span lang="EN-US">2.3<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">日志</span>
<span style="line-height: 172%; font-family: 黑体;">输出部分</span>
<span style="font-family: 黑体;">的设计</span>
</h4>
<p>
<span style="font-family: 宋体;">日志输出部分的设计具有一定的难度，在本文设计的日志系统中，日志的输出、多线程的支持、日志系统的扩展性、日志系统的效率等问题都交由日志输出部分进行管理。</span>
</p>
<p>
<span lang="EN-US">1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">日志输出器的继承结构</span>
</p>
<p>
<span style="font-family: 宋体;">在日志的输出部分采用了二层结构，即定义了一个抽象的日志输出器（</span>
<span lang="EN-US">AbstractLoggerAppender</span>
<span style="font-family: 宋体;">）</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">然后从该抽象类继承出实际的日志输出器。</span>
<span lang="EN-US">AbstractLoggerAppender</span>
<span style="font-family: 宋体;">定义了一系列的对日志进行过滤的方法，而具体输出到存储媒介的方法则是一个抽象方法，由子类实现。在系统中默认实现了控制台输出器和文件输出器两种，其中控制台输出器的实现颇为简单。</span>
</p>
<p>
<span lang="EN-US">2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 宋体;">文件输出器的内部实现</span>
</p>
<p>
<span style="font-family: 宋体;">在日志记录部分的实现中，并没有考虑多线程、高效率等问题，因此文件输出器必须考虑这些问题的处理。在文件输出器内部使用</span>
<span lang="EN-US">java.lang.Vector</span>
<span style="font-family: 宋体;">定
义了一个线程安全的高速缓冲，所有通过日志记录部分分派到文件输出器的日志被直接放置到该高速缓冲当中。同时在文件输出器内部定义一个工作线程，负责定期
将高速缓冲中的内容保存到文件，在保存的过程中同时可以进行日志文件的备份等工作。由于采用了高速缓冲的结构，很显然日志客户端的调用已经不再是一个同步
调用，从而不再会需要等到文件操作后才返回，提高的系统调用的速度。该原理</span>
<span style="font-family: 宋体;">如图</span>
<span lang="EN-US">3</span>
<span style="font-family: 宋体;">所示：</span>
</p>
<p>
<span lang="EN-US">
<img src="http://www.ncu983.com/youj/images/image004.gif" height="1" width="1">
</span>
</p>
<p style="text-align: center;" align="center">
<img src="http://www.ncu983.com/youj/images/image003.gif" height="121" width="289">
<span style="font-family: 宋体;">
<br>
<br>图</span>
<span lang="EN-US">3</span>
<span style="font-family: 宋体;">，文件输出器内部结构</span>
</p>
<h4>
<span lang="EN-US">2.4<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">设计难点</span>
</h4>
<p>
<span style="font-family: 宋体;">通过上述设计，一个具有良好扩展能力的高性能日志系统框架就已经具有了一定的雏形。在设计过程中几个难点问题需要进一步反思。</span>
</p>
<p>
<span style="font-family: 宋体;">一、是否整个系统应当采用完全异步的结构，通过类似于消息机制的方式来进行由日志客户端发送日志给日志系统。这种方式可以作为日志系统框架另一种运行方式，在后继设计中加以考虑。</span>
</p>
<p>
<span style="font-family: 宋体;">二、在文件输出器中可以看到，目前虽然可以扩展多个日志输出器，但是目前提供的抽象类中仅仅提供了对日志的过滤机制，而没有提供的缓存机制，目前的缓存机制被放在文件输出器中实现，因此在未来的进一步设计中，可以将文件输出器中的缓存机制上移到抽象类当中。</span>
</p>
<h4>
<span lang="EN-US">2.5<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">设计模式</span>
</h4>
<br>
<p>
<span style="font-family: 宋体;">在设计过程中我们特别注意使用了数个经典的设计模式。如：</span>
<span lang="EN-US">Logger</span>
<span style="font-family: 宋体;">对象的创建使用了工厂方法模式（</span>
<span lang="EN-US">Factory Method</span>
<span style="font-family: 宋体;">）、由</span>
<span lang="EN-US">AbstractLoggerAppender</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">ConsoleAppender</span>
<span style="font-family: 宋体;">以及</span>
<span lang="EN-US">FileAppender</span>
<span style="font-family: 宋体;">构成了策略模式（</span>
<span lang="EN-US">Strategy</span>
<span style="font-family: 宋体;">），除此以外，还大量使用了单例模式（</span>
<span lang="EN-US">Singleton</span>
<span style="font-family: 宋体;">）。在设计中适当运用设计模式能够加快设计进度、提高设计质量。</span>
</p>
<h3>
<span lang="EN-US">3<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span style="font-family: 黑体;">总结</span>
</h3>
<p>
<span style="font-family: 宋体;">本文探讨了日志系统的基本特性、实现日志系统的意义、方法和内部结构，并且给出了一种基于</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">平台的日志系统的详细设计。同时也指出日志系统会向服务化、异步化的方向发展。作为一种方便的跟踪调试、数据恢复工具，应当提倡在适当的环境下对日志系统的使用。</span>
</p>
<br> <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94825.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:36 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94825.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>log4j 简明手册</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94824.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:34:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94824.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94824.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94824.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94824.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94824.html</trackback:ping><description><![CDATA[<p>Copyright&amp;amp;copy; 2000-2004 The Apache Software Foundation.
版权所有。Log4j软件是在遵守Apache Software License 1.1版的条例下发行的，Apache Software
License的复制件被包括在log4j发布的LICENSE.txt文件里。这个简短手册也借用了The complete log4j
manual 里的一些内容，The complete log4j manual包含最新的更为详尽的信息。 &nbsp;<em>The
complete log4j manual</em></p>
<h2>摘要</h2>
<p>这个文档资料描述了log4j API，它的独特的特性和设计原理。Log4j是由许多
作者共同参与的开放源代码项目。它允许开发人员以任意的精细程度控制哪些
日志说明被输出。通过使用外部的配置文件，可以在运行时配置它。最好的
是，log4j 开发包很容易上手。注意，它也可能会使一些开发人员着迷。</p>
<h2>简 介</h2>
<p>几乎每个大的应用程序都有它自己的日志和跟踪程序的API。顺应这一规则，E.U. <a name="x">SEMPER</a>项目组决定编写它自己的程序跟踪API（tracing API）。这开始于1996年早期。经过无数的工作，更改和性能加强，这个API终于成为一个十分受欢迎的Java日志软件包，那就是log4j。这个软件包的发行遵守<a name="x">open source</a>动议认证的<a name="x">Apache Software License</a>。最新的log4j版本包括全部的源代码，类文件和文档资料，可以在 <a name="x"><strong>http://logging.apache.org/log4j/</strong></a>找到它们。另外，log4j已经被转换成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 语言。</p>
<p>把log statements插入到你的代码中是一种排错的低技能办法。这也许是唯一的方法，因为排错工具并不总是可以被使用或者适用于你的程序。对于多线程的应用程序和多数发行的应用程序，通常就是这样的情形。</p>
<p>经验告诉我们logging是开发过程中重要的一环。它具有多种优点。首先，它能精确地提供运行时的上下文(<em>context</em>)。
一旦在程序中加入了Log
代码，它就能自动的生成并输出logging信息而不需要人为的干预。另外，log信息的输出可以被保存到一个固定的地方，以备以后研究。除了在开发过程
中发挥它的作用外，一个性能丰富的日志记录软件包能当作一个审计工具(audit tool)使用。</p>
<p>Brian W. Kernighan 和 Rob Pike 在他们的"The Practice of Programming" 书中这样写到： &nbsp;<em>"The Practice of Programming"</em></p>
<pre>作为个人的选择，除了得到一大堆程序跟踪信息或一两个变量值以外，我们倾<br>向於不使用排错器。一个原因是在详细而复杂的数据结构和控制流程中很容易<br>迷失；我们发现认真思考并在关键处加入自我检查代码和输出指令，比起一步<br>步看程序要效率高。在日志说明里查找比在明智地放置自我检查代码后的输出<br>里查找要费时。而决定在哪里放置打印指令要比在日志说明里一步步找到关键<br>的代码要省时间。更重要的是，自我检查的排错指令和程序并存；而排错<br>sessions是暂时的。</pre>
<p>Logging确实也有它的缺陷。它降低了程序运行的速度。它太冗长，查看时很容易错过。为了减少这些负面影响，log4j 被设计得可靠，高效和灵活。因为，记录日志很少是一个应用程序的主要焦点，log4j API 尽量做到容易被理解和使用。</p>
<h2>Loggers, Appenders and Layouts</h2>
<p>Log4j 有三个主要组件：<em>loggers</em>, <em>appenders</em>和<em>layouts</em>。这三类组件一起应用，可以让开发人员能够根据日志的类型和级别进行记录，并且能在程序运行时控制log信息输出的格式和往什么地方输出信息。</p>
<h3>Logger hierarchy</h3>
<p>任何logging API 与简单的<code>System.out.println</code>输出调试信息方法比较，最主要的优点在于它能够关闭一些调试信息输出而不影响其他人的调试。这种能力的实现是假设这些logging空间，也就是所有的可能发生的日志说明空间，可以根据程序开发人员选择的标准进行分类。这一观察以前使得我们选择了<em>category</em>作为这个软件包的中心概念。但是，在log4j 1.2版本以后，<code><code>Logger</code></code>类取代了<code><code>Category</code></code>类。对于那些熟悉早先版本的log4j的开发人员来说，Logger类只不过是Category类的一个别名。</p>
<p>Loggers是被命名的实体。Logger的名字大小写有区别（case-sensitive），并且它们遵守阶层式的命名规则：</p>
<p>
</p>
<table bgcolor="#eeee99">
    <tbody>
        <tr>
            <td>
            <dl>
            <dt>
            <strong>Named Hierarchy</strong>
            </dt>
            <dd>
            <p>如果一个logger 的名字后面跟着一个点号（dot），它就是点号（dot）后面的那个logger的前辈（ <em>ancestor</em>），是这个晚辈（<em>descendant</em>） 的前缀。如果在它自己和这个晚辈之间没有其它的前辈，它和这个晚辈之间就是<em>父</em><em>子</em>关系。</p>
            </dd>
            </dl>
            </td>
        </tr>
    </tbody>
</table>
<p>例如，叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父辈 。同样地，"java"是"java.util" 的父辈，是"java.util.Vector"的前辈。大多数开发人员都熟悉这种命名方法。 &nbsp;<code>"com.foo"</code> &nbsp;<code>"com.foo.Bar"</code> &nbsp;<code>"java"</code> &nbsp;<code>"java.util"</code> &nbsp;<code>"java.util.Vector"</code></p>
<p>根（root）logger 位于logger 阶层的最上层。它在两个方面很特别:</p>
<ol>
    <li>它总是存在的，</li>
    <li>不能通过使用它的名字直接得到它。</li>
</ol>
<p>通过这个类的静态方法<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">Logger.getRootLogger</a>得到它（指RootLogger）。所有其他的loggers是通过静态方法<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">Logger.getLogger</a>来实例化并获取的。这个方法Logger.getLogger把所想要的logger的名字作为参数。 Logger类的一些其它基本方法在下面列出：</p>
<p>
</p>
<table>
    <tbody>
        <tr bgcolor="#cccccc">
            <td>
            <pre>package org.apache.log4j; <br>public class <strong>Logger</strong> { <br><br>  // Creation and retrieval methods: <br>  public static Logger getRootLogger(); <br>  public static Logger getLogger(String name); <br><br>  // printing methods: <br>  public void debug(Object message); <br>  public void info(Object message); <br>  public void warn(Object message); <br>  public void error(Object message); <br>  public void fatal(Object message); <br><br>  // generic printing method: <br>  public void log(Level l, Object message); <br><br>}</pre>
            </td>
        </tr>
    </tbody>
</table>
<p>Loggers<em>可以</em>被指派优先级别。<a name="x">Level.html#DEBUG"&gt;DEBUG</a>, <a name="x">INFO</a>, <a name="x">WARN</a>, <a name="x">ERROR</a> 和<a name="x">FATAL</a>这组
级别在<code><a name="x">org.apache.log4j.Level</a></code>类中有定义。你也<em>可以</em>通过Level类的子类去定
义你自己的优先级别，尽管我们不鼓励你这样做。在后面我们会讲到一个更好
的方法。</p>
<p>如果一个logger没有被指定优先级别，它将继承最接近的祖先所被指定的优先级别。下面是更多关于优先级别的信息：</p>
<p>
</p>
<table bgcolor="#eeee99">
    <tbody>
        <tr>
            <td>
            <dl>
            <dt>
            <strong>Level Inheritance</strong>
            </dt>
            <dd>
            <p>对于一个给定的logger C，它<em>继承的级别</em>等于logger阶层里，从C开始往<code>root</code> logger上去的第一个non-null级别。</p>
            </dd>
            </dl>
            </td>
        </tr>
    </tbody>
</table>
<p>要保证所有的loggers最终都继承一个优先级别，root logger总是有一个被指派的优先级。</p>
<p>下面是具有各种指派优先级别值的四个表格，以及根据上面的规则所得出的继承优先级别。</p>
<p>
</p>
<table border="1">
    <tbody>
        <tr>
            <th>Logger<br>name(名称)</th>
            <th>指派<br>级别</th>
            <th>继承<br>级别</th>
        </tr>
        <tr align="left">
            <td>根</td>
            <td>Proot</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X </td>
            <td>none</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X.Y </td>
            <td>none</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X.Y.Z</td>
            <td>none</td>
            <td>Proot</td>
        </tr>
    </tbody>
    <caption align="bottom">例子<br></caption>
</table>
<p>在上面的示例1中，只有root logger被指派了级别。这个级别的值，<code>Proot</code>，被其它的loggers X, <code>X.Y</code> 和 <code>X.Y.Z</code>继承了。</p>
<p>
</p>
<table border="1">
    <tbody>
        <tr>
            <th>Logger<br>name(名称)</th>
            <th>指派<br>级别</th>
            <th>继承<br>级别</th>
        </tr>
        <tr align="left">
            <td>根</td>
            <td>Proot</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X </td>
            <td>Px</td>
            <td>Px</td>
        </tr>
        <tr align="left">
            <td>X.Y </td>
            <td>Pxy</td>
            <td>Pxy</td>
        </tr>
        <tr align="left">
            <td>X.Y.Z</td>
            <td>Pxyz</td>
            <td>Pxyz</td>
        </tr>
    </tbody>
    <caption align="bottom">例子<br></caption>
</table>
<p>在上面的示例2中，所有的loggers都有一个指派的级别值。不需要级别继承。</p>
<p>
</p>
<table border="1">
    <tbody>
        <tr>
            <th>Logger<br>name(名称)</th>
            <th>指派<br>级别</th>
            <th>继承<br>级别</th>
        </tr>
        <tr align="left">
            <td>根</td>
            <td>Proot</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X </td>
            <td>Px</td>
            <td>Px</td>
        </tr>
        <tr align="left">
            <td>X.Y </td>
            <td>none</td>
            <td>Px</td>
        </tr>
        <tr align="left">
            <td>X.Y.Z</td>
            <td>Pxyz</td>
            <td>Pxyz</td>
        </tr>
    </tbody>
    <caption align="bottom">例子<br></caption>
</table>
<p>在示例3中，loggers <code>root</code>, X 和 <code><code>X.Y</code>.Z</code> 分别被指派级别值<code>Proot</code>, <code>Px</code> 和<code>Pxyz</code>。Logger X.Y 从它的父辈X那里继承它的级别值。</p>
<table border="1">
    <tbody>
        <tr>
            <th>Logger<br>name(名称)</th>
            <th>指派<br>级别</th>
            <th>继承<br>级别</th>
        </tr>
        <tr align="left">
            <td>根</td>
            <td>Proot</td>
            <td>Proot</td>
        </tr>
        <tr align="left">
            <td>X </td>
            <td>Px</td>
            <td>Px</td>
        </tr>
        <tr align="left">
            <td>X.Y </td>
            <td>none</td>
            <td>Px</td>
        </tr>
        <tr align="left">
            <td>X.Y.Z</td>
            <td>none</td>
            <td>Px</td>
        </tr>
    </tbody>
    <caption align="bottom">例子<br></caption>
</table>
<p>在示例4中，loggers <code>root</code>和X 分别被指派级别值<code>Proot</code>和<code>Px</code>。Logger <code>X.Y</code>和<code>X.Y.Z</code>继承它们最接近的父辈X的被指派的级别值。</p>
<p>日志请求是通过调用一个日志实例的打印方法（之一）而产生的。这些打印方法是<code><a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/%3Ca%20href=" java.lang.object="">log</a></code>4j/Logger.html#debug(java.lang.Object)"&gt;debug, <a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">info</a>, <a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">warn</a>, <a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">error</a>, <a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">fatal</a> 和 log。</p>
<p>根据定义，打印方法决定一个日志请求的级别。例如，如果c是一个日志实例，那么语句c.info("..") 就是级别为INFO的一个日志请求。 &nbsp;<code>c.info("..")</code></p>
<p>只有一个日志请求(A logging request)的级别高于或等于它的logger级别的时候才<em>能够被执行</em>。否则，则被认为这个日志请求<em>不能被执行</em>。一个没有被定义优先级别的logger将从层次关系中的前辈那里继承优先级别。这个规则总结如下：</p>
<p>
<a name="selectionRule">
</a>
</p>
<table bgcolor="#eeee99">
    <tbody>
        <tr>
            <td>
            <dl>
            <dt>
            <strong>Basic Selection Rule</strong>
            </dt>
            <dd>
            <p>在一个级别为q（被指定的或继承的）的logger里，一个级别为p的日志请求，只有在<em>p &gt;= q</em> 时才能够被执行。</p>
            </dd>
            </dl>
            </td>
        </tr>
    </tbody>
</table>
<p>
<a name="selectionRule">这个规则是log4j的核心。它假设级别是有先后顺序的。对于标准的优先级别来说，<code>DEBUG &lt; INFO &lt; WARN &lt; ERROR &lt; FATAL</code>。</a>
</p>
<p>
<a name="selectionRule">这里是一个关于这个规则的例子：</a>
</p>
<p>
</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre>   // get a logger instance named "com.foo"<br>   Logger  logger = Logger.getLogger(<strong>"com.foo"</strong>);<br><br>   // Now set its level. Normally you do not need to set the<br>   // level of a logger programmatically. This is usually done<br>   // in configuration files.<br><strong>logger</strong>.setLevel(<font color="#0000aa"><strong>Level.INFO</strong></font>);<br><br>   Logger barlogger = Logger.getLogger(<strong>"com.foo.Bar"</strong>);<br><br>   // This request is enabled, because <font color="#00aa00"><strong>WARN</strong></font> &gt;= <font color="#0000aa"><strong>INFO</strong></font>.<br>   logger.<font color="#00aa00"><strong>warn</strong></font>("Low fuel level.");<br><br>   // This request is disabled, because <font color="#00aa00"><strong>DEBUG</strong></font> &lt; <font color="#0000aa"><strong>INFO</strong></font>.<br>   logger.<font color="#00aa00"><strong>debug</strong></font>("Starting search for nearest gas station.");<br><br>   // The logger instance barlogger, named "com.foo.Bar",<br>   // will inherit its level from the logger named<br>   // "com.foo" Thus, the following request is enabled<br>   // because <font color="#00aa00"><strong>INFO</strong></font> &gt;= <font color="#0000aa"><strong>INFO</strong></font>.<br>   barlogger.<font color="#00aa00"><strong>info</strong></font>("Located nearest gas station.");<br><br>   // This request is disabled, because <font color="#00aa00"><strong>DEBUG</strong></font> &lt; <font color="#0000aa"><strong>INFO</strong></font>.<br>   barlogger.<font color="#00aa00"><strong>debug</strong></font>("Exiting gas station search");<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>
<a name="selectionRule">以一样的叁数名字调用<code>getLogger</code>方法，返回的reference总是指向完全相同的logger对象。</a>
</p>
<p>
<a name="selectionRule">例如，在这里：</a>
</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre>   Logger x = Logger.getLogger("wombat");<br>   Logger y = Logger.getLogger("wombat");</pre>
            </td>
        </tr>
    </tbody>
</table>
<a name="selectionRule">x和y指向<em>完全</em>相同的logger对象。</a>
<p>
<a name="selectionRule">因此，通过这种方式可以配置一个logger，而不需要传递references就能在其
他地方得到相同的实例。在生物的父子关系中父母总是排放在孩子们前面，
log4j loggers与此有相互矛盾的地方，那就是log4j loggers可以以任何顺序被
产生和配置。特别的是，一个"parent" logger 会找到并连接他的后代，即使他
是在他们之后被定义。</a>
</p>
<p>
<a name="selectionRule">Log4j环境通常是在程序被初始化的时候被配置的。最好的方式是通过阅读一
个配置文件去配置。我们会马上讨论到这方面的内容。</a>
</p>
<p>
<a name="selectionRule">Log4j使得通过软件组件的名称去定义loggers的名字很容易。这可以通过在每
个类中静态地instantiating一个logger，让logger的名字与这个合格的java类文
件名相同来完成。这是一种有用并且直观的定义loggers的方式。因为日志的
输出带有产生它们的logger的名字，这种命名策略使我们能够很方便地识别这
些log信息的来源。不过，尽管这是通用的一种loggers命名策略，Log4j没有限
制怎样对loggers进行命名。开发程序员可以根据自己的喜好随意定义
loggers。 &nbsp;<em>software
component</em></a>
</p>
<p>
<a name="selectionRule">当然，至今所知的最好的命名策略还是以它们所在的类的名称来命名
loggers。</a>
</p>
<h2>
<a name="selectionRule">Appenders and Layouts</a>
</h2>
<p>
<a name="selectionRule">基于自身的logger选择性地使用或不使用日志请求（logging requests ）的能
力仅仅整个Log4j能力的一部分。Log4j允许将log信息输出到许多不同的输出
设备中。用log4j的语言来说，一个log信息输出目的地就叫做一个<em>appender</em>。
目前，log4j 的appenders可以将log信息输出到</a>
<a name="x">console</a>，<a name="x">files</a>，GUI
components，<a name="x">remote socket</a> servers， <a name="x">JMS</a>，NT Event Loggers，和
remote UNIX <a name="x">Syslog</a> daemons。它还可以<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/AsyncAppender.html">同时</a>将log信息输出到多个输出设备
中。 &nbsp;<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/nt/NTEventLogAppender.html"> NT
Event Loggers</a></p>
<p>多个appenders可以和一个logger连接在一起。</p>
<p>使用<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">addAppender</a>方法把一个appender加入到给定的logger上。一个给定的
logger的每一个被允许的日志请求都会被传递给
这个logger的所有appenders，以及阶层中高级别的appenders。
换句话说appenders是从logger阶层中不断添加地被继承的。例如，一个
console appender加给了root logger，那么，这个root logger所有被允许输出
的日志信息将被输出到console。如果你又给一个名字为C的logger添加了一个
file appender，那么C 以及C的子辈的所有被允许的日志信息将被同时输出到
file appender<em>和</em>console appender。可以通过把additivity flag设置为<code>false</code>来覆
盖这个默认的行为从而使appender的继承关系不再是添加性的。 &nbsp;<strong>Each enabled logging
request for a given logger will be forwarded to all the appenders in
that logger as well as the appenders higher in the hierarchy.</strong> &nbsp;<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Logger.html">setting
the additivity flag</a></p>
<p>支配appender添加性的规则总结如下：</p>
<p>
<a name="additivity">
</a>
</p>
<table bgcolor="#eeee99">
    <tbody>
        <tr>
            <td>
            <dl>
            <dt>
            <strong>Appender Additivity</strong>
            </dt>
            <dd>
            <p>Logger C的log输出信息将被输出到C的所有appenders和它的前辈的
            appenders。这就是"appender additivity"的意思。</p>
            <p>但是，如果logger C的前辈，比如说P，P的additivity flag被设置为 <code>false</code>，那
            么，C的输出信息将被输出到C的所有appenders中去，以及它的前辈的——截
            止在P那里，包括P在内的，appenders中去，但是不会输出到P的前辈的
            appenders中去。</p>
            <p>默认情况下，Loggers的additivity flag设置为<code>true</code>。</p>
            </dd>
            </dl>
            </td>
        </tr>
    </tbody>
</table>
<p>
<a name="additivity">下面的表格显示一个示例：</a>
</p>
<p>
</p>
<table align="center" border="3" cellpadding="10">
    <tbody>
        <tr rowspan="2">
            <th>Logger<br>name(名称)</th>
            <th>添加的<br>Appenders </th>
            <th>Additivity<br>旗标</th>
            <th>输出目标</th>
            <th>注释</th>
        </tr>
        <tr>
            <td>根</td>
            <td>A1         </td>
            <td>not applicable </td>
            <td>A1
            </td>
            <td>Root logger是无名的，但是可以通过Logger.getRootLogger() 来访问。Root
            logger没有附带默认的appender。</td>
        </tr>
        <tr>
            <td>x       </td>
            <td>A-x1, A-x2 </td>
            <td>true </td>
            <td>A1, A-x1, A-x2
            </td>
            <td>"x" 和root logger里的Appenders。</td>
        </tr>
        <tr>
            <td>x.y     </td>
            <td>none       </td>
            <td>true </td>
            <td>A1, A-x1, A-x2
            </td>
            <td>"x" 和root logger里的Appenders。</td>
        </tr>
        <tr>
            <td>x.y.z   </td>
            <td>A-xyz1     </td>
            <td>true </td>
            <td>A1, A-x1, A-x2, A-xyz1
            </td>
            <td>"x.y.z", "x" 和root logger里的Appenders。</td>
        </tr>
        <tr>
            <td>安全</td>
            <td>A-sec	   </td>
            <td>
            <font color="blue">false</font>
            </td>
            <td>A-sec
            </td>
            <td>因为additivity flag被设置为 <code>false</code>，所以没有appender继承积累。</td>
        </tr>
        <tr>
            <td>security.access </td>
            <td>none </td>
            <td> true </td>
            <td> A-sec </td>
            <td>因为"security" logger里的additivity flag被设置为<code>false</code>，所以仅仅只
            有"security" logger的appenders。</td>
        </tr>
    </tbody>
</table>
<p>
<a name="additivity">通常，用户不仅希望自己指定log信息的输出目的地，而且，他们还希望指定
log信息的输出格式。这可以通过和appender相关的<em>layout</em>实现。Layout负责
根据用户的需要去格式化log信息的输出，而appender负责将一个格式化过的
log信息输出到它的目的地。</a>
<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/PatternLayout.html">PatternLayout</a> 是标准log4j发行包中的一部分，它
让用户根据和C语言中的<code>printf</code>方法相似的转换模式指定输出格式。</p>
<p>例如，具有"%r [%t] %-5p %c - %m%n" 转换格式的PatternLayout 将输出以下
的式样：</p>
<pre>176 [main] INFO org.foo.Bar - Located nearest gas station.</pre>
<p>第一个区域是从程序开始运行到输出日志信息所用的毫秒数。第二个区域是产
生日志请求的线程。第三个区域是这个log语句的优先级别。第四个区域是和
日志请求相关联的logger名字。在'-' 之后的文字是这个log信息的内容。</p>
<p>同样重要的是，log4j 将根据用户指定的标准来表达log信息的内容。例如，如
果你经常需要日志记录<code>Oranges</code>，Oranges是你当前项目中使用的一个对象类
型，那么你可以注册一个<code>OrangeRenderer</code>，这样每当需要日志记录一个
orange时，OrangeRenderer就会被调用。</p>
<p>对象的表达遵照类阶层（class hierarchy）形式。例如，假设oranges是
fruits，你注册了一个<code><code>FruitRenderer</code></code>，那么，包括oranges在内的所有的fruits
都将由FruitRenderer来表达，除非你自己为orange注册了一个特定的
<code>OrangeRenderer</code>。</p>
<p>Object renderers必须实施<a name="x">ObjectRenderer</a>界面。</p>
<h2>配 置</h2>
<p>在程序代码中插入这些日志请求需要相当大的工作量。调查显示，大约%4左
右的代码是logging。因此，即便是中等大小的应用程序也需要在它们的代码中
至少包含有几千行的log语句。就从这个数目来看，管理这些log语句而不用人
工地去修改它们是十分重要的。</p>
<p>Log4j环境是完全能够通过编程来配置的。但是使用配置文件去配置则更灵
活。目前，Log4j的配置文件是以XML格式和JAVA properties (key=value) 格
式编写的。</p>
<p>假设我们有个叫<code>MyApp</code>的程序使用log4j，让我们来看看这是怎样做到的：</p>
<p>
</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre> import com.foo.Bar;<br><br> // Import log4j classes.<br><strong>import org.apache.log4j.Logger;<br> import org.apache.log4j.BasicConfigurator;</strong><br><br> public class MyApp {<br><br>   // Define a static logger variable so that it references the<br>   // Logger instance named "MyApp".<br><strong>static</strong> Logger logger = <strong>Logger.getLogger(MyApp.class);</strong><br><br>   public static void main(String[] args) {<br><br>     // Set up a simple configuration that logs on the console.<br><strong>BasicConfigurator.configure();</strong><br><br>     logger.info("Entering application.");<br>     Bar bar = new Bar();<br>     bar.doIt();<br>     logger.info("Exiting application.");<br>   }<br> }<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>
<code>
<code>MyApp</code>
</code>类首先引入log4j的相关类，然后定义一个命名为MyApp的静态logger变
量，而这个名字恰好和MyApp的类名一样。</p>
<p>
<code>MyApp</code>类还使用了被定义在<code>com.foo</code>包中的<code>Bar</code>类：</p>
<p>
</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre>												<strong>package com.foo;</strong>
            <br> import org.apache.log4j.Logger;<br><br> public class Bar {<br><strong>static</strong> Logger logger = <strong>Logger.getLogger(Bar.class);</strong><br><br>   public void doIt() {<br>     logger.debug("Did it again!");<br>   }<br> }<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>通过调用<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/BasicConfigurator.html">BasicConfigurator.configure</a> 方法产生一个相当简单的log4j的设置。
这个方法将一个<a name="x">
ConsoleAppender</a>添加到root logger，从而让log信息输出到
console。通过把<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/PatternLayout.html">PatternLayout</a>设置为 %-4r [%t] %-5p %c %x - %m%n来确
定输出格式。</p>
<p>注意，默认的root logger被指派为<code>Level.DEBUG</code>。</p>
<p>MyApp的输出是这样的：</p>
<pre>0    [main] INFO  MyApp  - Entering application.<br>36   [main] DEBUG com.foo.Bar  - Did it again!<br>51   [main] INFO  MyApp  - Exiting application.<br></pre>
<p>下面的图形描绘了在调用<code>BasicConfigurator.configure</code>方法之后，<code>MyApp</code>的对
象图表。</p>
<p>
</p>
<center>
<img src="http://logging.apache.org/log4j/docs/od.gif" align="middle">
</center>
<p>注意，log4j 的子代loggers只和它们现有的前辈链接。在这里，名字叫
<code><code>com</code>.foo.Bar</code>的logger直接和<code>root</code> logger链接，因此绕过了没有被使用的com
或<code>com.foo</code> loggers。这样极大地提高了log4j的性能并减少了内存（memory）
的使用。</p>
<p>通过调用<code>BasicConfigurator.configure</code>方法来配置<code>MyApp</code>类。其它的类只需要
引入<code>org.apache.log4j.Logger</code>类，获取它们想要使用的loggers，就可以输出
log。</p>
<p>先前的例子总是输出同样的log信息。幸运的是，很容易修改<code>MyApp</code>程序就可
以在程序运行时对log输出进行控制。下面是略加修改后的版本：</p>
<p>
</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre> import com.foo.Bar;<br><br> import org.apache.log4j.Logger;<br><strong>import org.apache.log4j.PropertyConfigurator;</strong><br><br> public class MyApp {<br><br>   static Logger logger = Logger.getLogger(MyApp.class.getName());<br><br>   public static void main(String[] args) {<br><br><br>     // BasicConfigurator replaced with PropertyConfigurator.<br><strong>PropertyConfigurator.configure(args[0]);</strong><br><br>     logger.info("Entering application.");<br>     Bar bar = new Bar();<br>     bar.doIt();<br>     logger.info("Exiting application.");<br>   }<br> }<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>这个例子中<code>MyApp</code>指示<code>PropertyConfigurator</code>方法去解读配置文件并设置相应
的logging 。</p>
<p>这里是一个配置文件的示例，这个配置文件产生和前面<code>BasicConfigurator</code>例子
完全一样的输出结果：</p>
<p>
</p>
<table bgcolor="#ccaaaa">
    <tbody>
        <tr>
            <td>
            <pre># Set root logger level to DEBUG and its only appender to A1.<br>log4j.rootLogger=DEBUG, A1<br><br># A1 is set to be a ConsoleAppender.<br>log4j.appender.A1=org.apache.log4j.ConsoleAppender<br><br># A1 uses PatternLayout.<br>log4j.appender.A1.layout=org.apache.log4j.PatternLayout<br>log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<!--
<p>Please note that if you copy and paste the examples, then result is
likely to include trailing spaces on some lines. These trailing spaces
are not trimmed out but interpreted by the PropertyConfigurator.  By
the time you read this article the problem should be corrected.
-->
<p>假设我们不再需要<code>com.foo</code>软件包里任何组件的日志输出，下面的配置文件展
示了达到这一目的的一种可能的方法：</p>
<p>
</p>
<table bgcolor="#ccaaaa">
    <tbody>
        <tr>
            <td>
            <pre>log4j.rootLogger=DEBUG, A1<br>log4j.appender.A1=org.apache.log4j.ConsoleAppender<br>log4j.appender.A1.layout=org.apache.log4j.PatternLayout<br><br># <strong>Print the date in ISO 8601 format</strong><br>log4j.appender.A1.layout.ConversionPattern=<strong>%d</strong> [%t] %-5p %c - %m%n<br><br># Print only messages of level WARN or above in the package com.foo.<br><strong>log4j.logger.com.foo=WARN</strong></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>由这个文件所配置的<code>MyApp</code>的日志输出如下：</p>
<pre>				<strong>2000-09-07 14:07:41,508</strong> [main] INFO  MyApp - Entering application.<br><strong>2000-09-07 14:07:41,529</strong> [main] INFO  MyApp - Exiting application.<br></pre>
<p>因为logger <code><code>com.foo</code>.Bar</code> 没有指定的优先级别，它就从com.foo中继承优先级
别，而com.foo的优先级别在配置文件中被设置为WARN。 <code>Bar.doIt</code> 方法里的
log语句的级别为DEBUG，比WARN级别低。所以，<code>doIt()</code>方法的日志请求就被
压制住了。</p>
<p>这里是另一个使用多个appenders的配置文件。</p>
<p>
</p>
<table bgcolor="#ccaaaa">
    <tbody>
        <tr>
            <td>
            <pre>log4j.rootLogger=debug, <strong>stdout, R</strong><br><br>log4j.appender.<strong>stdout</strong>=org.apache.log4j.ConsoleAppender<br>log4j.appender.stdout.layout=org.apache.log4j.PatternLayout<br><br># Pattern to output the caller's file name and line number.<br>log4j.appender.stdout.layout.ConversionPattern=%5p [%t] <strong>(%F:%L)</strong> - %m%n<br><br>log4j.appender.<strong>R</strong>=org.apache.log4j.RollingFileAppender<br>log4j.appender.R.File=example.log<br><br>log4j.appender.R.MaxFileSize=<strong>100KB</strong><br># Keep one backup file<br>log4j.appender.R.MaxBackupIndex=1<br><br>log4j.appender.R.layout=org.apache.log4j.PatternLayout<br>log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>调用以这个配置文件增强了的MyApp会把下列输出信息输出到控制台
（console）上。</p>
<pre> INFO [main] <strong>(MyApp2.java:12)</strong> - Entering application.<br>DEBUG [main] (Bar.java:8) - Doing it again!<br> INFO [main] (MyApp2.java:15) - Exiting application.<br></pre>
<p>另外，当root logger增加了第二个appender时，log信息将同时也被输出到
<code><code>example.log</code></code>文件中。当example.log文件达到100KB 后，example.log文件将
被rolled over。当roll-over 发生时，example.log 的老版本将自动被移到
<code>example.log.1</code>中去。</p>
<p>注意，要获得这些不同的logging行为并不需要重新编译代码。我们还可以简单
地通过修改log配置文件把log信息输出到UNIX Syslog daemon中，把所有
<code>com.foo</code>的日志输出转指向NT Event logger 中，或者把log事件输出到远程
log4j服务器中，当然它要根据局部服务器规则进行log，例如可以把log事件输
出到第二个log4j服务器中去。</p>
<h2>默认的初始化过程</h2>
<p>Log4j库没有对它的环境作任何假设。特别是，没有默认的log4j appenders。
不过在一些精细定义过的情况下，这个<code>Logger</code>类的静态的initializer会试图自动
配置log4j。 Java语言确保一个类的静态的initializer在这个类被装载到内存里
时被调用一次，而且仅仅一次。这点很重要，要记住不同的classloaders会装
载同一个类的不同复制版。这些同一个类的不同复制版在JVM看来是完全不相
关的。</p>
<p>默认的初始化在这样的环境中很有用处，那就是同一个程序依据运行时的环境
作不同用处。例如，同样一个程序可以在web-server的控制下作为单独的程
序，作为一个applet，或者作为一个servlet被使用。</p>
<p>默认的初始化运算法则定义如下：</p>
<ol>
    <li>把<strong>log4j.defaultInitOverride</strong>的系统属性设置为 "false"以外的任何值将会造成
    log4j跳过默认的初始化过程。</li>
    <li>把<code><code>resource</code></code>这个string变量设置为<strong><strong><strong>log4j.configuration</strong></strong></strong>系统属性的值。
    最好的方法指定默认初始化文件是通过log4j.configuration系统属性来指定。
    在log4j.configuration系统属性没有被定义的情况下，把resource这个string变
    量设置成它的默认值"log4j.properties"。</li>
    <li>把<code>resource</code>变量转换为一个URL。</li>
    <li>如果这个<code>resource</code>变量不能转换为一个URL，例如，因为
    <code>MalformedURLException</code>的缘故，那么就通过调用
    org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在
    classpath上搜寻resource，它会返回一个URL。注意，
    string "log4j.properties"是一个不合式的URL。 &nbsp;<code>org.apache.log4j.helpers.Loader.getResource(resource,
    Logger.class)</code>
    <p>有关搜寻地址列单，请参看<a name="x">Loader.getResource(java.lang.String)</a>。</p>
    </li>
    <li>如果不能找到URL，那就放弃默认的初始化。否则，从URL配置log4j 。
    <p><a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Property%3Ca%20%20name=" x="">Configurator</a>.html"&gt;PropertyConfigurator将被用于解读URL来配置log4j，除非这个URL以".xml"扩
    展符结束，若这个URL以".xml"扩展符结束，<a name="x">DOMConfigurator</a>则被使用。你可
    以选择性地指定一个客户自己的configurator。<strong>log4j.configuratorClass</strong>系统属
    性的值就是你客户自己的configurator的类名。你指定的客户configurator<em>必须</em>
    实施Configurator接口。</p>
    </li>
</ol>
<h2>配置示例</h2>
<h2>Tomcat下默认的初始化</h2>
<p>默认的log4j初始化在web-server环境中特别有用。在Tomcat 3.x and 4.x下，
你应该把<code>log4j.properties</code>放置在你的网络程序的<code>WEB-INF/classes</code>目录下面。
Log4j自己会去找到属性文件并初始化。这样做又简单又有效。</p>
<p>你可以选择在Tomcat启动之前设置系统属性<strong>log4j.configuration</strong> 。对于
Tomcat 3.x ，<code><code>TOMCAT_OPTS</code></code> 环境变量被用来设置命令行选项。对于
Tomcat 4.0，使用<code>CATALINA_OPTS</code>环境变量而不是TOMCAT_OPTS 。</p>
<p>
<strong>例子</strong>
</p>
<p>Unix shell 命令</p>
<pre>export TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"</pre>
告诉log4j 使用文件<code>foobar.txt</code> 作为默认的配置文件。这个文件应该被放置在你
的网络应用程序的<code>WEB-INF/classes</code>目录下面。文件将通过
<a name="x">PropertyConfigurator</a>被读取。每个网络应用程序使用不同的默认配置文件，
因为每个文件都是和每个网络应用程序相关的。
<p><strong>例子</strong></p>
<p>Unix shell 命令</p>
<pre>   export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"<br></pre>
告诉log4j输出log4j-内部排错信息，并使用文件<code>foobar<code>.xml</code></code> 作为默认的配置文
件。这个文件应该被放置在你的网络应用程序的<code>WEB-INF/classes</code>目录下面。
因为文件以.xml扩展符结尾，将使用<a name="x">DOMConfigurator</a>来读取。每个网络应
用程序使用不同的默认配置文件，因为每个文件都是和每个网络应用程序相关
的。
<p><strong>例子</strong></p>
<p>Windows shell 命令</p>
<pre>   set TOMCAT_OPTS=-Dlog4j.configuration=foobar.lcf -Dlog4j.configuratorClass=com.foo.BarConfigurator<br></pre>
告诉log4j使用文件<code>foobar.lcf</code> 作为默认的配置文件。这个文件应该被放置在你
的网络应用程序的<code>WEB-INF/classes</code> 目录下面。根据<strong>log4j.configuratorClass</strong>
系统属性的定义 ，文件将通过将使用客户自己的configurator——
<code>com.foo.BarConfigurator</code>被读取。每个
网络应用程序使用不同的默认配置文件，因为每个文件都是和一个网络应用程
序相关的。
<p><strong>例子</strong></p>
<p>Windows shell 命令</p>
<pre>   set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf</pre>
告诉log4j使用文件c:\foobar.lcf 作为默认的配置文件。这个配置文件完全由
URL <code>file:/c:/foobar.lcf</code>指定。因此，这个相同的配置文件将被所有网络应用程
序使用。 &nbsp;<code>c:\foobar.lcf</code>
<p>不同的网络应用程序通过它们各自的classloaders装载log4j的类。因此，每个
log4j环境的image会独自地，没有任何相互协调地行动。例如，在多个网络应
用程序的配置中，<code>FileAppenders</code>若定义得完全相同，它们就会编写相同的文
件。这样的结果就不那么令人满意。你必须保证不同的网络应用程序的log4j配
置不使用相同的系统资源。</p>
<p><strong>初始化servlet</strong></p>
<p>还可以使用一个特别的servlet来进行log4j初始化。这里就是个示例：</p>
<p>&nbsp;</p>
<table bgcolor="#cccccc">
    <tbody>
        <tr>
            <td>
            <pre>package com.foo;<br><br>import org.apache.log4j.PropertyConfigurator;<br>import javax.servlet.http.HttpServlet;<br>import javax.servlet.http.HttpServletRequest;<br>import javax.servlet.http.HttpServletResponse;<br>import java.io.PrintWriter;<br>import java.io.IOException;<br><br>public class Log4jInit extends HttpServlet {<br><br>  public<br>  void <strong>init()</strong> {<br>    String prefix =  getServletContext().getRealPath("/");<br>    String file = getInitParameter("log4j-init-file");<br>    // if the log4j-init-file is not set, then no point in trying<br>    if(file != null) {<br>      PropertyConfigurator.configure(prefix+file);<br>    }<br>  }<br><br>  public<br>  void doGet(HttpServletRequest req, HttpServletResponse res) {<br>  }<br>}<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>在web.xml文件里为你的网络应用程序定义下面的servlet。</p>
<p>&nbsp;</p>
<table bgcolor="#ccaaaa">
    <tbody>
        <tr>
            <td>
            <pre>  &lt;servlet&gt;<br>    &lt;servlet-name&gt;log4j-init&lt;/servlet-name&gt;<br>    &lt;servlet-class&gt;com.foo.Log4jInit&lt;/servlet-class&gt;<br><br>    &lt;init-param&gt;<br>      &lt;param-name&gt;log4j-init-file&lt;/param-name&gt;<br>      &lt;param-value&gt;WEB-INF/classes/log4j.lcf&lt;/param-value&gt;<br>    &lt;/init-param&gt;<br><br><strong>&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;</strong><br>  &lt;/servlet&gt;<br></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>编写一个initialization servlet 是最灵活的方式来初始化log4j。不受任何限制，
你可以在这个servlet的<code>init()</code>方法里放入任何代码。</p>
<h2> Nested Diagnostic Contexts</h2>
<p>实际情况下的大多数系统都需要同时处理多个客户端问题。在这种系统的典型
的多线程实施中，通常是不同的线程去分别处理不同的客户需求。Logging特
别适合于复杂的程序跟踪和排错。一个通常的处理办法是通过给每个客户产生
一个新的分离开的logger来达到把不同的客户的日志输出信息区分开来。但这
促进了loggers的增殖，加大了logging的管理负担。</p>
<p>一个更简洁的技术是独特地标记来自于同一个客户的每一个日志请求。Neil
Harrison 在他的书中"Patterns for Logging Diagnostic Messages,"  in
Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle,
and F. Buschmann (Addison-Wesley, 1997) 对这个方法进行了描述。 &nbsp;<em>Pattern
Languages of Program Design 3</em></p>
<p>要独特地标记每个日志请求，用户把上下文信息送入NDC，NDC是
<em>Nested Diagnostic Context</em>的缩写。NDC类展示如下。</p>
<pre>  public class NDC {<br>    // Used when printing the diagnostic<br>    public <strong>static</strong> String get();<br><br>    // Remove the top of the context from the NDC.<br>    public <strong>static</strong> String pop();<br><br>    // Add diagnostic context for the current thread.<br>    public <strong>static</strong> void push(String message);<br><br>    // Remove the diagnostic context for this thread.<br>    public <strong>static</strong> void remove();<br>  }<br></pre>
<p>NDC类是作为一个保存线程上下文的<em>stack</em>来独个线程（per thread） 管理
的。注意，<code>org.apache.log4j.NDC</code>类中所有的方法都是静态的。假设NDC打印
功能被打开，每一次若有日志请求，相应的log4j组件就把这个当前线程的<em>整个</em>
NDC stack包括在日志输出中打印出来。这样做不需要用户干预，用户只需要
在代码中明确指定的几点通过<code>push</code>和<code>pop</code>方法将正确的信息放到NDC中就行
了。相反，per-client logger方法需要在代码中作很多更改。</p>
<p>为了说明这一点，我们举个有关一个servlet把信息内容发送到多个客户的例
子。这个Servlet程序在开始接到客户端的请求，执行其它代码之前，首先创建
一个NDC。该上下文信息可能是客户端的主机名，以及其他请求中固有的信
息，通常是包含在cookies中的信息。因此即便这个Servlet程序可能同时要服
务于多个客户，由相同的代码启动的这些logs，比如属于同一个logger，它们
仍然能够被区分开来，因为不同的客户端请求具有不同的NDC stack。这与在
客户请求期间把一个实例化的logger传递给所有要被执行的代码的复杂性形成
了反差。</p>
<p>然而，一些复杂的应用程序，比如虚拟网络服务器，必须依据虚拟主机的上下
文语言环境，以及发布请求的软体组件来作不同的log。最近的log4j发行版支
持多阶层树。这一功能的加强允许每个虚拟主机拥有它自己的logger阶层版
本。</p>
<h2>性能</h2>
<p>一个经常提出的争议就是logging的运算开销。这种关注是有道理的，因为即便
是一个中等大小的应用程序至少也会产生几千个log输出。许多工作都花费在
测量和改进logging性能上。Log4j声明它是快速和灵活的：速度第一，灵活性
第二。</p>
<p>用户需要清楚地了解下面这些与性能相关的问题：</p>
<ol>
    <li><strong>Logging performance when logging is turned off.</strong>
    <p>当logging被完全关闭或只是set of levels被关闭，日志请求的开销是方法的调
    用和整数的比较。在一个233 MHz Pentium II机器上，这种开销通常在5 to 50
    毫微秒范围内。 &nbsp;<a href="http://log4j.jaxwiki.org/log4j/docs/api/org/apache/log4j/Hierarchy.html">set
    of levels</a></p>
    <p>不过，方法的调用包含有参数的建造上的&#8220;隐闭&#8221;开销。</p>
    <p>例如下面的logger <code>cat</code>程序段中：</p>
    <pre>     logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));<br></pre>
    不管message被日志记录与否，构造message参数的开销还是有的，比如说，
    把整数i 和数组<code>entry[i]</code>转化为String，连接中间字串。参数构造的这种开销可能
    很高，它依赖于所介入的参数数量有多少。
    <p>为了避免这种参数构造开销，把以上的代码段改写为：</p>
    <pre>      if(logger.isDebugEnabled() {<br>        logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));<br>      }<br></pre>
    <p>如果排错功能不被使用，就不会有参数构造上的开销。但是，另一方面，如果
    logger的排错功能被起用，就会有俩倍的开销用于评估logger是否被起用：一
    次是判断<code><code>debug</code>Enabled</code>，一次是判断debug是否被启用。但这不是极重的负
    担，因为评估logger的时间只有整个log语句执行时间的1%</p>
    <p>在log4j中，把日志请求作为Logger类的实例。Logger是类而不是接口，这主
    要是为了减少程序调用的开销，但牺牲了接口所能带来的灵活性。</p>
    <p>有些用户使用预处理或compile-time技术来编译所有log语句。这样logging方面
    的性能是很好。但是，因为resulting application binary没有包含任何log语
    句，你不能对这个二进制程序起用logging。在我看来，这是为了小的性能增加
    而付出大的代价。</p>
    </li>
    <li><strong>The performance of deciding whether to log or not to log when
    logging is turned on.</strong>
    <p>本质上影响性能的因素是logger的层次关系。当logging功能被打开时，log4j仍
    然需要把log请求的级别去与request logger的级别作比较。不过，有些loggers
    并没有指派的优先级别，但它可以从它的上一层logger那里继承优先级别。因
    此在继承优先级之前，logger可能需要搜索它的ancestors。</p>
    <p>Log4j在这方面做了很大的努力，以便使这种阶层的优先级别搜寻（hierarchy
    walk ）尽可能的快速。例如，子代loggers仅仅只和它们现有的ancestors链
    接。在前面的<code>BasicConfigurator</code>示例中，叫做<code><code>com</code>.foo.Bar</code>的logger 直接与
    root logger链接，绕过了不存在的com或<code>com.foo</code> loggers。这极大地提高了优
    先级别搜寻的速度。</p>
    <p>阶层的优先级搜寻（walking the hierarchy ）的开销在于它比logging完全关闭
    时要慢三倍。</p>
    </li>
    <li><strong>Actually outputting log messages</strong>
    <p>这里讲的是log输出的格式化和把log信息发送到目标所在地的开销。Log4j在这
    方面也下了大力气让格式化能尽快执行。对appenders也是一样。通常情况
    下，格式化语句的开销可能是100到300微秒的处理时间。确切数字请参看
    <a name="x">org.apache.log4.performance.Logging</a> 。</p>
    </li>
</ol>
<p>尽管log4j具有许多功能特性，但速度是第一设计目标。为了提高性能，一些
log4j的部件曾经被重写过许多次。即使这样，log4j的贡献者们不断提出新的优
化办法。你应该很惊喜地发现当以<a name="x">SimpleLayout</a>来配置时，性能测试显示使用
log4j日志和使用<code>System.out.println</code>日志同样快。</p>
<h2>结论</h2>
<p>Log4j是用Java编写的一个非常流行的logging开发包。它的一个显著特性之一
是在loggers里运用了继承的概念。使用这种logger的层次关系，就可能准确地
控制每一个log语句的输出。这样减少了log信息的输出量并降低了logging的开
销。</p>
<p>Log4j API的优点之一是它的可管理性。一旦log语句被插入到代码中，他们就
能被配置文件控制而无需重新编译源代码。Log信息的输出能够有选择地被起
用或关闭，用户能够按照自己选择的格式将这些log信息输出到许多不同的输
出设备中。Log4j软件包的设计是在代码中保留log语句的同时不造成很大的性
能损失。</p>
<h2>感谢</h2>
Many thanks to N. Asokan for reviewing the article. He is also one of
the originators of the logger concept. I am indebted to Nelson Minar
for encouraging me to write this article. He has also made many useful
suggestions and corrections to this article. Log4j is the result of a
collective effort. My special thanks go to all the authors who have
contributed to the project.  Without exception, the best features in
the package have all originated in the user community.  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94824.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:34 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94824.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于事件的NIO多线程服务器</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94823.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:31:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94823.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94823.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94823.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94823.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94823.html</trackback:ping><description><![CDATA[		<blockquote>JDK1.4提供的无阻塞I/O（NIO）有效解决了多线程服务器存在的线程开销问题，但在使用上略显得复杂一些。许多基于
NIO的多线程服务器程序往往直接基于选择器（Selector）的Reactor模式实现。这种简单的事件机制对于较复杂的服务器应用，显然缺乏扩展性
和可维护性，而且缺乏直观清晰的结构层次。本文将通过一个基于事件回调的NIO多线程服务器的设计，试图提供一个简洁、直观、易于扩展的NIO多线程服务
器模型。</blockquote>
<p>JDK1.4的NIO有效解决了原有流式IO存在的线程开销的问题，在NIO中使用多线程，主要目的已不是为了应对每个客户端请求而分配独立的服务线程，而是通过多线程充分使用用多个CPU的处理能力和处理中的等待时间，达到提高服务能力的目的。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">多线程的引入，容易为本来就略显复杂的NIO代码进一步降低可读性和可维护性。引入良好的设计模型，将不仅带来高性能、高可靠的代码，也将带来一个惬意的开发过程。 </p>
<p>
<a name="1">
<span class="atitle2">线程模型</span>
</a>
<br>NIO
的选择器采用了多路复用（Multiplexing）技术，可在一个选择器上处理多个套接字，通过获取读写通道来进行IO操作。由于网络带宽等原因，在通
道的读、写操作中是容易出现等待的，所以在读、写操作中引入多线程，对性能提高明显，而且可以提高客户端的感知服务质量。所以本文的模型将主要通过使用
读、写线程池来提高与客户端的数据交换能力。</p>
<p>如下图所示，服务端接受客户端请求后，控制线程将该请求的读通道交给读线程池，由读线程池分配
线程完成对客户端数据的读取操作；当读线程完成读操作后，将数据返回控制线程，进行服务端的业务处理；完成业务处理后，将需回应给客户端的数据和写通道提
交给写线程池，由写线程完成向客户端发送回应数据的操作。</p>
<p>
<a name="N10054">
<strong>
<font color="#000000">（NIO 多线程服务器模型）</font>
</strong>
</a>
<br>&nbsp;<img src="http://www.zhanglihai.com/data/uploadfiles//200505/25/mcq5qbmg_1117011474846.gif"></p>
<p>同时整个服务端的流程处理，建立于事件机制上。在 <font color="#000000"><strong xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">[</strong>接受连接－＞读－＞业务处理－＞写 <strong xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">－</strong>＞关闭连接 <strong xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">]</strong>这个过程中，触发器将触发相应事件，由事件处理器对相应事件分别响应，完成服务器端的业务处理。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">下面我们就来详细看一下这个模型的各个组成部分。 </font></p>
<p>
<a name="2">
<span class="atitle2">相关事件定义 在这个模型中，我们定义了一些基本的事件：</span>
</a>
<br>（1）
onAccept：当服务端收到客户端连接请求时，触发该事件。通过该事件我们可以知道有新的客户端呼入。该事件可用来控制服务端的负载。例如，服务器可
设定同时只为一定数量客户端提供服务，当同时请求数超出数量时，可在响应该事件时直接抛出异常，以拒绝新的连接。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">（2）onAccepted：当客户端请求被服务器接受后触发该事件。该事件表明一个新的客户端与服务器正式建立连接。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">（3）
onRead：当客户端发来数据，并已被服务器控制线程正确读取时，触发该事件。该事件通知各事件处理器可以对客户端发来的数据进行实际处理了。需要注意
的是，在本模型中，客户端的数据读取是由控制线程交由读线程完成的，事件处理器不需要在该事件中进行专门的读操作，而只需将控制线程传来的数据进行直接处
理即可。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">（4）onWrite：当客户端可以开始接受服务端发送数据时触发该事件，通过该事件，我们可以向客户端发送回应数据。在本模型中，事件处理器只需要在该事件中设置 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">（5）onClosed：当客户端与服务器断开连接时触发该事件。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">（6）onError：当客户端与服务器从连接开始到最后断开连接期间发生错误时触发该事件。通过该事件我们可以知道有什么错误发生。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"><br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/"></p>
<p>
<a name="3">
<span class="atitle2">事件回调机制的实现</span>
</a>
<br>在这个模型中，事件采用广播方式，也就是所有在册的事件处理器都能获得事件通知。这样可以将不同性质的业务处理，分别用不同的处理器实现，使每个处理器的业务功能尽可能单一。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">如下图：整个事件模型由监听器、事件适配器、事件触发器、事件处理器组成。 </p>
<p>
<a name="N1009E">
<strong>
<font color="#000000">（事件模型）</font>
</strong>
</a>
<br>&nbsp;<img src="http://www.zhanglihai.com/data/uploadfiles//200505/25/y813da8k_1117011577868.gif"></p>
<ol xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
    <li>监听器（Serverlistener）：这是一个事件接口，定义需监听的服务器事件，如果您需要定义更多的事件，可在这里进行扩展。 <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code><br> public interface Serverlistener {<br>   public void onError(String error);<br><br>   public void onAccept() throws Exception;<br><br>   public void onAccepted(Request request) throws Exception;<br><br>   public void onRead(Request request) throws Exception;<br><br>   public void onWrite(Request request, Response response) throws Exception;<br><br>   public void onClosed(Request request) throws Exception;<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>事件适配器（EventAdapter）：对Serverlistener接口实现一个适配器(EventAdapter)，这样的好处是最终的事件处理器可以只处理所关心的事件。 <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code><br>public abstract class EventAdapter implements Serverlistener {<br>    public EventAdapter() {<br>    }<br>    public void onError(String error) {}<br>    public void onAccept() throws Exception {}<br>    public void onAccepted(Request request)  throws Exception {}<br>    public void onRead(Request request)  throws Exception {}<br>    public void onWrite(Request request, Response response)  throws Exception {}<br>    public void onClosed(Request request)  throws Exception {}<br>}   <br></code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>事件触发器（Notifier）：用于在适当的时候通过触发服务器事件，通知在册的事件处理器对事件做出响应。触发器以Singleton模式实现，统一控制整个服务器端的事件，避免造成混乱。 <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class Notifier {<br>    private static Arraylist listeners = null;<br>    private static Notifier instance = null;<br><br>    private Notifier() {<br>        listeners = new Arraylist();<br>    }<br><br>    /**<br>     * 获取事件触发器<br>     * @return 返回事件触发器<br>     */<br>    public static synchronized Notifier getNotifier() {<br>        if (instance == null) {<br>            instance = new Notifier();<br>            return instance;<br>        }<br>        else return instance;<br>    }<br><br>    /**<br>     * 添加事件监听器<br>     * @param l 监听器<br>     */<br>    public void addlistener(Serverlistener l) {<br>        synchronized (listeners) {<br>            if (!listeners.contains(l))<br>                listeners.add(l);<br>        }<br>    }<br><br>    public void fireOnAccept() throws Exception {<br>        for (int i = listeners.size() - 1; i &gt;= 0; i--)<br>            ( (Serverlistener) listeners.get(i)).onAccept();<br>    }<br><br>    ....// other fire method<br>}<br></code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>事件处理器（Handler）：继承事件适配器，对感兴趣的事件进行响应处理，实现业务处理。以下是一个简单的事件处理器实现，它响应onRead事件，在终端打印出从客户端读取的数据。 <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code><br>public class ServerHandler extends EventAdapter {<br>    public ServerHandler() {<br>    }<br><br>    public void onRead(Request request) throws Exception {<br>        System.out.println("Received: " + new String(data));<br>    }<br>}<br></code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>事件处理器的注册。为了能让事件处理器获得服务线程的事件通知，事件处理器需在触发器中注册。 <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code><br>ServerHandler handler = new ServerHandler();<br>Notifier.addlistener(handler);<br></code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
</ol>
<p>
<a name="4">
<span class="atitle2">实现NIO多线程服务器</span>
</a>
<br>NIO多线程服务器主要由主控服务线程、读线程和写线程组成。</p>
<p>
<a name="N100EC">
<strong>
<font color="#000000">（线程模型）</font>
</strong>
</a>
<br>&nbsp;<img src="http://www.zhanglihai.com/data/uploadfiles//200505/25/41880759_1117011605815.gif"></p>
<ol xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
    <li>主控服务线程（Server）：主控线程将创建读、写线程池，实现监听、接受客户端请求，同时将读、写通道提交由相应的读线程（Reader）和写服务线程(Writer），由读写线程分别完成对客户端数据的读取和对客户端的回应操作。
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class Server implements Runnable {<br>    ....<br><br>    private static int MAX_THREADS = 4;<br>    public Server(int port) throws Exception {<br>        ....<br><br>        // 创建无阻塞网络套接<br>        selector = Selector.open();<br>        sschannel = ServerSocketChannel.open();<br>        sschannel.configureBlocking(false);<br>        address = new InetSocketAddress(port);<br>        ServerSocket ss = sschannel.socket();<br>        ss.bind(address);<br>        sschannel.register(selector, SelectionKey.OP_ACCEPT);<br>    }<br><br>    public void run() {<br>        System.out.println("Server started ...");<br>        System.out.println("Server listening on port: " + port);<br>        // 监听<br>        while (true) {<br>            try {<br>                int num = 0;<br>                num = selector.select();<br><br>                if (num &gt; 0) {<br>                    Set selectedKeys = selector.selectedKeys();<br>                    Iterator it = selectedKeys.iterator();<br>                    while (it.hasNext()) {<br>                        SelectionKey key = (SelectionKey) it.next();<br>                        it.remove();<br>                        // 处理IO事件<br>                        if ( (key.readyOps() &amp; SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {<br>                           // Accept the new connection<br>                           ServerSocketChannel ssc = (ServerSocketChannel) key.channel();<br>                           notifier.fireOnAccept();<br><br>                           SocketChannel sc = ssc.accept();<br>                           sc.configureBlocking(false);<br><br>                           // 触发接受连接事件<br>                           Request request = new Request(sc);<br>                           notifier.fireOnAccepted(request);<br><br>                           // 注册读操作,以进行下一步的读操作<br>                           sc.register(selector,  SelectionKey.OP_READ, request);<br>                       }<br>                       else if ( (key.readyOps() &amp; SelectionKey.OP_READ) == SelectionKey.OP_READ ) {<br>                           Reader.processRequest(key);  // 提交读服务线程读取客户端数据<br>                           key.cancel();<br>                       }<br>                       else if ( (key.readyOps() &amp; SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE ) {<br>                           Writer.processRequest(key);  // 提交写服务线程向客户端发送回应数据<br>                           key.cancel();<br>                       }<br>                    }<br>                }<br>                else {<br>                    addRegister();  // 在Selector中注册新的写通道<br>                }<br>            }<br>            catch (Exception e) {<br>                notifier.fireOnError("Error occured in Server: " + e.getMessage());<br>                continue;<br>            }<br>        }<br>    }<br>    ....<br>}        </code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>读线程（Reader）：使用线程池技术，通过多个线程读取客户端数据，以充分利用网络数据传输的时间，提高读取效率。
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class Reader extends Thread {<br>    public void run() {<br>        while (true) {<br>            try {<br>                SelectionKey key;<br>                synchronized (pool) {<br>                    while (pool.isEmpty()) {<br>                        pool.wait();<br>                    }<br>                    key = (SelectionKey) pool.remove(0);<br>                }<br>                // 读取客户端数据，并触发onRead事件<br>                read(key);  <br>            }<br>            catch (Exception e) {<br>                continue;<br>            }<br>        }<br>    }<br>    ....<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>写线程（Writer）：和读操作一样，使用线程池，负责将服务器端的数据发送回客户端。
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code><br>public final class Writer extends Thread {<br>    public void run() {<br>        while (true) {<br>            try {<br>                SelectionKey key;<br>                synchronized (pool) {<br>                    while (pool.isEmpty()) {<br>                        pool.wait();<br>                    }<br>                    key = (SelectionKey) pool.remove(0);<br>                }<br><br>                // 向客户端发送数据，然后关闭连接，并分别触发onWrite，onClosed事件<br>                write(key); <br>            }<br>            catch (Exception e) {<br>                continue;<br>            }<br>        }<br>    }<br>    ....<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
</ol>
<p>
<a name="5">
<span class="atitle2">具体应用</span>
</a>
<br>NIO多线程模型的实现告一段落，现在我们可以暂且将NIO的各个API和烦琐的调用方法抛于脑后，专心于我们的实际应用中。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">我们用一个简单的TimeServer（时间查询服务器）来看看该模型能带来多么简洁的开发方式。 <br xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">在
这个TimeServer中，将提供两种语言（中文、英文）的时间查询服务。我们将读取客户端的查询命令（GB/EN），并回应相应语言格式的当前时间。
在应答客户的请求的同时，服务器将进行日志记录。做为示例，对日志记录，我们只是简单地将客户端的访问时间和IP地址输出到服务器的终端上。 </p>
<ol xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dw="http://www.ibm.com/developerworks/">
    <li>实现时间查询服务的事件处理器（TimeHandler）： <br>
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class TimeHandler extends EventAdapter {<br>    public TimeHandler() {<br>    }<br><br>    public void onWrite(Request request, Response response) throws Exception {<br>        String command = new String(request.getDataInput());<br>        String time = null;<br>        Date date = new Date();<br><br>        // 判断查询命令<br>        if (command.equals("GB")) {<br>            // 中文格式<br>            DateFormat cnDate = DateFormat.getDateTimeInstance(DateFormat.FulL,<br>                DateFormat.FulL, Locale.CHINA);<br>            time = cnDate.format(date);<br>        }<br>        else {<br>            // 英文格式<br>            DateFormat enDate = DateFormat.getDateTimeInstance(DateFormat.FulL,<br>                DateFormat.FulL, Locale.US);<br>            time = enDate.format(date);<br>        }<br><br>        response.send(time.getBytes());<br>    }<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br></li>
    <li>实现日志记录服务的事件处理器（LogHandler）：
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class LogHandler extends EventAdapter {<br>    public LogHandler() {<br>    }<br><br>    public void onClosed(Request request) throws Exception {<br>        String log = new Date().toString() + " from " + request.getAddress().toString();<br>        System.out.println(log);<br>    }<br><br>    public void onError(String error) {<br>        System.out.println("Error: " + error);<br>    }<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
    <li>启动程序：
    <table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="90%">
        <tbody>
            <tr>
                <td>
                <pre><code>public class Start {<br><br>    public static void main(String[] args) {<br>        try {<br>            LogHandler loger = new LogHandler();<br>            TimeHandler timer = new TimeHandler();<br>            Notifier notifier = Notifier.getNotifier();<br>            notifier.addlistener(loger);<br>            notifier.addlistener(timer);<br><br>            System.out.println("Server starting ...");<br>            Server server = new Server(5100);<br>            Thread tServer = new Thread(server);<br>            tServer.start();<br>        }<br>        catch (Exception e) {<br>            System.out.println("Server error: " + e.getMessage());<br>            System.exit(-1);<br>        }<br>    }<br>}</code></pre>
                </td>
            </tr>
        </tbody>
    </table>
    </li>
</ol>
<a name="6">
<span class="atitle2">小结</span>
</a>
<br>通过例子我们可以看到，基于事件回调的NIO多线程服务器模型，提供了清晰直观的实现方式，可让开发者从NIO及多线程的技术细节中摆脱出来，集中精力关注具体的业务实现。&nbsp;  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94823.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:31 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94823.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 异步消息处理</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94800.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:10:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94800.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94800.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94800.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94800.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94800.html</trackback:ping><description><![CDATA[一.&nbsp;&nbsp;&nbsp;&nbsp;它要能适应不同类型的请求:<br>本节用&nbsp;makeString来说明要求有返回值的请求.用displayString来说明不需要返回值的请求.<br>二.&nbsp;&nbsp;&nbsp;&nbsp;要能同时并发处理多个请求,并能按一定机制调度:<br>本节将用一个队列来存放请求,所以只能按FIFO机制调度,你可以改用LinkedList,就可以简单实现一个优先级(优先级高的addFirst,低的addLast).<br>三.&nbsp;&nbsp;&nbsp;&nbsp;有能力将调用的边界从线程扩展到机器间(RMI)<br>四.&nbsp;&nbsp;&nbsp;&nbsp;分离过度耦合,如分离调用句柄(取货凭证)和真实数据的实现.分离调用和执行的过程,可以尽快地将调返回.<br><br>现在看具体的实现:<br>public&nbsp;interface&nbsp;Axman&nbsp;{<br>&nbsp;&nbsp;Result&nbsp;resultTest(int&nbsp;count,char&nbsp;c);<br>&nbsp;&nbsp;void&nbsp;noResultTest(String&nbsp;str);<br>}<br>这个接口有两个方法要实现,就是有返回值的调用resultTest和不需要返回值的调用<br>noResultTest,
我们把这个接口用一个代理类来实现,目的是将方法调用转化为对象,这样就可以将多个请求(多个方法调)放到一个容器中缓存起来,然后统一处理,因为
Java不支持方法指针,所以把方法调用转换为对象,然后在这个对象上统一执行它们的方法,不仅可以做到异步处理,而且可以将代表方法调用的请求对象序列
化后通过网络传递到另一个机器上执行(RMI).这也是Java回调机制最有力的实现.<br>&nbsp;&nbsp;&nbsp;&nbsp;一个简单的例子.<br>&nbsp;&nbsp;&nbsp;&nbsp;如果&nbsp;1:&nbsp;做A<br>&nbsp;&nbsp;&nbsp;&nbsp;如果&nbsp;2:&nbsp;做B<br>如果&nbsp;3:&nbsp;做C<br>如果有1000个情况,你不至于用1000个case吧?以后再增加呢?<br>所以如果C/C++程序员,会这样实现:&nbsp;(c和c++定义结构不同)<br><br>type&nbsp;define&nbsp;struct&nbsp;MyStruct{<br>int&nbsp;mark;<br>(*fn)&nbsp;();<br>}&nbsp;MyList;<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;然后你可以声明这个结构数据:<br>&nbsp;&nbsp;&nbsp;&nbsp;{1,A,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2,B<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3,C<br>}<br>做一个循环:<br>for(i=0;i&lt;length;i++)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;if(数据组[i].mark&nbsp;==&nbsp;传入的值)&nbsp;(数据组[i].*fn)();<br>}<br>简单说c/c++中将要被调用的涵数可以被保存起来,然后去访问,调用,而Java中,我们无法将一个方法保存,除了直接调用,所以将要调用的方法用子类来实现,然后把这些子类实例保存起来,然后在这些子类的实现上调用方法:<br>interface&nbsp;My{<br>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;test();<br>}<br><br>class&nbsp;A&nbsp;implements&nbsp;My{<br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;test(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&#8220;A&#8221;):<br>}<br>}<br>class&nbsp;B&nbsp;implements&nbsp;My{<br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;test(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&#8220;B&#8221;):<br>}<br>}<br><br>class&nbsp;C&nbsp;implements&nbsp;My{<br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;test(){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&#8220;C&#8221;):<br>}<br>}<br><br>class&nbsp;MyStruct&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;mark;<br>&nbsp;&nbsp;&nbsp;&nbsp;My&nbsp;m;<br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;MyStruct(int&nbsp;mark,My&nbsp;m){this.mark&nbsp;=&nbsp;amrk;this.m&nbsp;=&nbsp;m}<br>}<br>数组:<br>{&nbsp;new&nbsp;MyStruct(1,new&nbsp;A()),new&nbsp;MyStruct(2,new&nbsp;B()),new&nbsp;MyStruct(3,new&nbsp;C())}<br>for(xxxxxxxxx)&nbsp;if(参数&nbsp;==数组[i].mark)&nbsp;数组[i].m.test();<br><br>这样把要调用的方法转换为对象的保程不仅仅是可以对要调用的方法进行调度,而且可以把对象序列化后在另一台机器上执行,这样就把调用边界从线程扩展到了机器.<br><br>回到我们的例子:<br>class&nbsp;Proxy&nbsp;implements&nbsp;Axman{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;Scheduler&nbsp;scheduler;<br>&nbsp;&nbsp;private&nbsp;final&nbsp;Servant&nbsp;servant;<br><br>&nbsp;&nbsp;public&nbsp;Proxy(Scheduler&nbsp;scheduler,Servant&nbsp;servant){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.scheduler&nbsp;=&nbsp;scheduler;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.servant&nbsp;=&nbsp;servant;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;public&nbsp;Result&nbsp;resultTest(int&nbsp;count,char&nbsp;c){<br>&nbsp;&nbsp;&nbsp;&nbsp;FutureResult&nbsp;futrue&nbsp;=&nbsp;new&nbsp;FutureResult();<br>&nbsp;&nbsp;&nbsp;&nbsp;this.scheduler.invoke(new&nbsp;ResultRequest(servant,futrue,count,c));<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;futrue;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;noResultTest(String&nbsp;str){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.scheduler.invoke(new&nbsp;NoResultRequest(this.servant,str));<br>&nbsp;&nbsp;}<br>}<br><br>其中scheduler是管理对调用的调度,&nbsp;servant是真正的对方法的执行:<br><br>Servant就是去真实地实现方法:<br><br>class&nbsp;Servant&nbsp;implements&nbsp;Axman{<br>&nbsp;&nbsp;public&nbsp;Result&nbsp;resultTest(int&nbsp;count,char&nbsp;c){<br>&nbsp;&nbsp;&nbsp;&nbsp;char[]&nbsp;buf&nbsp;=&nbsp;new&nbsp;char[count];<br>&nbsp;&nbsp;&nbsp;&nbsp;for(int&nbsp;i&nbsp;=&nbsp;0;i&nbsp;&lt;&nbsp;count;i++){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf[i]&nbsp;=&nbsp;c;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(100);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}catch(Throwable&nbsp;t){}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;RealResult(new&nbsp;String(buf));<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;noResultTest(String&nbsp;str){<br>&nbsp;&nbsp;&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("displayString&nbsp;:"&nbsp;+&nbsp;str);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(10);<br>&nbsp;&nbsp;&nbsp;&nbsp;}catch(Throwable&nbsp;t){}<br>&nbsp;&nbsp;}<br>}<br>在scheduler
将方法的调用(invkoe)和执行(execute)进行了分离,调用就是开始&#8221;注册&#8221;方法到要执行的容器中,这样就可以立即返回出来.真正执行多久就
是execute的事了,就象一个人点燃爆竹的引信就跑了,至于那个爆竹什么时候爆炸就不是他能控制的了.<br>public&nbsp;class&nbsp;Scheduler&nbsp;extends&nbsp;Thread&nbsp;{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;ActivationQueue&nbsp;queue;<br>&nbsp;&nbsp;public&nbsp;Scheduler(ActivationQueue&nbsp;queue){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.queue&nbsp;=&nbsp;queue;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;invoke(MethodRequest&nbsp;request){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.queue.putRequest(request);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;run(){<br>&nbsp;&nbsp;&nbsp;&nbsp;while(true){<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//如果队列中有请求线程,测开始执行请求<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MethodRequest&nbsp;request&nbsp;=&nbsp;this.queue.takeRequest();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.execute();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br>在scheduler中只用一个队列来保存代表方法和请求对象,实行简单的FIFO调用,你要实更复杂的调度就要在这里重新实现:<br>class&nbsp;ActivationQueue{<br>&nbsp;&nbsp;private&nbsp;static&nbsp;final&nbsp;int&nbsp;MAX_METHOD_REQUEST&nbsp;=&nbsp;100;<br>&nbsp;&nbsp;private&nbsp;final&nbsp;MethodRequest[]&nbsp;requestQueue;<br>&nbsp;&nbsp;private&nbsp;int&nbsp;tail;<br>&nbsp;&nbsp;private&nbsp;int&nbsp;head;<br>&nbsp;&nbsp;private&nbsp;int&nbsp;count;<br><br>&nbsp;&nbsp;public&nbsp;ActivationQueue(){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.requestQueue&nbsp;=&nbsp;new&nbsp;MethodRequest[MAX_METHOD_REQUEST];<br>&nbsp;&nbsp;&nbsp;&nbsp;this.head&nbsp;=&nbsp;this.count&nbsp;=&nbsp;this.tail&nbsp;=&nbsp;0;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;synchronized&nbsp;void&nbsp;putRequest(MethodRequest&nbsp;request){<br>&nbsp;&nbsp;&nbsp;&nbsp;while(this.count&nbsp;&gt;=&nbsp;this.requestQueue.length){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.wait();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch&nbsp;(Throwable&nbsp;t)&nbsp;{}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;this.requestQueue[this.tail]&nbsp;=&nbsp;request;<br>&nbsp;&nbsp;&nbsp;&nbsp;tail&nbsp;=&nbsp;(tail&nbsp;+&nbsp;1)%this.requestQueue.length;<br>&nbsp;&nbsp;&nbsp;&nbsp;count&nbsp;++&nbsp;;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.notifyAll();<br><br>&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;public&nbsp;synchronized&nbsp;MethodRequest&nbsp;takeRequest(){<br>&nbsp;&nbsp;&nbsp;&nbsp;while(this.count&nbsp;&lt;=&nbsp;0){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.wait();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch&nbsp;(Throwable&nbsp;t)&nbsp;{}<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;MethodRequest&nbsp;request&nbsp;=&nbsp;this.requestQueue[this.head];<br>&nbsp;&nbsp;&nbsp;&nbsp;this.head&nbsp;=&nbsp;(this.head&nbsp;+&nbsp;1)&nbsp;%&nbsp;this.requestQueue.length;<br>&nbsp;&nbsp;&nbsp;&nbsp;count&nbsp;--;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.notifyAll();<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;request;<br>&nbsp;&nbsp;}<br>}<br><br>为了将方法调用转化为对象,我们通过实现MethodRequest对象的execute方法来方法具体方法转换成具体对象:<br>abstract&nbsp;class&nbsp;MethodRequest{<br>&nbsp;&nbsp;protected&nbsp;final&nbsp;Servant&nbsp;servant;<br>&nbsp;&nbsp;protected&nbsp;final&nbsp;FutureResult&nbsp;future;<br><br>&nbsp;&nbsp;protected&nbsp;MethodRequest(Servant&nbsp;servant,FutureResult&nbsp;future){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.servant&nbsp;=&nbsp;servant;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.future&nbsp;=&nbsp;future;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;abstract&nbsp;void&nbsp;execute();<br>}<br><br>class&nbsp;ResultRequest&nbsp;extends&nbsp;MethodRequest{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;int&nbsp;count;<br>&nbsp;&nbsp;private&nbsp;final&nbsp;char&nbsp;c;<br>&nbsp;&nbsp;public&nbsp;ResultRequest(Servant&nbsp;servant,FutureResult&nbsp;future,int&nbsp;count,char&nbsp;c){<br>&nbsp;&nbsp;&nbsp;&nbsp;super(servant,future);<br>&nbsp;&nbsp;&nbsp;&nbsp;this.count&nbsp;=&nbsp;count;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.c&nbsp;=&nbsp;c;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;public&nbsp;void&nbsp;execute(){<br>&nbsp;&nbsp;&nbsp;&nbsp;Result&nbsp;result&nbsp;=&nbsp;servant.resultTest(this.count,this.c);<br>&nbsp;&nbsp;&nbsp;&nbsp;this.future.setResult(result);<br>&nbsp;&nbsp;}<br>}<br><br>class&nbsp;NoResultRequest&nbsp;extends&nbsp;MethodRequest{<br>&nbsp;&nbsp;private&nbsp;String&nbsp;str;<br>&nbsp;&nbsp;public&nbsp;NoResultRequest(Servant&nbsp;servant,String&nbsp;str){<br>&nbsp;&nbsp;&nbsp;&nbsp;super(servant,null);<br>&nbsp;&nbsp;&nbsp;&nbsp;this.str&nbsp;=&nbsp;str;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;execute(){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.servant.noResultTest(str);<br>&nbsp;&nbsp;}<br>}<br><br>而返回的数据我们也将真实数据的获取和取货凭证逻辑分离:<br>package&nbsp;com.axman.jasync;<br><br>public&nbsp;abstract&nbsp;class&nbsp;Result&nbsp;{<br>&nbsp;&nbsp;public&nbsp;abstract&nbsp;Object&nbsp;getResult();<br>}<br><br>class&nbsp;FutureResult&nbsp;extends&nbsp;Result{<br>&nbsp;&nbsp;private&nbsp;Result&nbsp;result;<br>&nbsp;&nbsp;private&nbsp;boolean&nbsp;completed;<br><br>&nbsp;&nbsp;public&nbsp;synchronized&nbsp;void&nbsp;setResult(Result&nbsp;result){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.result&nbsp;=&nbsp;result;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.completed&nbsp;=&nbsp;true;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.notifyAll();<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;synchronized&nbsp;Object&nbsp;getResult(){<br>&nbsp;&nbsp;&nbsp;&nbsp;while(!this.completed){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.wait();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}catch(Throwable&nbsp;t){}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;this.result.getResult();<br>&nbsp;&nbsp;}<br>}<br><br>class&nbsp;RealResult&nbsp;extends&nbsp;Result{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;Object&nbsp;result;<br><br>&nbsp;&nbsp;public&nbsp;RealResult(Object&nbsp;result){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.result&nbsp;=&nbsp;result;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;public&nbsp;Object&nbsp;getResult(){<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;this.result;<br>&nbsp;&nbsp;}<br>}<br>OK,现在这个异步消息处理器已经有了模型,这个异步处理器中有昭雪些对象参与呢?<br>&nbsp;&nbsp;&nbsp;&nbsp;Servant&nbsp;忠心做真实的事务<br>&nbsp;&nbsp;&nbsp;&nbsp;ActivationQueue将请求缓存起来以便调度<br>&nbsp;&nbsp;&nbsp;&nbsp;Scheduler对容器中的请求根据一定原则进行调度执行<br>&nbsp;&nbsp;&nbsp;&nbsp;Proxy将特定方法请求转换为特定对象<br>所有这些都是这个异步处理器的核心部件,虽然是核心部件,我们就要进行封装而不能随便让调用者来修改,所以我们用工厂模式(我KAO,我实在不想提模式但有时找不到其它词来表述)来产生处理器Axman对象:<br>package&nbsp;com.axman.jasync;<br><br>public&nbsp;class&nbsp;AxmanFactory&nbsp;{<br>&nbsp;&nbsp;public&nbsp;static&nbsp;Axman&nbsp;createAxman()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Servant&nbsp;s&nbsp;=&nbsp;new&nbsp;Servant();<br>&nbsp;&nbsp;&nbsp;&nbsp;ActivationQueue&nbsp;queue&nbsp;=&nbsp;new&nbsp;ActivationQueue();<br>&nbsp;&nbsp;&nbsp;&nbsp;Scheduler&nbsp;st&nbsp;=&nbsp;new&nbsp;Scheduler(queue);<br>&nbsp;&nbsp;&nbsp;&nbsp;Proxy&nbsp;p&nbsp;=&nbsp;new&nbsp;Proxy(st,s);<br>&nbsp;&nbsp;&nbsp;&nbsp;st.start();<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;p;<br>&nbsp;&nbsp;}<br>}<br>好了,我们现在用两个请求的产生者不停产生请求:<br>ResultInvokeThreadv&nbsp;发送有返回值的请求:<br>package&nbsp;com.axman.jasync;<br><br>public&nbsp;class&nbsp;ResultInvokeThread&nbsp;extends&nbsp;Thread{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;Axman&nbsp;ao;<br>&nbsp;&nbsp;private&nbsp;final&nbsp;char&nbsp;c;<br>&nbsp;&nbsp;public&nbsp;ResultInvokeThread(String&nbsp;name,Axman&nbsp;ao){<br>&nbsp;&nbsp;&nbsp;&nbsp;this.ao&nbsp;=&nbsp;ao;<br>&nbsp;&nbsp;&nbsp;&nbsp;this.c&nbsp;=&nbsp;name.charAt(0);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;run(){<br>&nbsp;&nbsp;&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(true){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Result&nbsp;result&nbsp;&nbsp;=&nbsp;this.ao.resultTest(i++,c);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(10);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;&nbsp;=&nbsp;(String)result.getResult();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(Thread.currentThread().getName()&nbsp;+&nbsp;"&nbsp;&nbsp;=&nbsp;"&nbsp;+&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;catch(Throwable&nbsp;t){}<br>&nbsp;&nbsp;}<br>}<br><br>NoResultInvokeThread发送无返回值的请求:<br>package&nbsp;com.axman.jasync;<br><br>public&nbsp;class&nbsp;NoResultInvokeThread&nbsp;extends&nbsp;Thread{<br>&nbsp;&nbsp;private&nbsp;final&nbsp;Axman&nbsp;ao;<br>&nbsp;&nbsp;public&nbsp;NoResultInvokeThread(String&nbsp;name,Axman&nbsp;ao){<br>&nbsp;&nbsp;&nbsp;&nbsp;super(name);<br>&nbsp;&nbsp;&nbsp;&nbsp;this.ao&nbsp;=&nbsp;ao;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;public&nbsp;void&nbsp;run(){<br>&nbsp;&nbsp;&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(true){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;s&nbsp;=&nbsp;Thread.currentThread().getName()&nbsp;+&nbsp;i++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ao.noResultTest(s);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(20);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;catch(Throwable&nbsp;t){}<br>&nbsp;&nbsp;}<br>}<br><br>对了,我们还需要一个什么东西来产生一个演示:<br>package&nbsp;com.axman.jasync;<br><br>public&nbsp;class&nbsp;Program&nbsp;{<br>&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Axman&nbsp;ao&nbsp;=&nbsp;AxmanFactory.createAxman();<br>&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;ResultInvokeThread("Axman",ao).start();<br>&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;ResultInvokeThread("Sager",ao).start();<br>&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;NoResultInvokeThread("Macke",ao).start();<br>&nbsp;&nbsp;}<br>}<br>看看结果吧.你可以把不同类型的请求不断地向处理器发送,处理器会不断地接收请求,放到队列中,并同时不断从队列中提出请求进行处理.  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94800.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:10 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94800.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Cindy User Guide</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94797.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94797.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94797.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94797.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94797.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94797.html</trackback:ping><description><![CDATA[<h2>Overview</h2>
<p>Cindy是一个Java异步I/O框架，提供了一个统一高效的模型，同时支持TCP、UDP以及Pipe，并能够方便的在异步和同步操作之间进行
切换。目前其实现是基于Java
NIO，并计划通过JNI来支持各操作系统上本身提供的异步I/O功能，应用可以方便的通过运行期属性来方便的切换到更为高效的实现上。</p>
<p>
<span style="font-weight: bold;">为什么不使用Java IO？</span>
</p>
<p>Java
IO包采用阻塞模型来处理网络操作。假设应用调用了read方法读取来自网络流上的数据，而数据尚未到达本机，则对read方法的调用将一直等到数据到达
并成功接收后才返回。由于IO包的所有操作会一直阻塞当前线程，为了不影响其他事务的处理，在一般情况下应用总是会用一个独立线程或线程池来处理这些
I/O操作。</p>
<p>Java
IO包阻塞模型的优点就是非常简单易用，如果配合上线程池，效率也非常不错，所以得到了广泛的应用。但这种简单模型也有其固有的缺点：扩展性不足。如果应
用只需要进行少量的网络操作，那么开启若干个单独的I/O线程无伤大雅；但是如果是实现一个服务端的应用，需要同时处理成千上万个网络连接，采用阻塞模型
的话就要同时开启上千个线程。虽然现在强劲的服务器能够负担起这么多线程，但系统花在线程调度上的时间也会远远多于用于处理网络操作上的时间。</p>
<p>
<span style="font-weight: bold;">为什么要使用Java NIO？</span>
</p>
<p>采用IO包的阻塞模型，如果数据量不大的话，则线程的大部分时间都会浪费在等待上。对于稀缺的服务器资源而言，这是一种极大的浪费。</p>
<p>在Java
1.4中引入的NIO包里，最引人注目的就是提供了非阻塞I/O的实现。和IO包提供的阻塞模型不同的是，对一个非阻塞的连接进行操作，如果此时相应的状
态还未就绪，则调用会立即返回，而不是等待状态就绪后才返回。假设应用调用了read方法读取来自网络流上的数据，而此刻数据尚未到达本机，则对read
方法的调用将立即返回，并通知应用目前只能读到0个字节。应用可以根据自身的策略来进行处理，比如读取其他网络连接的数据等等，这就使得一个线程管理多个
连接成为可能。</p>
<p>NIO包还提供了Selector机制，将一个非阻塞连接注册在Selector上，应用就不需去轮询该连接当前是否可以读取或写入数据，在相应状
态就绪后Selector会通知该连接。由于一个Selector上可以注册多个非阻塞连接，这样就使得可以用更少的线程数来管理更多的连接。</p>
<p>
<span style="font-weight: bold;">为什么选择Cindy，而不直接使用NIO？</span>
</p>
<p>Java
NIO包虽然提供了非阻塞I/O模型，但是直接使用NIO的非阻塞I/O需要成熟的网络编程经验，处理众多底层的网络异常，以及维护连接状态，判断连接超
时等等。对于关注于其业务逻辑的应用而言，这些复杂性都是不必要的。不同Java版本的NIO实现也会有一些Bug，Cindy会巧妙的绕开这些已知的
Bug并完成相应功能。并且NIO本身也在不断发展中，Java 1.4的NIO包中只实现了TCP/UDP单播/Pipe，Java
5.0中引入的SSLEngine类使得基于非阻塞的流协议（TCP/Pipe）支持SSL/TLS成为可能，在未来的版本中还可能会加入非阻塞多播的实
现。Cindy会关注这些新功能，并将其纳入到统一的框架中来。</p>
<p>Cindy虽然目前的实现是基于NIO，但它会不仅仅局限于NIO。等到一些基于操作系统本身实现的AIO（Asynchronous IO）类库成熟后，它也会加入对这些类库的支持，通过操作系统本身实现的AIO来提高效率。</p>
<p>如果应用程序只想使用一种高效的模型，而不想关心直接使用NIO所带来的这些限制，或希望将来无需更改代码就切换到更高效率的AIO实现上，那么
Cindy会是一个很好的选择。并且使用Cindy，应用可以在同步和异步之间进行无缝切换，对于大部分操作是异步，可某些特殊操作需要同步的应用而言，
这极大的提高了易用性。</p>
<h2>
<a name="CindyUserGuide-Helloworldexample">
</a>
<a name="CindyUserGuide-Helloworldexample">
</a>Hello world example</h2>
<p>场景：服务端监听本地的1234端口，打印任何收到的消息到控制台上；客户端建立TCP连接到本地的1234端口，发送完"Hello world!"，然后断开连接。</p>
<h3>
<a name="CindyUserGuide-基于JavaIO包的客户端示例">
</a>基于Java IO包的客户端示例</h3>
<p>基于Java IO包的阻塞模型的示例，用于对比，不做额外说明。（异常处理代码略）</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">Socket socket = <span class="code-keyword">new</span> Socket(<span class="code-quote">"localhost"</span>,1234);<br>OutputStream os = <span class="code-keyword">new</span> BufferedOutputStream(socket.getOutputStream());<br>os.write(<span class="code-quote">"Hello world!"</span>.getBytes());<br>os.close();<br>socket.close();</pre>
</div>
</div>
<h3>
<a name="CindyUserGuide-基于JavaIO包的服务端示例">
</a>基于Java IO包的服务端示例</h3>
<p>基于Java IO包的阻塞模型的示例，用于对比，不做额外说明。（异常处理代码略）</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">ServerSocket ss = <span class="code-keyword">new</span> ServerSocket(1234);<br><span class="code-keyword">while</span> (<span class="code-keyword">true</span>) {<br>? <span class="code-keyword">final</span> Socket socket = ss.accept();<br>? <span class="code-keyword">new</span><span class="code-object">Thread</span>() {<br><br>??? <span class="code-keyword">public</span> void run() {<br>????? BufferedReader br = <span class="code-keyword">new</span> BufferedReader(<span class="code-keyword">new</span> InputStreamReader(socket.getInputStream()));<br>????? <span class="code-object">String</span> line = <span class="code-keyword">null</span>;<br>????? <span class="code-keyword">while</span> ((line = br.readLine()) != <span class="code-keyword">null</span>) {<br>??????? <span class="code-object">System</span>.out.println(line);<br>????? }<br>    }<br><br>  }.start();<br>}</pre>
</div>
</div>
<h3>
<a name="CindyUserGuide-基于Cindy的同步客户端示例">
</a>基于Cindy的同步客户端示例</h3>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-comment">//create tcp session<br></span>Session session = SessionFactory.createSession(SessionType.TCP);    <br><br><span class="code-comment">//set remote address<br></span>session.setRemoteAddress(<span class="code-keyword">new</span> InetSocketAddress(<span class="code-quote">"localhost"</span>, 1234)); <br><br><span class="code-comment">//start session and wait until completed<br></span>session.start().complete();    <br><br><span class="code-comment">//create send packet<br></span>Packet packet = <span class="code-keyword">new</span> DefaultPacket(BufferFactory.wrap(<span class="code-quote">"Hello world\!"</span>.getBytes())); <br><br><span class="code-comment">//send packet and wait until completed<br></span>session.flush(packet).complete();<br><br><span class="code-comment">//close session and wait until completed<br></span>session.close().complete();</pre>
</div>
</div>
<p>
<span style="font-weight: bold;">Step 1: create session</span>
</p>
<p>Session是连接的抽象，一个Session代表着一个连接。连接有多种类型，SessionType.TCP代表着TCP连接，SessionType.UDP代表着UDP连接，SessionType.PIPE代表着Pipe连接等等。</p>
<p>在这里通过SessionFactory.createSession(SessionType.TCP)创建了一个TCP连接。如果要创建UDP连接，可以使用类似的代码：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">Session session = SessionFactory.createSession(SessionType.UDP);</pre>
</div>
</div>
<p>连接创建后可以通过返回Session实例的getSessionType方法来得到该连接的类型。</p>
<p>
<span style="font-weight: bold;">Step 2: set the remote address</span>
</p>
<p>TCP需要先设置好要连接的地址才能开始连接，在这里是连接到本机的1234端口。对于UDP来说，这一步设置不是必须的。</p>
<p>
<span style="font-weight: bold;">Step 3: start session</span>
</p>
<p>参数设置完成后就可以通过session.start方法启动Session了。*和Java
IO中的阻塞同步调用不同，由于Cindy是一个异步I/O框架，在调用session.start()方法并返回后，连接可能还没有建立成功。*如果我
们想要切换到同步阻塞的方式，比如等连接建立成功后才返回，则可以通过start方法返回的Future对象来进行切换。</p>
<p>Session中所有的异步方法(如start/close/flush/send等)的返回值均为Future对象，该对象代表着所进行的异步操作。我们可以通过三种方式来处理Future对象：</p>
<ul>
    <li>阻塞：调用Future.complete()，则该方法会一直等到异步操作完成后才返回；或者可以调用Future.complete(int timeout)来等待异步操作完成，如果在指定时间内操作未完成，则该方法也会返回。<span style="font-weight: bold;">值得注意的是操作完成(isCompleted)包括操作成功和操作失败两种状态(isSucceeded)</span>。</li>
    <li>轮询：周期性的调用Future.isCompleted()方法来查询异步操作是否完成。</li>
    <li>回调：通过Future.addListener()方法加入自定义的FutureListener，在异步操作结束后，Future会触发FutureListener的futureCompleted事件。<span style="font-weight: bold;">对任何已完成的Future对象调用addListener方法会马上触发FutureListener的futureCompleted事件。</span></li>
</ul>
<p>在该示例中我们采用的是第一种方式——阻塞，等待连接建立完成后才返回。可以通过Future.complete()方法的返回值或者
Session.isStarted()来判断连接是否建立成功。在这个简单的示例里假设不会出现任何连接错误，所以就不再判断异常情况了，不过在正式的
应用中应当注意相关的判断。</p>
<p>
<span style="font-weight: bold;">Step 4: the Packet/Buffer interface</span>
</p>
<p>任何数据要在网络上传输最终都得转换成字节流，Buffer接口就是字节流的抽象。注意的是这里使用的Buffer是net.sf.cindy.Buffer，而没有采用java.nio.Buffer，这个设计上的取舍可以参考以后的介绍。</p>
<p>而数据传输除了要有内容外，还得有目的地，这样才能把内容送到正确的地址上。Packet接口就代表着要发送的数据，包括一个Buffer对象和一
个目的地址------SocketAddress。对于流协议（如TCP/Pipe）而言，目的地址在建立连接的时候就已经设置好了，所以发送过程中无
需再进行设置，用默认的null即可；对于消息协议（比如非连接型的UDP），则需要在发送时指定每个包的目的地址。</p>
<p>在这里要将"Hello world!"转换为Buffer对象，可以通过BufferFactory来完成：BufferFactory.wrap("Hello world!".getBytes())，将一个字节数组包装成Buffer。</p>
<p>由于是TCP连接，目的地址在建立连接时就已经指定好了，所以可以简单的构造一个只有Buffer而没有SocketAddress的Packet对象：new DefaultPacket(buffer)。（DefaultPacket是Packet接口的默认实现）</p>
<p>
<span style="font-weight: bold;">Step 5: send packet</span>
</p>
<p>Session的flush方法和start方法一样都是异步方法，在调用flush并返回时，数据可能并没有发送完成，在该示例中仍然采用阻塞方式等待数据发送完成后才返回：session.flush(packet).complete()。</p>
<p>flush和send方法的区别在于：flush方法接受的参数是Packet对象，send方法接受的参数是Object对象，通过send方法发送的对象会被Session所关联的PacketEncoder转换为Packet对象再进行发送。</p>
<p>
<span style="font-weight: bold;">Step 6: close session</span>
</p>
<p>发送完成后需要关闭连接，close方法同样是一个异步方法，在这里等待连接完全关闭后才返回：session.close().complete()。</p>
<p>从这个同步的示例可以看到，虽然Cindy是一个异步I/O框架，但用它来完成同步的I/O操作也是一件非常容易的事情。</p>
<h3>
<a name="CindyUserGuide-基于Cindy的异步客户端示例">
</a>基于Cindy的异步客户端示例</h3>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-keyword">final</span> Session session = SessionFactory.createSession(SessionType.TCP);<br>session.setRemoteAddress(<span class="code-keyword">new</span> InetSocketAddress(<span class="code-quote">"localhost"</span>, 1234));<br>Future <span class="code-keyword">future</span> = session.start();<br><span class="code-keyword">future</span>.addListener(<span class="code-keyword">new</span> FutureListener() {<br><br><span class="code-keyword">public</span> void futureCompleted(Future <span class="code-keyword">future</span>) <span class="code-keyword">throws</span> Exception {<br>    Packet packet = <span class="code-keyword">new</span> DefaultPacket(<span class="code-quote">"Hello world!"</span>.getBytes());<br>    session.flush(packet).complete();<br>    session.close();<br>  }<br><br>});</pre>
</div>
</div>
<p>前面的三行代码和同步示例没有区别，只是在第四行代码中采用了回调的方式来处理Future对象，而不是通过阻塞的方式。当连接建立完成后，FutureListener的futureCompleted事件被触发，应用可以在该方法中做相应的事件处理。</p>
<p>在发送Packet的过程中，其实也可以采用回调的方式来处理。在这里仍采用同步方法处理，一方面是减少内嵌类的数量，另一方面是示例Cindy可以非常容易的切换同步和异步操作。后文对异步处理会有更详细的介绍。</p>
<h3>
<a name="CindyUserGuide-基于Cindy的服务端示例">
</a>基于Cindy的服务端示例</h3>
<div class="code">
<div class="codeContent">
<pre class="code-java">SessionAcceptor acceptor = SessionFactory.createSessionAcceptor(SessionType.TCP);<br>acceptor.setListenPort(1234);<br>acceptor.setAcceptorHandler(<span class="code-keyword">new</span> SessionAcceptorHandlerAdapter() {<br><br><span class="code-keyword">public</span> void sessionAccepted(SessionAcceptor acceptor, Session session) <span class="code-keyword">throws</span> Exception {<br>    session.setSessionHandler(<span class="code-keyword">new</span> LogSessionHandler());<br>    session.start();<br>  }<br><br>});<br>acceptor.start();</pre>
</div>
</div>
<p>
<span style="font-weight: bold;">Step 1: create SessionAcceptor</span>
</p>
<p>SessionAcceptor代表着连接的服务端，SessionAcceptor和Session是一对多关系，类似于IO包的ServerSocket和Socket。这里创建的是TCP服务端。</p>
<p>
<span style="font-weight: bold;">Step 2: set listen port</span>
</p>
<p>设置服务端的监听端口，在这里是1234端口；如果要设置监听地址，则可以通过setListenAddress来进行设置。</p>
<p>
<span style="font-weight: bold;">Step 3: set acceptor handler</span>
</p>
<p>每当SessionAcceptor上有连接建立成功，将会触发该SessionAcceptor所关联SessionAcceptorHandler的sessionAccepted事件，应用可以在该事件中为连接上的Session设置一些属性并开始或关闭连接。</p>
<p>SessionAcceptorHandler接口是处理SessionAcceptor产生的各种事件，SessionHandler接口则用于
处理Session产生的各种事件。在这里我们仅仅关心对象接收事件（objectReceived），当接收到对象后，将该对象打印到控制台上。（注：
LogSessionHandler的代码没有列出来）</p>
<p>SessionHandler/SessionAcceptorHandler的更多介绍请参阅后面章节。</p>
<h2>
<a name="CindyUserGuide-PacketEncoder/PacketDecoder">
</a>
<a name="CindyUserGuide-PacketEncoder/PacketDecoder">
</a>PacketEncoder/PacketDecoder</h2>
<h3>
<a name="CindyUserGuide-PacketEncoder">
</a>PacketEncoder</h3>
<p>在前面的Hello
world示例中，我们都是通过session.flush方法来发送数据，而该方法只接收Packet类型的参数，这就要求我们在发送任何数据前都要先
进行转换。比如在前面示例中，我们就得先将"Hello world!"字符串转换成一个代表"Hello
world!"的Packet，然后再进行发送。</p>
<p>这种做法没有什么问题，其唯一的缺陷在于将发送逻辑和序列化逻辑耦合在一起。比如在上面的示例中，发送逻辑是将"Hello
world!"发送出去，序列化逻辑是"Hello
world"字符串的字节表示。虽然有一定的关联，但的确是两种不同的逻辑，比如我们可以更改序列化逻辑，通过Serializable接口来序列化
"Hello world"，但这并不影响发送逻辑。</p>
<p>神说，要有光，就有了光。你知道，神不关心光是怎么来的。PacketEncoder的作用就是分离发送逻辑和序列化逻辑。对于应用而言，在发送时
它只需要把要发送的对象传递给Session，至于怎么序列化，则由Session所关联的PacketEncoder来处理。所以在上面的Hello
world示例中，发送逻辑可以改为：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">session.send(<span class="code-quote">"Hello world!"</span>);</pre>
</div>
</div>
<p>序列化逻辑可以通过PacketEncoder来设置：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">session.setPacketEncoder(<span class="code-keyword">new</span> PacketEncoder() {<br><br><span class="code-keyword">public</span> Packet encode(Session session, <span class="code-object">Object</span> obj) <span class="code-keyword">throws</span> Exception {<br><span class="code-keyword">return</span><span class="code-keyword">new</span> DefaultPacket(BufferFactory.wrap(obj.toString().getBytes()));<br>  }<br><br>});</pre>
</div>
</div>
<p>如果要改变序列化逻辑，比如通过Serializable接口来序列化，则只需要更改PacketEncoder，而不需要改动发送代码：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">session.setPacketEncoder(<span class="code-keyword">new</span> SerialEncoder());</pre>
</div>
</div>
<p>通过Cindy内置的PacketEncoderChain，应用可通过Session发送任意对象，把序列化逻辑完全交给PacketEncoder。如下面的伪码所示：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">PacketEncoderChain chain = <span class="code-keyword">new</span> PacketEncoderChain();<br>chain.addPacketEncoder(<span class="code-keyword">new</span> Message1Encoder());<br>chain.addPacketEncoder(<span class="code-keyword">new</span> Message2Encoder());<br><br>session.setPacketEncoder(chain);<br>session.send(<span class="code-keyword">new</span> Message1());<br>session.send(<span class="code-keyword">new</span> Message2());</pre>
</div>
</div>
<h3>
<a name="CindyUserGuide-PacketDecoder">
</a>PacketDecoder</h3>
<p>PacketEncoder是用来处理序列化逻辑的，相应的，PacketDecoder则是用来处理反序列化逻辑的。</p>
<p>发送方通过session.send方法可以发送任意对象，Session关联的PacketEncoder会将该对象转换为Packet发送出
去；接收方收到了Packet后，也可以通过其关联的PacketDecoder将该Packet转换为一个对象，再通知应用。假设发送方用了
SerialEncoder来发送序列化对象，则接收方就可以使用SerialDecoder来进行反序列化，然后应用就可以直接对对象进行处理。</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">session.setPacketDecoder(<span class="code-keyword">new</span> SerialDecoder());<br>session.setSessionHandler(<span class="code-keyword">new</span> SessionHandlerAdapter() {<br><br><span class="code-keyword">public</span> void objectReceived(Session session, <span class="code-object">Object</span> obj) <span class="code-keyword">throws</span> Exception {<br><span class="code-comment">//obj即为反序列化后得到的对象<br></span>  }<br><br>}</pre>
</div>
</div>
<p>Cindy源代码example目录下net.sf.cindy.example.helloworld包下提供了一个非常简单的示例。</p>
<p>
<span style="font-weight: bold;">应用在实现PacketDecoder的时候要注意判断当前已接收到的内容长度。</span>
</p>
<p>比如TCP是一个流协议，一方发送了1000个字节，另一方可能在接收的时候只收到了前200个字节，剩下的800个字节要在下次接收时才收到，可是需要1000个字节才能构造出一个完整的对象，则应用的PacketDecoder实现可能会类似于：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-keyword">public</span>
<span class="code-object">Object</span> decode(Session session, Packet packet) <span class="code-keyword">throws</span> Exception {<br>  Buffer content = packet.getContent();  <br><span class="code-keyword">if</span> (content.remaining() &gt;= 1000) {<br><span class="code-comment">//如果接收到的长度大于1000，则从Buffer中取出内容并返回decode结果<br></span>  }<br><span class="code-keyword">return</span><span class="code-keyword">null</span>;  <span class="code-comment">//接收长度不足，等待全部接收完成后再decode，返回null不会触发objectReceived事件<br></span>}</pre>
</div>
</div>
<h2>
<a name="CindyUserGuide-SessionHandler/SessionHandlerAdapter">
</a>
<a name="CindyUserGuide-SessionHandler/SessionHandlerAdapter">
</a>SessionHandler/SessionHandlerAdapter</h2>
<p>SessionHandler接口用于处理Session的各种事件。比如当连接建立成功后，会触发SessionHandler的
sessionStarted事件；连接关闭后，会触发SessionHandler的sessionClosed事件；对象发送成功后会触发
SessionHandler的objectSent事件；对象接收成功后会触发SessionHandler的objectReceived事件等等。</p>
<p>SessionHandlerAdapter是SessionHandler的空实现。即如果你仅仅对SessionHandler中某几个事件感
兴趣，就不用全部实现SessionHandler中定义的各种方法，而只需要继承自SessionHandlerAdapter，实现感兴趣的事件即
可。</p>
<p>通过SessionHandler，可以极大的减少内嵌类的数量。如前面的异步Hello world示例，如果用SessionHandler来改写，则会是：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">Session session = SessionFactory.createSession(SessionType.TCP);<br>session.setRemoteAddress(<span class="code-keyword">new</span> InetSocketAddress(<span class="code-quote">"localhost"</span>, 1234));<br>session.setSessionHandler(<span class="code-keyword">new</span> SessionHandlerAdapter() {<br><br><span class="code-keyword">public</span> void sessionStarted(Session session) <span class="code-keyword">throws</span> Exception {<br><span class="code-comment">// session started<br></span>    Buffer buffer = BufferFactory.wrap(<span class="code-quote">"Hello world!"</span>.getBytes());<br>    Packet packet = <span class="code-keyword">new</span> DefaultPacket(buffer);<br>    session.send(packet);<br>  }<br><br><span class="code-keyword">public</span> void objectSent(Session session, <span class="code-object">Object</span> obj) <span class="code-keyword">throws</span> Exception {<br><span class="code-comment">// the packet have been sent, close current session<br></span>    session.close();<br>  };<br>};<br>session.start();</pre>
</div>
</div>
<p>在上面的代码中，当sessionStarted事件触发后，即Session成功建立后，会发送"Hello
world!"消息；当objectSent事件触发后，即消息发送成功，调用session.close()异步关闭连接。这些处理全部都是异步的，并
且仅仅使用了一个内嵌类。</p>
<p>SessionHandler中一共定义了如下几个方法：</p>
<ul>
    <li>void sessionStarted(Session session) throws Exception //连接已建立</li>
    <li>void sessionClosed(Session session) throws Exception //连接已关闭</li>
    <li>void sessionTimeout(Session session) throws Exception //连接超时</li>
    <li>void objectReceived(Session session, Object obj) throws Exception //接收到了对象</li>
    <li>void objectSent(Session session, Object obj) throws Exception //发送了对象</li>
    <li>void exceptionCaught(Session session, Throwable cause) //捕捉到异常</li>
</ul>
<p>如果通过session.setSessionTimeout方法设置了超时时间，则在指定的时间内没有接收或发送任何数据就会触发
sessionTimeout事件。发生了该事件并不代表着连接被关闭，应用可以选择关闭该空闲连接或者发送某些消息来检测网络连接是否畅通。默认情况下
sessionTimeout为0，即从不触发sessionTimeout事件。</p>
<p>如果通过PacketEncoder发送了任何对象，则objectSent事件将被触发（注：通过Session.flush方法发送的
Packet不会触发objectSent事件，只有通过Session.send方法发送的对象才会触发objectSent事件）；通过
PacketDecoder接收到任何对象，则objectReceived事件将被触发，一般情况下应用都通过监听该事件来对接收到的对象做相应处理。</p>
<p>exceptionCaught事件代表着session捕捉到了一个异常，这个异常可能是由于底层网络所导致，也可能是应用在处理SessionHandler事件时所抛出来的异常。请注意，<span style="font-weight: bold;">如果应用在处理exceptionCaught事件中抛出运行期异常，则该异常不会再度触发exceptionCaught事件，否则可能出现死循环</span>。
由于应用无法处理底层网络所引发的异常，所以在部署稳定后，可以通过指定运行期参数-
Dnet.sf.cindy.disableInnerException来取消对底层网络异常的分发，或者判断异常类型——所有的内部异常都是从
SessionException基类继承下来的，应用可以根据这个特性来判断是底层网络出现了异常，还是SessionHandler或
SessionFilter中抛出了异常。</p>
<h2>
<a name="CindyUserGuide-SessionFilter/SessionFilterAdapter">
</a>
<a name="CindyUserGuide-SessionFilter/SessionFilterAdapter">
</a>SessionFilter/SessionFilterAdapter</h2>
<p>SessionFilter与SessionHandler有些类似，均用于处理Session的各种事件，不同的则是SessionFilter
先处理这些事件，并判断是否需要把该事件传递给下一个SessionFilter。等到所有SessionFilter处理完成后，事件才会传递给
SessionHandler由其来处理。</p>
<p>SessionFilterAdapter是SessionFilter的空实现，默认是把事件传递给下一个SessionFilter。SessionFilter中定义了如下几个方法：</p>
<ul>
    <li>void sessionStarted(SessionFilterChain filterChain) throws Exception;</li>
    <li>void sessionClosed(SessionFilterChain filterChain) throws Exception;</li>
    <li>void sessionTimeout(SessionFilterChain filterChain) throws Exception;</li>
    <li>void objectReceived(SessionFilterChain filterChain, Object obj) throws Exception;</li>
    <li>void objectSent(SessionFilterChain filterChain, Object obj) throws Exception;</li>
    <li>void exceptionCaught(SessionFilterChain filterChain, Throwable cause);</li>
    <li>void packetReceived(SessionFilterChain filterChain, Packet packet) throws Exception;</li>
    <li>void packetSend(SessionFilterChain filterChain, Packet packet) throws Exception;</li>
    <li>void packetSent(SessionFilterChain filterChain, Packet packet) throws Exception;</li>
</ul>
<p>其中前六种方法和SessionHandler的作用一致，后三种方法则是用于处理接收和发送的Packet的。</p>
<p>
<span style="font-weight: bold;">可以看到SessionFilter可以算做SessionHandler的超集，那为什么需要引入SessionHandler呢？</span>
</p>
<p>虽然SessionFilter和SessionHandler在表现形式上有很多接近的地方，但是在应用逻辑上却是处于不同的地位。
SessionHandler目的就是处理应用最为核心的业务逻辑，这些逻辑都是基于Object的，和网络层没有太大的关系；而
SessionFilter和网络层的关联就比较大了，一般用来处理网络相关的一些逻辑（如包压缩/解压缩、包加密/解密）或者是核心业务逻辑外的一些分
支逻辑（如记录日志、黑名单处理）。</p>
<p>基于SessionFilter，应用可以做很多扩展而不影响核心的业务处理（核心的业务处理应该放在SessionHandler中）。比如数据
包相关的扩展：加入SSLFilter，则所发送的数据都会被SSL编码后才发送，接收的数据会先被解码成明文才接收；加入ZipFilter，则可以压
缩所发送的数据，接收时再解压缩。比如统计的扩展：加入StatisticFilter，则可以统计发送和接收的字节数，以及发送速率。比如ACL的扩
展：加入AllowListFilter/BlockListFilter，则可以允许指定或限制某些IP地址访问；加入LoginFilter，如果用
户没有登录，则不把事件传递给后面处理业务逻辑的SessionFilter或SessionHandler。比如线程处理的扩展：加入
ThreadPoolFilter，可以指定让某个线程池来进行后面事件的处理。比如日志记录的扩展：加入LogFilter，则可以记录相应的事件信
息。</p>
<p>所列举的这些只是一些基于SessionFilter的常见应用，应用可以根据自身的业务需要来进行选择。Cindy所推荐的实践是将不同的业务逻辑分散到不同的SessionFilter中，在SessionHandler中只处理核心逻辑。</p>
<p>在这里可以示范一个ZipFilter的伪码：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-keyword">public</span> class ZipFilter <span class="code-keyword">extends</span> SessionFilterAdapter {<br><br><span class="code-keyword">public</span> void packetReceived(SessionFilterChain filterChain, Packet packet) <span class="code-keyword">throws</span> Exception {<br>    Packet unzippedPacket = unzip(packet);  <span class="code-comment">//解压缩<br></span><span class="code-keyword">super</span>.packetReceived(filterChain, unzippedPacket);<span class="code-comment">//把解压缩后的包传递给下一个Filter<br></span>  }<br><br><span class="code-keyword">public</span> void packetSend(SessionFilterChain filterChain, Packet packet) <span class="code-keyword">throws</span>  Exception {<br>    Packet zippedPacket = zip(packet);  <span class="code-comment">//压缩<br></span><span class="code-keyword">super</span>.packetSend(filterChain, zippedPacket); <span class="code-comment">//把压缩后的包传递给下一个Filter<br></span>  }<br>}</pre>
</div>
</div>
<h2>
<a name="CindyUserGuide-Buffer/Packet">
</a>
<a name="CindyUserGuide-Buffer/Packet">
</a>Buffer/Packet</h2>
<p>在前面的示例中我们已经接触到了Buffer/Packet，Buffer是数据流的抽象，Packet是网络包的抽象。</p>
<p>
<span style="font-weight: bold;">Java NIO中已经提供了java.nio.ByteBuffer类用于表示字节流，为什么不直接使用java.nio.ByteBuffer？</span>
</p>
<p>java.nio.ByteBuffer虽然是NIO中表示字节流的标准类，但是对于高负荷的网络应用而言，其设计上存在着以下缺陷：</p>
<ul>
    <li>ByteBuffer并不是一个接口，而是一个抽象类。最为关键的地方是其构造函数为包级私有，这意味着我们无法继承自ByteBuffer构造子类。</li>
    <li>ByteBuffer
    仅仅是其所持有内容的一个外部包装，多个不同的ByteBuffer可以共享相同的内容。比如通过slice、duplicate等方法构造一个新的
    ByteBuffer，该新ByteBuffer和原ByteBuffer共享的是同一份内容。这就意味着无法构造基于ByteBuffer的缓存机制。</li>
</ul>
<p>比如如下代码：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">ByteBuffer buffer1 = ByteBuffer.allocate(100);<br>ByteBuffer buffer2 = buffer1.slice();<br><span class="code-object">System</span>.out.println(buffer1 == buffer2);<br><span class="code-object">System</span>.out.println(buffer1.equals(buffer2));</pre>
</div>
</div>
<p>打印出来的都是false，而实际上两个ByteBuffer共享的是同一份数据。在不经意的情况下，可能发生应用把两个ByteBuffer返回缓存中，被缓存当成是不同的对象进行处理，可能破坏数据完整性。</p>
<p>
<span style="font-weight: bold;">为什么Cindy使用自定义的Buffer接口？</span>
</p>
<ul>
    <li>Buffer是一个接口，如果对现有的实现类不满意，应用可以方便的加入自己的实现</li>
    <li>支持一系列的工具方法，比如indexOf/getString/putString/getUnsignedXXX等等，加入这些常用方法会给应用带来很大的方便</li>
    <li>可以非常方便的与nio中的ByteBuffer做转换，并且效率上不会有太大损失。由于大部分的网络类库都是基于nio的ByteBuffer来设计的，这样保证了兼容性</li>
    <li>NIO的ByteBuffer无法表示ByteBuffer数组，而Cindy中提供了工具类把Buffer数组包装成一个Buffer</li>
    <li>Cindy的Buffer是可缓存的，对于高负荷的网络应用而言，这会带来性能上优势</li>
</ul>
<p>在一般情况下，应用应该直接通过BufferFactory类来构造Buffer实例。目前BufferFactory类中有如下方法：</p>
<ul>
    <li>Buffer wrap(byte[] array)</li>
    <li>Buffer wrap(byte[] array, int offset, int length)</li>
    <li>Buffer wrap(ByteBuffer buffer)</li>
    <li>Buffer wrap(Buffer[] buffers)</li>
    <li>Buffer allocate(int capacity)</li>
    <li>Buffer allocate(int capacity, boolean direct)</li>
</ul>
<p>前四种方法都是包装方法，将现有的字节数组、ByteBuffer以及Buffer数组包装成一个单一的Buffer对象。第五和第六种方法是构造
一个新的Buffer对象，新构造Buffer对象的内容是不确定的（java.nio.ByteBuffer.allocate得到的内容是全0），可
能来自缓存或直接生成，依赖于对Buffer缓存的配置。一般情况下都是通过第五种方法构造新的Buffer对象，通过运行期参数-
Dnet.sf.cindy.useDirectBuffer可以改变默认行为，是构造Non-Direct Buffer还是构造Direct
Buffer。</p>
<p>默认情况下生成的Buffer是可以被缓存，在调用了session.send/flush方法后，Buffer的内容就会被释放掉，或者手动调用
Buffer.release也能释放Buffer所持有的内容。通过Buffer.isReleased方法可以可以判断当前的Buffer是否被释放
掉。对已经释放掉的Buffer进行get/put操作都会导致ReleasedBufferException，但是对
position/limit/capacity的操作还是有效的。如果应用不希望Buffer的内容被释放，可以通过设置permanent属性为
true来使得Buffer的内容不被释放。</p>
<p>
<span style="font-weight: bold;">是否所有Buffer都需要手动调用release来释放？</span>
</p>
<p>
<span style="font-weight: bold;">这里有一个基本的原则：谁生成，谁释放</span>。比如应用调用BufferFactory.allocate(1024)得到了一个Buffer实例，这个Buffer实例是由应用生成出来的，那么应用在使用完该Buffer实例后就应该负责调用buffer.release将它释放掉。</p>
<p>不过框架为了提供应用的方便，所有通过session.send/flush方法发送的Buffer都会在发送完成后被释放。如果上面得到的这个
Buffer实例通过调用session.flush方法被发送出去，那么该Buffer实例就不需要再通过release方法手工释放了。</p>
<p>如果应用不手动release自己生成出来的Buffer，也不会造成内存泄漏，因为这些Buffer会通过Java的垃圾回收机制被回收掉。唯一的缺陷在于由于无法重用对象，性能可能会有少许的降低。</p>
<p>举个例子，假设你实现了一个特定的PacketDecoder：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-keyword">public</span> class MyPacketDecoder <span class="code-keyword">implements</span> PacketDecoder {<br><br><span class="code-keyword">public</span><span class="code-object">Object</span> decode(Session session, Packet packet) <span class="code-keyword">throws</span> Exception {<br>    Buffer content = packet.getContent();<br>    Buffer result = allocateBuffer();<br>    decodeContent(content, result);<br><span class="code-keyword">return</span> result;<br>  }<br><br>}</pre>
</div>
</div>
<p>可以看到，在MyPacketDecoder的实现当中，并没有对接收到的Buffer调用release方法，因为这个Buffer并不是应用生
成出来的，不应该由应用来释放。在该MyPacketDecoder实现中生成了一个新的Buffer实例，并当作PacketDecoder的返回值。
这个新的实例是应用生成出来的，则应该由应用来释放，所以应用应该在相应的objectReceived事件中释放该Buffer实例，如：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-keyword">public</span> void objectReceived(Session session, <span class="code-object">Object</span> obj) <span class="code-keyword">throws</span> Exception {<br>  Buffer buffer = (Buffer) obj;<br><span class="code-keyword">try</span> {<br>    process(buffer);<br>  } <span class="code-keyword">finally</span> {<br>    buffer.release();<br>  }<br>}</pre>
</div>
</div>
<h2>
<a name="CindyUserGuide-AdvancedTopic">
</a>
<a name="CindyUserGuide-AdvancedTopic">
</a>Advanced Topic</h2>
<h3>
<a name="CindyUserGuide-JMX支持">
</a>JMX支持</h3>
<p>通过运行期属性-Dnet.sf.cindy.useJmx可以开启JMX支持（Java 5.0中内置了JMX支持，如果运行在Java 1.4版本中，则需要手工下载相应类库）。</p>
<p>下图是通过jconsole进行JMX管理的示例：</p>
<p>
<img src="http://confluence.redsaga.com/download/attachments/1361/jmx.png" align="absmiddle" border="0">
</p>
<h3>
<a name="CindyUserGuide-流量控制">
</a>流量控制</h3>
<p>当网络接收的速度大于应用的处理速度时，如果不控制接收速率，则收到的消息会在队列中堆积，应用无法及时处理而造成内存溢出。Cindy
3.0中加入了流量控制功能，当接收队列中消息超过指定数量时，Cindy会放慢网络接收速度。该功能对于防止内存溢出以及应用程序调试有很大帮助。</p>
<p>可以通过运行期参数-Dnet.sf.cindy.dispatcher.capacity来指定队列中最多可以堆积的消息数。默认值是1000，即队列中消息数超过1000，网络接收速度就会放缓，等到消息数少于1000后，接收速度就会恢复正常。</p>
<h3>
<a name="CindyUserGuide-Readpacketsize">
</a>Read packet size</h3>
<p>Read packet size是指接收时每次读包所构造的缓冲区大小。对于TCP而言，这个值影响到的仅仅是效率；对于UDP则影响到数据正确性。</p>
<p>对于UDP应用，假设发送方发送的包所携带数据大小为1000字节，接收方的read packet size设置为600字节，则后面400个字节会被丢弃，这样<br>
构造出来的逻辑包可能会不正确。</p>
<p>对于TCP应用，如果逻辑包是定长的，这个值最好也设为逻辑包的长度。该值太少，则可能导致在过多的包组合操作（比如逻辑包长度为1000字节，
read packet
size设置为100，则可能需要把10次接收到的packet组合成一个packet才能构造出一个逻辑包）；该值太大，可能造成内存的浪费（不过由于
Buffer缓存的存在会缓解这一情况）。</p>
<p>默认的read packet
size是8192字节，可以通过运行期属性-Dnet.sf.cindy.session.readPacketSize来更改。如果对单独的
Session进行更改，则可以通过session的readPacketSize属性进行设置。</p>
<h3>
<a name="CindyUserGuide-DirectByteBuffervsNonDirectByteBuffer">
</a>Direct ByteBuffer vs Non-Direct ByteBuffer</h3>
<p>java.nio.ByteBuffer引入了这两种类型的ByteBuffer，在Cindy中也有基于这两种ByteBuffer的Buffer包装类。那么这两者的区别在什么地方？在什么环境下采用哪种类型的ByteBuffer会更有效率？</p>
<p>Non-direct ByteBuffer内存是分配在堆上的，直接由Java虚拟机负责垃圾收集，你可以把它想象成一个字节数组的包装类，如下伪码所示：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">HeapByteBuffer <span class="code-keyword">extends</span> ByteBuffer {<br><span class="code-object">byte</span>[] content;<br><span class="code-object">int</span> position, limit, capacity;<br>    ......<br>}</pre>
</div>
</div>
<p>而Direct ByteBuffer是通过JNI在Java虚拟机外的内存中分配了一块，该内存块并不直接由Java虚拟机负责垃圾收集，但是在Direct ByteBuffer包装类被回收时，会通过Java Reference机制来释放该内存块。如下伪码所示：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">DirectByteBuffer <span class="code-keyword">extends</span> ByteBuffer {<br><span class="code-object">long</span> address;<br><span class="code-object">int</span> position, limit, capacity;<br><br><span class="code-keyword">protected</span> void finalize() <span class="code-keyword">throws</span> Throwable{<br><span class="code-comment">//释放内存块，该段代码仅仅用于演示，真正的Direct ByteBuffer并不是通过finalize来释放的<br></span>        releaseAddress();  <br>       ......<br>    }<br>    ......<br>}</pre>
</div>
</div>
<p>除开以上的这些外，如果我们查找Java实现类的代码，就可以了解到这两者之间更深入的区别。比如在Sun的Java实现中，绝大部分
Channel类都是通过sun.nio.ch.IOUtil这个工具类和外界进行通讯的，如FileChannel/SocketChannel等等。
简单的用伪码把write方法给表达出来（read方法也差不多，就不多做说明了）：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">								<span class="code-object">int</span> write(ByteBuffer src, ......) {<br><span class="code-keyword">if</span> (src <span class="code-keyword">instanceof</span> DirectBuffer)<br><span class="code-keyword">return</span> writeFromNativeBuffer(...);<br>    ByteBuffer direct = getTemporaryDirectBuffer(src);<br>    writeFromNativeBuffer(direct,......);<br>    updatePosition(src);<br>    releaseTemporaryDirectBuffer(direct);<br>}</pre>
</div>
</div>
<p>是的，在发送和接收前会把Non-direct ByteBuffer转换为Direct
ByteBuffer，然后再进行相关的操作，最后更新原始ByteBuffer的position。这意味着什么？假设我们要从网络中读入一段数据，再
把这段数据发送出去的话，采用Non-direct ByteBuffer的流程是这样的：</p>
<div class="preformatted">
<div class="preformattedContent">
<pre>								<br>网络 --&gt; 临时的Direct ByteBuffer --&gt; 应用 Non-direct ByteBuffer --&gt; 临时的Direct ByteBuffer --&gt; 网络<br></pre>
</div>
</div>
<p>而采用Direct ByteBuffer的流程是这样的：</p>
<div class="preformatted">
<div class="preformattedContent">
<pre>网络 --&gt; 应用 Direct ByteBuffer --&gt; 网络<br></pre>
</div>
</div>
<p>可以看到，除开构造和析构临时Direct ByteBuffer的时间外，起码还能节约两次内存拷贝的时间。那么是否在任何情况下都采用Direct Buffer呢？</p>
<p>答案是否定的。对于大部分应用而言，两次内存拷贝的时间几乎可以忽略不计，而构造和析构Direct
Buffer的时间却相对较长。在JVM的实现当中，某些方法会缓存一部分临时Direct ByteBuffer，意味着如果采用Direct
ByteBuffer仅仅能节约掉两次内存拷贝的时间，而无法节约构造和析构的时间。就用Sun的实现来说，write(ByteBuffer)和
read(ByteBuffer)方法都会缓存临时Direct
ByteBuffer，而write(ByteBuffer[])和read(ByteBuffer[])每次都生成新的临时Direct
ByteBuffer。</p>
<p>根据这些区别，在选择ByteBuffer类型上有如下的建议：</p>
<ul>
    <li>如果你做中小规模的应用（在这里，应用大小是按照使用ByteBuffer的次数和规模来做划分的），并不在乎这些细节问题，请选择Non-direct ByteBuffer</li>
    <li>如果采用Direct ByteBuffer后性能并没有出现你所期待的变化，请选择Non-direct ByteBuffer</li>
    <li>如果没有Direct ByteBuffer Pool，尽量不要使用Direct ByteBuffer</li>
    <li>除非你确定该ByteBuffer会长时间存在，并且和外界有频繁交互，可采用Direct ByteBuffer</li>
    <li>如
    果采用Non-direct
    ByteBuffer，那么采用非聚集(gather)的write/read(ByteBuffer)效果反而*可能*超出聚集的write/read
    (ByteBuffer[])，因为聚集的write/read的临时Direct
    ByteBuffer是非缓存的（在Sun的实现上是这样，其他的实现则不确定）</li>
</ul>
<p>基本上，采用Non-direct
ByteBuffer总是对的！因为内存拷贝需要的开销对大部分应用而言都可以忽略不计。在Cindy中，一般的应用只需要通过
BufferFactory.allocate方法来得到Buffer实例即可，默认设置下采用的是Non-Direct
Buffer；通过BufferFactory.wrap方法包装字节数组或ByteBuffer得到的Buffer类也有很高的效率；只有通过
BufferFactory.wrap(Buffer[])方法目前还处于实验阶段，其效率不一定比生成一个大的Buffer，然后拷贝现有内容更快。如
果应用非常注重效率，要使用该方法上要多加注意。</p>
<h3>
<a name="CindyUserGuide-默认参数设置">
</a>默认参数设置</h3>
<p>对Cindy中一些默认参数的配置可以通过以下两种方式：</p>
<ul>
    <li>配置文件配置</li>
</ul>
<p>Cindy在启动时会在当前classpath上寻找cindy.properties文件，如果找到后会把该属性配置文件读入到缓存中。在该
properties中的配置无需net.sf.cindy.前缀，即如果要配置net.sf.cindy.enableJmx=true并且使用
DirectBuffer，则只需要在cindy.properties中加入：</p>
<div class="code">
<div class="codeContent">
<pre class="code-java">enableJmx=<span class="code-keyword">true</span><br>useDirectBuffer=<span class="code-keyword">true</span></pre>
</div>
</div>
<ul>
    <li>运行期属性配置</li>
</ul>
<p>运行时通过-D参数指定的配置，如果和上面配置文件中的Key相同，则会覆盖配置文件的配置。通过-D参数指定配置时不能省略net.sf.cindy.前缀。</p>
<p>当前版本全部可以配置的属性请参见Cindy源代码包的readme.txt。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94797.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:09 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94797.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java多线程经典实例</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94799.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94799.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94799.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94799.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94799.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94799.html</trackback:ping><description><![CDATA[             import java.io.*;
<br>//多线程编程
<br>public class MultiThread &nbsp;
<br>{
<br>public static void main(String args[])
<br>{
<br>System.out.println("我是主线程!");
<br>//下面创建线程实例thread1
<br>ThreadUseExtends thread1=new ThreadUseExtends();
<br>//创建thread2时以实现了Runnable接口的THhreadUseRunnable类实例为参数
<br>Thread thread2=new Thread(new ThreadUseRunnable(),"SecondThread");
<br>thread1.start();//启动线程thread1使之处于就绪状态
<br>//thread1.setPriority(6);//设置thread1的优先级为6
<br>//优先级将决定cpu空出时，处于就绪状态的线程谁先占领cpu开始运行
<br>//优先级范围1到10,MIN_PRIORITY,MAX_PRIORITY,NORM_PAIORITY
<br>//新线程继承创建她的父线程优先级,父线程通常有普通优先级即5NORM_PRIORITY
<br>System.out.println("主线程将挂起7秒!");
<br>try
<br>{
<br>Thread.sleep(7000);//主线程挂起7秒
<br>}
<br>catch (InterruptedException e)
<br>{
<br>return;
<br>}
<br>System.out.println("又回到了主线程!");
<br>if(thread1.isAlive())
<br>{ &nbsp;
<br>thread1.stop();//如果thread1还存在则杀掉他
<br>System.out.println("thread1休眠过长,主线程杀掉了thread1!");
<br>}
<br>else
<br>System.out.println("主线程没发现thread1,thread1已醒顺序执行结束了!");
<br>thread2.start();//启动thread2
<br>System.out.println("主线程又将挂起7秒!");
<br>try
<br>{
<br>Thread.sleep(7000);//主线程挂起7秒
<br>}
<br>catch (InterruptedException e)
<br>{
<br>return;
<br>}
<br>System.out.println("又回到了主线程!");
<br>if(thread2.isAlive())
<br>{ &nbsp;
<br>thread2.stop();//如果thread2还存在则杀掉他
<br>System.out.println("thread2休眠过长，主线程杀掉了thread2!");
<br>}
<br>else
<br>System.out.println("主线程没发现thread2,thread2已醒顺序执行结束了!");
<br>System.out.println("程序结束按任意键继续!");
<br>try
<br>{
<br>System.in.read();
<br>}
<br>catch (IOException e)
<br>{
<br>System.out.println(e.toString());
<br>}
<br><br>}//main
<br>}//MultiThread
<br><br><br>class ThreadUseExtends extends Thread
<br>//通过继承Thread类,并实现它的抽象方法run()
<br>//适当时候创建这一Thread子类的实例来实现多线程机制
<br>//一个线程启动后（也即进入就绪状态）一旦获得CPU将自动调用它的run()方法
<br>{
<br><br>ThreadUseExtends(){}//构造函数
<br>public void run()
<br>{
<br>System.out.println("我是Thread子类的线程实例!");
<br>System.out.println("我将挂起10秒!");
<br>System.out.println("回到主线程,请稍等,刚才主线程挂起可能还没醒过来！");
<br>try
<br>{
<br>sleep(10000);//挂起5秒
<br>}
<br>catch (InterruptedException e)
<br>{
<br>return;
<br>}
<br>//如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
<br>//但如果休眠时间过长,则线程还存活,可能被stop()杀掉
<br>}
<br>}
<br><br><br>class ThreadUseRunnable implements Runnable
<br>//通过实现Runnable接口中的run()方法,再以这个实现了run()方法的类
<br>//为参数创建Thread的线程实例
<br>{
<br>//Thread thread2=new Thread(this);
<br>//以这个实现了Runnable接口中run()方法的类为参数创建Thread类的线程实例
<br>ThreadUseRunnable(){}//构造函数
<br>public void run()
<br>{
<br>System.out.println("我是Thread类的线程实例并以实现了Runnable接口的类为参数!");
<br>System.out.println("我将挂起1秒!");
<br>System.out.println("回到主线程,请稍等,刚才主线程挂起可能还没醒过来！");
<br>try
<br>{
<br>Thread.sleep(1000);//挂起5秒
<br>}
<br>catch (InterruptedException e)
<br>{
<br>return;
<br>}
<br>//如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
<br>//但如果休眠时间过长,则线程还存活,可能被stop()杀掉
<br>}
<br>}
<br>//该程序可做的修改如改休眠时间或优先级setPriority()&nbsp; <br>  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94799.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:09 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94799.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java数据报之失序和丢包</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94795.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:08:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94795.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94795.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94795.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94795.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94795.html</trackback:ping><description><![CDATA[		<span id="ArticleContent1_ArticleContent1_lblContent">
<p>习惯了TCP编程，认为UDP可以包办这些问题是错误的。一个UDP应用程序要承担可靠性方面的全部工作，包括报文的丢失、重复、时延、乱序以及连接失效等问题。</p>
<p>通常我们在可靠性好，传输时延小的局域网上开发测试，一些问题不容易暴露，但在大型互联网上却会出现错误。</p>
<p>UDP协议把递送的可靠性责任推到了上层即应用层，下面简单编写了几个类来专门处理两个问题：乱序和丢包。</p>
<p>四个类：DataPacket 类，PacketHeader类，PacketBody类 ，DataEntry类，位于同一个文件DataPacket .java中。</p>
<p>DataPacket 类相当于一个门面模式，提供给外部使用，通信数据也在这个类中处理。</p>
<p>package com.skysoft.pcks;</p>
<p>import java.io.*;<br>import java.net.*;<br>import java.util.*;</p>
<p>public class&nbsp; DataPacket {<br>&nbsp; InputStream is;<br>&nbsp; OutputStream os;<br>&nbsp; PacketHeader header;<br>&nbsp; PacketBody body;<br>&nbsp; ArrayList al;<br>&nbsp; public static final int DataSwapSize = 64532;</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 在接收数据报使用<br>&nbsp;&nbsp; */<br>&nbsp; public DataPacket() {<br>&nbsp;&nbsp;&nbsp; header = new PacketHeader();<br>&nbsp;&nbsp;&nbsp; body = new PacketBody();<br>&nbsp;&nbsp;&nbsp; al = new ArrayList();<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 在发送数据报时使用,它调用报文分割操作.<br>&nbsp;&nbsp; * @param file String&nbsp; 硬盘文件<br>&nbsp;&nbsp; */<br>&nbsp; public DataPacket(String file) {<br>&nbsp;&nbsp;&nbsp; this();<br>&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is = new FileInputStream(file);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header.CalcHeaderInfo(is.available());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.madeBody();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //this.Gereratedata();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch (FileNotFoundException ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch (IOException ex1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex1.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 在发送数据报时使用,它调用报文分割操作.<br>&nbsp;&nbsp; * @param url URL url地址<br>&nbsp;&nbsp; */<br>&nbsp; public DataPacket(URL url) {<br>&nbsp;&nbsp;&nbsp; this();<br>&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //is = url.openStream();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; URLConnection conn=url.openConnection();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is=conn.getInputStream();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int total=conn.getContentLength();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header.CalcHeaderInfo(total);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.madeBody();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.println(total+":"+total);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.close();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch (IOException ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 为发送构造分组,使用PackageHeader处理了报头格式,并为分组编序号.<br>&nbsp;&nbsp; */<br>&nbsp; private void madeBody() {<br>&nbsp;&nbsp;&nbsp; al.clear();<br>&nbsp;&nbsp;&nbsp; byte[] buffer;<br>&nbsp;&nbsp;&nbsp; DataEntry de;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; header.fragmentcounter; i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ByteArrayOutputStream bos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //is.skip(i * body.BODY_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header.ArrageSort(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = new DataEntry(PacketBody.BODY_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.setSn(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.setStreamsize(header.getStreamsize());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.setFragmentcounter(header.getFragmentcounter());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.isWTailFragment(i)) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer = new byte[header.getMinfragment()];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.read(buffer, 0, buffer.length);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header.setActByteSize(header.getMinfragment());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.setActByteSize(header.getMinfragment());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer = new byte[body.BODY_BUFFER_SIZE];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.read(buffer, 0, buffer.length);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.println("length-------"+i+" "+body.getBody().length+" "+header.getMinfragment());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; body.setBody(buffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.println("length:" + i + " " + header.toString());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(header.getByte(), 0, header.HEADER_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(body.getBody(), 0, body.getBody().length);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.setBytes(bos.toByteArray());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; al.add(de);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (IOException ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 为发送构造分组,没有考虑报头格式,也没有为分组编序号.<br>&nbsp;&nbsp; */<br>&nbsp; private void madeBody1() {<br>&nbsp;&nbsp;&nbsp; al.clear();<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; header.fragmentcounter; i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.isWTailFragment(i))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.read(body.getBody(), i * body.BODY_BUFFER_SIZE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header.getMinfragment());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; is.read(body.getBody(), i * body.BODY_BUFFER_SIZE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; body.BODY_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ByteArrayOutputStream bos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(header.getByte(), 0, header.HEADER_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(body.getBody(), header.HEADER_BUFFER_SIZE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; body.getBody().length);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; al.add(bos);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (IOException ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 在接收到报文后,对此报文执行组装,并处理报文丢失和乱序情况.<br>&nbsp;&nbsp; * @param b1 byte[]<br>&nbsp;&nbsp; */<br>&nbsp; public void Add(byte[] b1) {<br>&nbsp;&nbsp;&nbsp; byte[] buffer = (byte[]) b1.clone();<br>&nbsp;&nbsp;&nbsp; handlerText(buffer);<br>&nbsp;&nbsp;&nbsp; DataEntry de = new DataEntry(buffer, header.getActByteSize());<br>&nbsp;&nbsp;&nbsp; de.setSn(header.getSn());<br>&nbsp;&nbsp;&nbsp; de.setStreamsize(header.getStreamsize());<br>&nbsp;&nbsp;&nbsp; de.setFragmentcounter(header.getFragmentcounter());<br>&nbsp;&nbsp;&nbsp; al.add(de);<br>&nbsp; }<br>&nbsp; private void handlerText(byte[] buffer) {<br>&nbsp;&nbsp;&nbsp; ByteArrayOutputStream baos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp; baos.write(buffer, 0, header.HEADER_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp; byte[] b=new byte[header.HEADER_BUFFER_SIZE];<br>&nbsp;&nbsp;&nbsp; System.arraycopy(buffer,0,b,0,b.length);<br>&nbsp;&nbsp;&nbsp; ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());<br>&nbsp;&nbsp;&nbsp; InputStreamReader isr = new InputStreamReader(bais);<br>&nbsp;&nbsp;&nbsp; BufferedReader br = new BufferedReader(isr);<br>&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header = new PacketHeader(br.readLine());<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch (Exception ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; <br>&nbsp; private String calFileSize(int size) {<br>&nbsp;&nbsp;&nbsp; return size / 1024 + "K";<br>&nbsp; }</p>
<p>&nbsp; public ArrayList getDataPackets() {<br>&nbsp;&nbsp;&nbsp; return al;<br>&nbsp; }<br>/**<br>&nbsp;* 是否接收完毕,通过序号是否等于最大段数来判断,这也许有问题,比如,正好是最后一个段丢失了,这样<br>&nbsp;* 这个包整个就丢失了.<br>&nbsp;* @return<br>&nbsp;*/<br>&nbsp; public boolean isFull() {<br>&nbsp;&nbsp;&nbsp; return this.header.getSn() == this.header.getFragmentcounter() - 1 ? true : false;<br>&nbsp; }<br>/**<br>&nbsp;* 判断是否只有一个段.<br>&nbsp;* @return<br>&nbsp;*/<br>&nbsp; public boolean isZero() {<br>&nbsp;&nbsp;&nbsp; return this.header.getSn() == 0 ? true : false;<br>&nbsp; }<br>/**<br>&nbsp;* 该函数执行报文组装,不考虑丢失的报文.<br>&nbsp;* @return<br>&nbsp;*/<br>&nbsp; private ByteArrayOutputStream fetchDataPackets() {<br>&nbsp;&nbsp;&nbsp; ByteArrayOutputStream bos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp; byte[] buffer = null;<br>&nbsp;&nbsp;&nbsp; DataEntry de;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; al.size(); i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = this.getSnData(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer = de.getByte();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.getStreamsize() == de.getStreamsize()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(de.getByte(), header.HEADER_BUFFER_SIZE, de.getActByteSize());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(de.toString() + " -- fetchDataPackets");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (Exception ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return bos;<br>&nbsp; }</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 该函数执行报文组装,对于丢失的报文,写入空报文.<br>&nbsp;&nbsp; * @return ByteArrayOutputStream<br>&nbsp;&nbsp; */<br>&nbsp; private ByteArrayOutputStream fetchDataPackets_sn() {<br>&nbsp;&nbsp;&nbsp; ByteArrayOutputStream bos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp; byte[] buffer;<br>&nbsp;&nbsp;&nbsp; DataEntry de;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; header.getFragmentcounter(); i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = this.getSnData(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (de == null) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = seachDeData(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer = de.getByte();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.println(de.getSn() + ":" + i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //handlerText(buffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //bos.write(buffer, header.HEADER_BUFFER_SIZE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer.length - header.HEADER_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.getStreamsize() == de.getStreamsize()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(de.getByte(), header.HEADER_BUFFER_SIZE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de.getActByteSize());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //System.out.println(de.toString());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (Exception ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return bos;<br>&nbsp; }</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 对缓冲的数据包进行排序处理,即按顺序提取同一帧的数据,如果没有找到该序号的帧,则返回空值.<br>&nbsp;&nbsp; * @param sn int 要找的帧序号.<br>&nbsp;&nbsp; * @return DataEntry<br>&nbsp;&nbsp; */<br>&nbsp; private DataEntry getSnData(int sn) {<br>&nbsp;&nbsp;&nbsp; DataEntry de = null;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; al.size(); i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = (DataEntry) al.get(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.getStreamsize() == de.getStreamsize()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (sn == de.getSn())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = null;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return de;<br>&nbsp; }</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 按序号开始向前或者是向后寻找最近的帧片段,日后可以增加请求重发功能,通过开一个通信连接.<br>&nbsp;&nbsp; * @param sn int<br>&nbsp;&nbsp; * @return DataEntry<br>&nbsp;&nbsp; */<br>&nbsp; private DataEntry seachDeData(int sn) {<br>&nbsp;&nbsp;&nbsp; DataEntry de = null;<br>&nbsp;&nbsp;&nbsp; int init, min = 10000;<br>&nbsp;&nbsp;&nbsp; DataEntry back, fore = null;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; al.size(); i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = (DataEntry) al.get(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.getStreamsize() == de.getStreamsize()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init = Math.abs(de.getSn() - sn);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (de.getFragmentcounter() != de.getSn() &amp;&amp; init &lt; min) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; min = init;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fore = de;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return fore;<br>&nbsp; }</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 除去最后一帧外,随机抽取一帧.<br>&nbsp;&nbsp; * @return DataEntry<br>&nbsp;&nbsp; */<br>&nbsp; private DataEntry seachDeData() {<br>&nbsp;&nbsp;&nbsp; DataEntry de = null;<br>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; al.size(); i++) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de = (DataEntry) al.get(i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("sky ::::" + de.getFragmentcounter() + ":" + de.getSn() +<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ":" + i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header.getStreamsize() == de.getStreamsize()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (de.getFragmentcounter() != de.getSn()) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return de;<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 生成组装完的结果数据.因为用图像来做测试,所以令其返回图像.<br>&nbsp;&nbsp; * @return Image<br>&nbsp;&nbsp; */<br>&nbsp; public java.awt.Image Gereratedata() {<br>&nbsp;&nbsp;&nbsp;&nbsp; ByteArrayInputStream bis;<br>&nbsp;&nbsp;&nbsp;&nbsp; java.awt.image.BufferedImage bimage = null;<br>&nbsp;&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; byte[] b = fetchDataPackets_sn().toByteArray(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //fetchDataPackets_old1()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bis = new ByteArrayInputStream(b);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bimage = javax.imageio.ImageIO.read(bis);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; catch (Exception ex1) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex1.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; return bimage;<br>&nbsp; }</p>
<p>&nbsp; public static void main(String args[]) {<br>&nbsp;&nbsp;&nbsp; DataPacket dp = new DataPacket("e:\\nature\\14.jpg");<br>&nbsp; }<br>}<br>/**<br>&nbsp;* 数据实体,充当临时处理场所.<br>&nbsp;* @author Administrator<br>&nbsp;*<br>&nbsp;*/<br>class DataEntry {<br>&nbsp; byte[] bytes;<br>&nbsp; int fragmentcounter, sn, actbytesize;<br>&nbsp; long streamsize;<br>&nbsp; int minfragment;</p>
<p>&nbsp; public DataEntry() {</p>
<p>&nbsp; }</p>
<p>&nbsp; public DataEntry(int size) {<br>&nbsp;&nbsp;&nbsp; this.actbytesize = size;<br>&nbsp; }</p>
<p>&nbsp; public DataEntry(byte[] b, int i) {<br>&nbsp;&nbsp;&nbsp; this.bytes = b;<br>&nbsp;&nbsp;&nbsp; this.actbytesize = i;<br>&nbsp; }</p>
<p>&nbsp; public byte[] getByte() {<br>&nbsp;&nbsp;&nbsp; return this.bytes;<br>&nbsp; }</p>
<p>&nbsp; public void setBytes(byte[] b) {<br>&nbsp;&nbsp;&nbsp; this.bytes = b;<br>&nbsp; }</p>
<p>&nbsp; public void setStreamsize(long size) {<br>&nbsp;&nbsp;&nbsp; this.streamsize = size;<br>&nbsp; }</p>
<p>&nbsp; public long getStreamsize() {<br>&nbsp;&nbsp;&nbsp; return this.streamsize;<br>&nbsp; }</p>
<p>&nbsp; public int getMinfragment() {<br>&nbsp;&nbsp;&nbsp; return minfragment;<br>&nbsp; }</p>
<p>&nbsp; public synchronized void setSn(int i) {<br>&nbsp;&nbsp;&nbsp; this.sn = i;<br>&nbsp; }</p>
<p>&nbsp; public synchronized int getSn() {<br>&nbsp;&nbsp;&nbsp; return sn;<br>&nbsp; }</p>
<p>&nbsp; public synchronized int getFragmentcounter() {<br>&nbsp;&nbsp;&nbsp; return fragmentcounter;<br>&nbsp; }</p>
<p>&nbsp; public synchronized void setFragmentcounter(int c) {<br>&nbsp;&nbsp;&nbsp; this.fragmentcounter = c;<br>&nbsp; }</p>
<p>&nbsp; public void setActByteSize(int size) {<br>&nbsp;&nbsp;&nbsp; actbytesize = size;<br>&nbsp; }</p>
<p>&nbsp; public int getActByteSize() {<br>&nbsp;&nbsp;&nbsp; return actbytesize;<br>&nbsp; }</p>
<p>&nbsp; public String toString() {<br>&nbsp;&nbsp;&nbsp; return this.streamsize + "::" + this.fragmentcounter + "::" + this.sn +<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "::" + this.actbytesize + " recv DataEntry";<br>&nbsp; }<br>}<br>/**<br>&nbsp;* 报头,处理报头格式<br>&nbsp;* @author Administrator<br>&nbsp;*<br>&nbsp;*/<br>class PacketHeader implements Serializable{<br>&nbsp; public static final int HEADER_BUFFER_SIZE = 1024;<br>&nbsp; int fragmentcounter, sn;<br>&nbsp; int actbytesize = PacketBody.BODY_BUFFER_SIZE;<br>&nbsp; byte[] header; //= new byte[HEADER_BUFFER_SIZE];<br>&nbsp; long streamsize;<br>&nbsp; int minfragment;</p>
<p>&nbsp; public PacketHeader() {</p>
<p>&nbsp; }</p>
<p>&nbsp; public PacketHeader(long l) {<br>&nbsp;&nbsp;&nbsp; this.setStreamsize(l);</p>
<p>&nbsp; }</p>
<p>&nbsp; public PacketHeader(String s) {<br>&nbsp;&nbsp;&nbsp; String[] tm = s.split("::");<br>&nbsp;&nbsp;&nbsp; this.setActByteSize(Integer.parseInt(tm[3]));<br>&nbsp;&nbsp;&nbsp; this.setSn(Integer.parseInt(tm[2]));<br>&nbsp;&nbsp;&nbsp; this.setFragmentcounter(Integer.parseInt(tm[1]));<br>&nbsp;&nbsp;&nbsp; this.setStreamsize(Long.parseLong(tm[0]));<br>&nbsp; }</p>
<p>&nbsp; /**<br>&nbsp;&nbsp; * 根据文件的段的顺序生成数据头.<br>&nbsp;&nbsp; * @param sn 文件序列<br>&nbsp;&nbsp; */<br>&nbsp; public void ArrageSort(int sn) {<br>&nbsp;&nbsp;&nbsp; this.setSn(sn);<br>&nbsp;&nbsp;&nbsp; this.setByte();<br>&nbsp; }</p>
<p>&nbsp; public void CalcHeaderInfo(long l) {<br>&nbsp;&nbsp;&nbsp; this.setStreamsize(l);<br>&nbsp;&nbsp;&nbsp; CalcHeaderInfo();<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 计算流要被分成的片段数量,并得出最小片段余量.<br>&nbsp;&nbsp; */<br>&nbsp; public void CalcHeaderInfo() {<br>&nbsp;&nbsp;&nbsp; fragmentcounter = Math.round( (float) streamsize /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PacketBody.BODY_BUFFER_SIZE);<br>&nbsp;&nbsp;&nbsp; float critical = (float) streamsize / PacketBody.BODY_BUFFER_SIZE;<br>&nbsp;&nbsp;&nbsp; if (critical - fragmentcounter &lt; 0.5 &amp;&amp; critical - fragmentcounter &gt; 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fragmentcounter++;<br>&nbsp;&nbsp;&nbsp; minfragment = (int) (streamsize % PacketBody.BODY_BUFFER_SIZE);<br>&nbsp; }</p>
<p>&nbsp; public byte[] getHeader() {<br>&nbsp;&nbsp;&nbsp; Long it = new Long(this.streamsize);<br>&nbsp;&nbsp;&nbsp; return new byte[] {it.byte()};<br>&nbsp; }</p>
<p>&nbsp; public byte[] getByte() {<br>&nbsp;&nbsp;&nbsp; return header; //this.toString().getBytes();<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 生成报头字节,首先取得数据包头 流尺寸::段片数::段顺序::段实际尺寸 的字节形式,<br>&nbsp;&nbsp; * 然后加入回车换行符号,对于1024字节中剩余的部分一律写入元素为0的字节数组.<br>&nbsp;&nbsp; */<br>&nbsp; public void setByte() {<br>&nbsp;&nbsp;&nbsp; ByteArrayOutputStream bos = new ByteArrayOutputStream();<br>&nbsp;&nbsp;&nbsp; byte[] buffer = this.toByte();<br>&nbsp;&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(buffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write("\r\n".getBytes());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bos.write(new byte[PacketHeader.HEADER_BUFFER_SIZE - buffer.length], 0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PacketHeader.HEADER_BUFFER_SIZE - buffer.length);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; header = bos.toByteArray();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch (IOException ex) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ex.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }</p>
<p>&nbsp; public void setStreamsize(long size) {<br>&nbsp;&nbsp;&nbsp; this.streamsize = size;<br>&nbsp; }</p>
<p>&nbsp; public long getStreamsize() {<br>&nbsp;&nbsp;&nbsp; return this.streamsize;<br>&nbsp; }</p>
<p>&nbsp; public int getMinfragment() {<br>&nbsp;&nbsp;&nbsp; return minfragment;<br>&nbsp; }</p>
<p>&nbsp; public synchronized void setSn(int i) {<br>&nbsp;&nbsp;&nbsp; this.sn = i;<br>&nbsp; }</p>
<p>&nbsp; public int getSn() {<br>&nbsp;&nbsp;&nbsp; return sn;<br>&nbsp; }</p>
<p>&nbsp; public int getFragmentcounter() {<br>&nbsp;&nbsp;&nbsp; return fragmentcounter;<br>&nbsp; }</p>
<p>&nbsp; public synchronized void setFragmentcounter(int c) {<br>&nbsp;&nbsp;&nbsp; this.fragmentcounter = c;<br>&nbsp; }</p>
<p>&nbsp; public void setActByteSize(int size) {<br>&nbsp;&nbsp;&nbsp; actbytesize = size;<br>&nbsp;&nbsp;&nbsp; setByte();<br>&nbsp; }</p>
<p>&nbsp; public int getActByteSize() {<br>&nbsp;&nbsp;&nbsp; return actbytesize;<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 数据包头的格式为:流尺寸::段片数::段顺序::段实际尺寸<br>&nbsp;&nbsp; * 报头字节长度是可变化的,比如,可以加入流的具体信息如:流所属文件的名称,文件类型以及一些其他信息.<br>&nbsp;&nbsp; * @return String<br>&nbsp;&nbsp; */<br>&nbsp; public String toString() {<br>&nbsp;&nbsp;&nbsp; return streamsize + "::" + this.fragmentcounter + "::" + this.getSn() +<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "::" + this.getActByteSize();<br>&nbsp; }</p>
<p>&nbsp; public byte[] toByte() {<br>&nbsp;&nbsp;&nbsp; return this.toString().getBytes();<br>&nbsp; }<br>&nbsp; /**<br>&nbsp;&nbsp; * 是否为尾段<br>&nbsp;&nbsp; * @param i int<br>&nbsp;&nbsp; * @return boolean<br>&nbsp;&nbsp; */<br>&nbsp; public boolean isWTailFragment(int i) {<br>&nbsp;&nbsp;&nbsp; return (i == fragmentcounter - 1) ? true : false;<br>&nbsp; }</p>
<p>}<br>/**<br>&nbsp;* 用户数据区<br>&nbsp;* @author Administrator<br>&nbsp;*<br>&nbsp;*/<br>class PacketBody implements Serializable{<br>&nbsp; public static final int BODY_BUFFER_SIZE = 63508; //65508<br>&nbsp; byte[] body;</p>
<p>&nbsp; public PacketBody() {<br>&nbsp; }</p>
<p>&nbsp; public void setBody(byte[] b) {<br>&nbsp;&nbsp;&nbsp; this.body = b;<br>&nbsp; }</p>
<p>&nbsp; public byte[] getBody() {<br>&nbsp;&nbsp;&nbsp; return body;<br>&nbsp; }<br>}</p>
<p>这个数据处理类，将在接下来使用。</p>
</span> <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94795.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:08 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94795.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 5.0 多线程编程实践</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94796.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:08:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94796.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94796.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94796.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94796.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94796.html</trackback:ping><description><![CDATA[Java5增加了新的类库并发集java.util.concurrent，该类库为并发程序提供了丰富的API多线程编程在Java
5中更加容易，灵活。本文通过一个网络服务器模型，来实践Java5的多线程编程，该模型中使用了Java5中的线程池，阻塞队列，可重入锁等，还实践了
Callable， Future等接口，并使用了Java 5的另外一个新特性泛型。<br><br>　　简介 <br><br>　　本文将实现一个网络服务器模型，一旦有客户端连接到该服务器，则启动一个新线程为该连接服务，服务内容为往客户端输送一些字符信息。一个典型的网络服务器模型如下：<br><br>　　1. 建立监听端口。<br><br>　　2. 发现有新连接，接受连接，启动线程，执行服务线程。 3. 服务完毕，关闭线程。<br><br>
这个模型在大部分情况下运行良好，但是需要频繁的处理用户请求而每次请求需要的服务又是简短的时候，系统会将大量的时间花费在线程的创建销毁。Java
5的线程池克服了这些缺点。通过对重用线程来执行多个任务，避免了频繁线程的创建与销毁开销，使得服务器的性能方面得到很大提高。因此，本文的网络服务器
模型将如下：<br><br>　　1. 建立监听端口，创建线程池。<br><br>　　2. 发现有新连接，使用线程池来执行服务任务。<br><br>　　3. 服务完毕，释放线程到线程池。<br><br>　　下面详细介绍如何使用Java 5的concurrent包提供的API来实现该服务器。<br><br>　　初始化<br><br>
初始化包括创建线程池以及初始化监听端口。创建线程池可以通过调用java.util.concurrent.Executors类里的静态方法
newChahedThreadPool或是newFixedThreadPool来创建，也可以通过新建一个
java.util.concurrent.ThreadPoolExecutor实例来执行任务。这里我们采用newFixedThreadPool方
法来建立线程池。<br><br>ExecutorService pool = Executors.newFixedThreadPool(10); <br><br>　　表示新建了一个线程池，线程池里面有10个线程为任务队列服务。<br><br>　　使用ServerSocket对象来初始化监听端口。<br><br>private static final int PORT = 19527;<br>serverListenSocket = new ServerSocket(PORT);<br>serverListenSocket.setReuseAddress(true);<br>serverListenSocket.setReuseAddress(true); <br><br>　　服务新连接<br><br>　　当有新连接建立时，accept返回时，将服务任务提交给线程池执行。<br><br>while(true){<br>　Socket socket = serverListenSocket.accept();<br>　pool.execute(new ServiceThread(socket));<br>} <br><br>　　这里使用线程池对象来执行线程，减少了每次线程创建和销毁的开销。任务执行完毕，线程释放到线程池。<br><br>　　服务任务<br><br>
服务线程ServiceThread维护一个count来记录服务线程被调用的次数。每当服务任务被调用一次时，count的值自增1，因此
ServiceThread提供一个increaseCount和getCount的方法，分别将count值自增1和取得该count值。由于可能多个
线程存在竞争，同时访问count，因此需要加锁机制，在Java 5之前，我们只能使用synchronized来锁定。Java
5中引入了性能更加粒度更细的重入锁ReentrantLock。我们使用ReentrantLock保证代码线程安全。下面是具体代码：<br><br>private static ReentrantLock lock = new ReentrantLock ();<br>private static int count = 0;<br>private int getCount(){<br>　int ret = 0;<br>　try{<br>　　lock.lock();<br>　　ret = count;<br>　}finally{<br>　　lock.unlock();<br>　}<br>　return ret;<br>} <br>private void increaseCount(){<br>　try{<br>　　lock.lock();<br>　　++count;<br>　}finally{<br>　　lock.unlock();<br>　}<br>} <br><br>　　服务线程在开始给客户端打印一个欢迎信息，<br><br>increaseCount();<br>int curCount = getCount();<br>helloString = "hello, id = " + curCount+"\r\n";<br>dos = new DataOutputStream(connectedSocket.getOutputStream());<br>dos.write(helloString.getBytes()); <br><br>
然后使用ExecutorService的submit方法提交一个Callable的任务，返回一个Future接口的引用。这种做法对费时的任务非
常有效，submit任务之后可以继续执行下面的代码，然后在适当的位置可以使用Future的get方法来获取结果，如果这时候该方法已经执行完毕，则
无需等待即可获得结果，如果还在执行，则等待到运行完毕。<br><br>ExecutorService executor = Executors.newSingleThreadExecutor();<br>Future <string> future = executor.submit(new TimeConsumingTask());<br>dos.write("let's do soemthing other".getBytes());<br>String result = future.get();<br>dos.write(result.getBytes()); <br><br>　　其中TimeConsumingTask实现了Callable接口<br><br>class TimeConsumingTask implements Callable <string>{<br>　public String call() throws Exception {<br>　　System.out.println("It's a time-consuming task, you'd better retrieve your result in the furture");<br>　　return "ok, here's the result: It takes me lots of time to produce this result";<br>　}<br>} <br><br>
这里使用了Java
5的另外一个新特性泛型，声明TimeConsumingTask的时候使用了String做为类型参数。必须实现Callable接口的call函数，
其作用类似与Runnable中的run函数，在call函数里写入要执行的代码，其返回值类型等同于在类声明中传入的类型值。在这段程序中，我们提交了
一个Callable的任务，然后程序不会堵塞，而是继续执行dos.write("let's do soemthing
other".getBytes());当程序执行到String result =
future.get()时如果call函数已经执行完毕，则取得返回值，如果还在执行，则等待其执行完毕。
<br><span style="font-weight: bold;"><br></span></string></string>
<p>&nbsp;</p>
服务器端的完整实现<br><br>　　服务器端的完整实现代码如下：<br><br>package com.andrew;<br><br>import java.io.DataOutputStream;<br>import java.io.IOException;<br>import java.io.Serializable;<br>import java.net.ServerSocket;<br>import java.net.Socket;<br>import java.util.concurrent.ArrayBlockingQueue;<br>import java.util.concurrent.BlockingQueue;<br>import java.util.concurrent.Callable;<br>import java.util.concurrent.ExecutionException;<br>import java.util.concurrent.ExecutorService;<br>import java.util.concurrent.Executors;<br>import java.util.concurrent.Future;<br>import java.util.concurrent.RejectedExecutionHandler;<br>import java.util.concurrent.ThreadPoolExecutor;<br>import java.util.concurrent.TimeUnit;<br>import java.util.concurrent.locks.ReentrantLock;<br><br>public class Server {<br>　private static int produceTaskSleepTime = 100;<br>　private static int consuskSleepTime = 1200;<br>　private static int produceTaskMaxNumber = 100;<br>　private static final int CORE_POOL_SIZE = 2;<br>　private static final int MAX_POOL_SIZE = 100;<br>　private static final int KEEPALIVE_TIME = 3;<br>　private static final int QUEUE_CAPACITY = (CORE_POOL_SIZE + MAX_POOL_SIZE) / 2;<br>　private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;<br>　private static final String HOST = "127.0.0.1";<br>　private static final int PORT = 19527;<br>　private BlockingQueue workQueue = new ArrayBlockingQueue(QUEUE_CAPACITY);<br>　//private ThreadPoolExecutor serverThreadPool = null;<br>　private ExecutorService pool = null;<br>　private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.DiscardOldestPolicy();<br>　private ServerSocket serverListenSocket = null;<br>　private int times = 5;<br>　public void start() {<br>　　// You can also init thread pool in this way.<br>　　/*serverThreadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,<br>　　MAX_POOL_SIZE, KEEPALIVE_TIME, TIME_UNIT, workQueue,<br>　　rejectedExecutionHandler);*/<br>　　pool = Executors.newFixedThreadPool(10);<br>　　try {<br>　　　serverListenSocket = new ServerSocket(PORT);<br>　　　serverListenSocket.setReuseAddress(true);<br><br>　　　System.out.println("I'm listening");<br>　　　while (times-- &gt; 0) {<br>　　　　Socket socket = serverListenSocket.accept();<br>　　　　String welcomeString = "hello";<br>　　　　//serverThreadPool.execute(new ServiceThread(socket, welcomeString));<br>　　　　pool.execute(new ServiceThread(socket));<br>　　　}<br>　　} catch (IOException e) {<br>　　　// TODO Auto-generated catch block<br>　　　e.printStackTrace();<br>　　}<br>　　cleanup();<br>　}<br><br>　public void cleanup() {<br>　　if (null != serverListenSocket) {<br>　　　try {<br>　　　　serverListenSocket.close();<br>　　　} catch (IOException e) {<br>　　　　// TODO Auto-generated catch block<br>　　　　e.printStackTrace();<br>　　　}<br>　　}<br>　　//serverThreadPool.shutdown();<br>　　pool.shutdown();<br>　}<br><br>　public static void main(String args[]) {<br>　　Server server = new Server();<br>　　server.start();<br>　}<br>}<br><br>class ServiceThread implements Runnable, Serializable {<br>　private static final long serialVersionUID = 0;<br>　private Socket connectedSocket = null;<br>　private String helloString = null;<br>　private static int count = 0;<br>　private static ReentrantLock lock = new ReentrantLock();<br><br>　ServiceThread(Socket socket) {<br>　　connectedSocket = socket;<br>　}<br><br>　public void run() {<br>　　increaseCount();<br>　　int curCount = getCount();<br>　　helloString = "hello, id = " + curCount + "\r\n";<br><br>　　ExecutorService executor = Executors.newSingleThreadExecutor();<br>　　Future future = executor.submit(new TimeConsumingTask());<br><br>　　DataOutputStream dos = null;<br>　　try {<br>　　　dos = new DataOutputStream(connectedSocket.getOutputStream());<br>　　　dos.write(helloString.getBytes());<br>　　　try {<br>　　　　dos.write("let's do soemthing other.\r\n".getBytes());<br>　　　　String result = future.get();<br>　　　　dos.write(result.getBytes());<br>　　　} catch (InterruptedException e) {<br>　　　　e.printStackTrace();<br>　　　} catch (ExecutionException e) {<br>　　　　e.printStackTrace();<br>　　　}<br>　　} catch (IOException e) {<br>　　　// TODO Auto-generated catch block<br>　　　e.printStackTrace();<br>　　} finally {<br>　　　if (null != connectedSocket) {<br>　　　　try {<br>　　　　　connectedSocket.close();<br>　　　　} catch (IOException e) {<br>　　　　　// TODO Auto-generated catch block<br>　　　　　e.printStackTrace();<br>　　　　}<br>　　　}<br>　　　if (null != dos) {<br>　　　　try {<br>　　　　　dos.close();<br>　　　　} catch (IOException e) {<br>　　　　　// TODO Auto-generated catch block<br>　　　　　e.printStackTrace();<br>　　　　}<br>　　　}<br>　　　executor.shutdown();<br>　　}<br>　}<br><br>　private int getCount() {<br>　　int ret = 0;<br>　　try {<br>　　　lock.lock();<br>　　　ret = count;<br>　　} finally {<br>　　　lock.unlock();<br>　　}<br>　　return ret;<br>　}<br><br>　private void increaseCount() {<br>　　try {<br>　　　lock.lock();<br>　　　++count;<br>　　} finally {<br>　　　lock.unlock();<br>　　}<br>　}<br>}<br><br>class TimeConsumingTask implements Callable {<br>　public String call() throws Exception {<br>　　System.out.println("It's a time-consuming task, you'd better retrieve your result in the furture");<br>　　return "ok, here's the result: It takes me lots of time to produce this result";<br>　}<br><br>} <br>　　运行程序<br><br>　　运行服务端，客户端只需使用telnet 127.0.0.1 19527 即可看到信息如下：  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94796.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:08 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94796.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java开发的邮件发送程序</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94792.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:07:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94792.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94792.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94792.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94792.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94792.html</trackback:ping><description><![CDATA[实
现的原理非常简单，首先建立和邮件服务器的Socket连接，然后进行和服务器握手，然后发送smtp指令，并封装邮件体，然后发送即可。大家不妨一试。<br><br><br>import java.net.*;<br>import java.io.*;<br>import java.util.*;<br><br>public class SMTPSender{<br><br>Socket socket=null;<br>PrintWriter outData=null;<br>BufferedReader inData=null;<br><br>String smtpServer="";<br><br>String user="";<br>String pass="";<br>String from="";<br><br>String LINEFEED="\r\n";<br>boolean isNeedAuthLogin=false;<br>Vector to=new Vector();<br><br>public static void main(String[] args){<br>SMTPSender smtp=new SMTPSender();<br>smtp.setMailServer("mail.ehawa.com");<br>smtp.setMailFrom("root@ehawa.com","???","???");<br>smtp.addMailTo("root@ehawa.com");<br>if(smtp.send("hello","这是一个测试!")){<br>System.out.println("邮件发送成功！"); <br>}else System.out.println("邮件发送失败！"); <br>}<br>public void setMailServer(String s){<br>smtpServer=s;<br>}<br>public void setMailFrom(String s,String uid,String pwd){<br>this.from=s;<br>this.user=uid;<br>this.pass=pwd;<br>this.isNeedAuthLogin=(this.user!=null&amp;&amp;this.pass!=null&amp;&amp;!this.user.equals("")&amp;&amp;!this.pass.equals(""));<br>}<br>public boolean addMailTo(String mailAddr){<br>to.addElement(mailAddr);<br>return true;<br>}<br>public boolean send(String subject,String content){<br>try{<br>if(smtpServer==null||smtpServer.equals(""))return false;<br>if(from==null||from.equals(""))return false;<br>if(to.size()&lt;1)return false;<br>socket=new Socket(smtpServer,25);<br>outData=new PrintWriter(socket.getOutputStream());<br>inData=new BufferedReader(new InputStreamReader(socket.getInputStream()));<br>//与邮件服务器连接成功<br>readResponse("220");<br>//HELO host<br>sendRequest("HELO "+smtpServer+LINEFEED);<br>readResponse("250");<br>if(isNeedAuthLogin){<br>//AUTH LOGIN<br>sendRequest("AUTH LOGIN"+LINEFEED);<br>readResponse("334");<br>//USERNAME:<br>sendRequest(new String(Base64.encodeString(user))+LINEFEED);<br>readResponse("334");<br>//PASSWORD:<br>sendRequest(new String(Base64.encodeString(pass))+LINEFEED);<br>readResponse("235");<br>}<br>//MAIL FROM:&lt;..&gt;<br>sendRequest("MAIL FROM:&lt;"+from+"&gt;"+LINEFEED);<br>readResponse("250");<br>//RCPT TO:&lt;..&gt;<br>for(Enumeration enu=to.elements();enu.hasMoreElements();){<br>String to1=(String)enu.nextElement();<br>sendRequest("RCPT To:&lt;"+to1+"&gt;"+LINEFEED);<br>readResponse("250");<br>}<br>//DATA<br>sendRequest("DATA"+LINEFEED);<br>readResponse("354");<br>//邮件内容<br>StringBuffer s1=new StringBuffer("From: &lt;"+from+"&gt;"+LINEFEED);<br>s1.append("To: &lt;"+to+"&gt;"+LINEFEED);<br>s1.append("Subject: "+subject+LINEFEED);<br>s1.append("Date: "+new java.util.Date().toLocaleString()+LINEFEED);<br>s1.append("Content-Type: text/plain;charset=\"GB2312\""+LINEFEED);<br>s1.append(LINEFEED);<br>s1.append(content);<br>s1.append(LINEFEED+"."+LINEFEED);//发送<br>sendRequest(s1.toString());<br>readResponse("250");<br>//QUIT退出<br>sendRequest("QUIT"+LINEFEED);<br>readResponse("221");<br>try{<br>inData.close();<br>inData=null;<br>}catch(Exception ex){}<br>try{<br>outData.close();<br>outData=null;<br>}catch(Exception ex){}<br>try{<br>socket.close();<br>socket=null;<br>}catch(Exception ex){}<br>}catch(Exception e){<br>return false;<br>//e.printStackTrace();<br>}<br>return true;<br>}<br>private void readResponse(String cmd)throws Exception{<br>String tmp=inData.readLine();<br>if(tmp.startsWith(cmd));//System.out.println(" [S:]"+tmp);<br>else throw new Exception("##########邮件发送失败!##########"+tmp);<br>while(tmp.startsWith(cmd+"-"))tmp=inData.readLine();<br>}<br>private void sendRequest(String msg){<br>//System.out.print("***[C:]"+msg);<br>outData.write(msg);<br>outData.flush();<br>}<br>public void close(){<br>try{<br>inData.close();<br>inData=null;<br>}catch(Exception ex){}<br>try{<br>outData.close();<br>outData=null;<br>}catch(Exception ex){}<br>try{<br>socket.close();<br>socket=null;<br>}catch(Exception ex){}<br>}<br>}  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94792.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:07 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94792.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA解析网络数据流</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94793.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:07:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94793.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94793.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94793.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94793.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94793.html</trackback:ping><description><![CDATA[UTF8转换成GB2312
当我们在基于HTTP协议的JSP或Servlet的应用中获取数据或发送请求时，JVM会把输送的数据编码成UTF8格式。如果我们直接从HTTP流中
提取中文数据，提取的结果为&#8220;????&#8221;（可能更多问号），为转换成我们能够理解的中文字符，我们需要把UTF8转换成GB2312，借助ISO-
8859-1标准编码能够轻易的实现，下面的代码实现了这一功能：
<p>byte [] b;</p>
<p>String utf8_; </p>
<p>utf8_ = request.getParameter("NAME");//从HTTP流中取"NAME"的UTF8数据</p>
<p>b = utf8_.getBytes("8859_1"); //中间用ISO-8859-1过渡</p>
<p>String name = new String(b, "GB2312"); //转换成GB2312字符</p>
<p>在
知道流长度的情况下将输入流转换成字节数组 Java中的输入流抽象类InputStream有int read(byte[] b, int
off, int len)方法，参数中byte[] b是用来存放从InputStream中读取的数据,int
off指定数组b的偏移地址，也就是数组b的起始下标，int
len指定需要读取的长度，方法返回实际读取的字节数。刚学Java的朋友可能要说：先定义一个与流长度等长的字节数组，调用read方法，指定起始下标
为0，指定读取长度与数组长度等长，不是一下子可以读出来了吗？说的没错，笔者曾经也试着这样读取数据，但后来发现在读取网络数据时很不安全，我们想想在
网络上获取数据可能并没那么流畅，数据流的传送可能会断断续续，所以并不能保证一次就能读取全部数据，特别是在读取大容量数据时更是如此，所以我们必须在
读取数据时检测实际读到的长度，如果没有读完已知长度的数据就应该再次读取，以此循环检测，直到实际读取的长度累加与已知的长度相等，下面的代码实现了这
一功能：</p>
<p>ServletInputStream inStream = request.getInputStream(); //取HTTP请求流</p>
<p>int size = request.getContentLength(); //取HTTP请求流长度</p>
<p>byte[] buffer = new byte[size]; //用于缓存每次读取的数据</p>
<p>byte[] in_b = new byte[size]; //用于存放结果的数组</p>
<p>int count = 0;</p>
<p>int rbyte = 0;</p>
<p>while (count &lt; size) { //循环读取</p>
<p>rbyte = inStream.read(buffer); //每次实际读取长度存于rbyte中</p>
<p>for(int i=0;i&lt;rbyte;i++) {</p>
<p>in_b[count + i] = buffer[i];</p>
<p>}</p>
<p>count += rbyte;</p>
<p>}</p>
<p>在
不知道流长度的情况下将输入流转换成字节数组
前面介绍了已知流长度的情况下的转换方法，那么当我们不知道流有多长时，也就是说不能确定转换后的字节数组有多大时，该怎么处理呢？笔者查看了JDK文档
之后发现ByteArrayOutputStream有一个byte[]
toByteArray()方法，该方法会自动创建一个字节数组，然后返回。于是就巧妙的用ByteArrayOutputStream来作中间过渡实现
转换，其它处理跟上面所介绍已知长度的情况差不多。假设需要被转换的流已经放在inStream里了，我们可以用如下的代码实现这一功能：</p>
<p>ByteArrayOutputStream swapStream = new ByteArrayOutputStream();</p>
<p>byte[] buff = new byte[100]; //buff用于存放循环读取的临时数据</p>
<p>int rc = 0;</p>
<p>while ((rc = inStream.read(buff, 0, 100)) &gt; 0) {</p>
<p>swapStream.write(buff, 0, rc);</p>
<p>}</p>
<p>byte[] in_b = swapStream.toByteArray(); //in_b为转换之后的结果 </p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94793.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:07 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94793.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用JavaNIO编写高性能服务器</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94790.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:03:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94790.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94790.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94790.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94790.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94790.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 从JDK 1.4开始，Java的标准库中就包含了NIO，即所谓的&#8220;NewIO&#8221;。其中最重要的功能就是提供了&#8220;非阻塞&#8221;的IO，当然包括了Socket。NonBlocking的IO就是对select(Unix平台下)以及WaitForMultipleObjects(Windows平台)的封装，提供了高性能、易伸缩的服务架构。说来惭愧，直到...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94790.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94790.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:03 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94790.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现非阻塞套接字的一种简单方法</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94789.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:02:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94789.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94789.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94789.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94789.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94789.html</trackback:ping><description><![CDATA[<p>阻塞，还是非阻塞？这就是问题所在。无论在程序员的头脑中多么高贵&#8230;&#8230;当然这不是莎士比亚，本文提出了任何程序员在编写 Internet 客户程序时都应该考虑的一个重要问题。通信操作应该是阻塞的还是非阻塞的？
</p>
<p>许多程序员在使用 Java 语言编写 Internet 客户程序时并没有考虑这个问题，主要是因为在以前只有一种选择――阻塞通信。但是现在
Java 程序员有了新的选择，因此我们编写的每个客户程序也许都应该考虑一下。 </p>
<p> 非阻塞通信在 Java 2 SDK 的 1.4 版被引入 Java 语言。如果您曾经使用该版本编过程序，可能会对新的 I/O 库（NIO）留下了印象。在引入它之前，非阻塞通信只有在实现第三方库的时候才能使用，而第三方库常常会给应用程序引入缺陷。
</p>
<p> NIO 库包含了文件、管道以及客户机和服务器套接字的非阻塞功能。库中缺少的一个特性是安全的非阻塞套接字连接。在 NIO 或者 JSSE 库中没有建立安全的非阻塞通道类，但这并不意味着不能使用安全的非阻塞通信。只不过稍微麻烦一点。
</p>
<p>要完全领会本文，您需要熟悉：</p>
<ul>
    <li>Java 套接字通信的概念。您也应该实际编写过应用程序。而且不只是打开连接、读取一行然后退出的简单应用程序，应该是实现 POP3 或
    HTTP 之类协议的客户机或通信库这样的程序。
    </li>
    <li>SSL 基本概念和加密之类的概念。基本上就是知道如何设置一个安全连接（但不必担心 JSSE ――这就是关于它的一个&#8220;紧急教程&#8221;）。
    </li>
    <li> NIO
    库。</li>
    <li>在您选择的平台上安装 Java 2 SDK 1.4
    或以后的版本。（我是在 Windows 98 上使用 1.4.1_01 版。）
    </li>
</ul>
<p>如果需要关于这些技术的介绍，请参阅
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#resources">参考资料</a>部分。
</p>
<p>那么到底什么是阻塞和非阻塞通信呢？
</p>
<p>
<a name="1">
<span class="atitle">阻塞和非阻塞通信</span>
</a>
</p>
<p>
阻塞通信意味着通信方法在尝试访问套接字或者读写数据时阻塞了对套接字的访问。在 JDK 1.4 之前，绕过阻塞限制的方法是无限制地使用线程，但这样常常会造成大量的线程开销，对系统的性能和可伸缩性产生影响。java.nio
包改变了这种状况，允许服务器有效地使用 I/O 流，在合理的时间内处理所服务的客户请求。 </p>
<p>没
有非阻塞通信，这个过程就像我所喜欢说的&#8220;为所欲为&#8221;那样。基本上，这个过程就是发送和读取任何能够发送/读取的东西。如果没有可以读取的东西，它就中止
读操作，做其他的事情直到能够读取为止。当发送数据时，该过程将试图发送所有的数据，但返回实际发送出的内容。可能是全部数据、部分数据或者根本没有发送
数据。
</p>
<p>阻塞与非阻塞相比确实有一些优点，特别是遇到错误控制问题的时候。在阻塞套接字通信中，如果出现错误，该访问会自动返回标志错误的代码。错误可能是由于网络超时、套接字关闭或者任何类型的
I/O 错误造成的。在非阻塞套接字通信中，该方法能够处理的唯一错误是网络超时。为了检测使用非阻塞通信的网络超时，需要编写稍微多一点的代码，以确定自从上一次收到数据以来已经多长时间了。
</p>
<p> 哪种方式更好取决于应用程序。如果使用的是同步通信，如果数据不必在读取任何数据之前处理的话，阻塞通信更好一些，而非阻塞通信则提供了处理任何已经读取的数据的机会。而异步通信，如
IRC 和聊天客户机则要求非阻塞通信以避免冻结套接字。 </p>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="2">
<span class="atitle">创建传统的非阻塞客户机套接字</span>
</a>
</p>
<p>
Java NIO 库使用通道而非流。通道可同时用于阻塞和非阻塞通信，但创建时默认为非阻塞版本。但是所有的非阻塞通信都要通过一个名字中包含
<code>Channel</code>
的类完成。在套接字通信中使用的类是
<code>SocketChannel，</code> 而创建该类的对象的过程不同于典型的套接字所用的过程，如清单
1 所示。
</p>
<br>
<a name="listing1">
<span style="font-weight: bold;">清单 1. 创建并连接 SocketChannel 对象</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>SocketChannel sc = SocketChannel.open();<br>sc.connect("www.ibm.com",80);<br>sc.finishConnect();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>必须声明一个
<code>SocketChannel</code>
类型的指针，但是不能使用
<code>new</code>
操作符创建对象。相反，必须调用
<code>SocketChannel</code>
类的一个静态方法打开通道。打开通道后，可以通过调用
<code>connect()</code>
方法与它连接。但是当该方法返回时，套接字不一定是连接的。为了确保套接字已经连接，必须接着调用
<code>finishConnect()</code> 。
</p>
<p>当套接字连接之后，非阻塞通信就可以开始使用
<code>SocketChannel</code>
类的
<code>read()</code> 和
<code>write()</code>
方法了。也可以把该对象强制转换成单独的
<code>ReadableByteChannel</code>
和
<code>WritableByteChannel</code>
对象。无论哪种方式，都要对数据使用
<code>Buffer</code>
对象。因为 NIO
库的使用超出了本文的范围，我们不再对此进一步讨论。
</p>
<p>当不再需要套接字时，可以使用
<code>close()</code> 方法将其关闭：
</p>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>sc.close();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>这样就会同时关闭套接字连接和底层的通信通道。
</p>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="3">
<span class="atitle">创建替代的非阻塞的客户机套接字</span>
</a>
</p>
<p>上述方法比传统的创建套接字连接的例程稍微麻烦一点。不过，传统的例程也能用于创建非阻塞套接字，不过需要增加几个步骤以支持非阻塞通信。
</p>
<p>
<code>SocketChannel</code> 对象中的底层通信包括两个
<code>Channel</code> 类：
<code>ReadableByteChannel</code>
和
<code>WritableByteChannel。</code> 这两个类可以分别从现有的
<code>InputStream</code>
和
<code>OutputStream</code><code> 阻塞流中使用
<code>Channels</code>
类的
<code>newChannel()</code> 方法创建，如清单 2 所示：
</code></p>
<br>
<a name="listing2">
<span style="font-weight: bold;">清单 2. 从流中派生通道</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());<br>WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>
<code>Channels</code> 类也用于把通道转换成流或者 reader 和 writer。这似乎是把通信切换到阻塞模式，但并非如此。如果试图读取从通道派生的流，读方法将抛出
<code>IllegalBlockingModeException </code> 异常。
</p>
<p>相反方向的转换也是如此。不能使用
<code>Channels</code> 类把流转换成通道而指望进行非阻塞通信。如果试图读从流派生的通道，读仍然是阻塞的。但是像编程中的许多事情一样，这一规则也有例外。
</p>
<p>这种例外适合于实现
<code>SelectableChannel</code>
抽象类的类。
<code>SelectableChannel</code>
和它的派生类能够选择使用阻塞或者非阻塞模式。
<code>SocketChannel</code>
就是这样的一个派生类。
</p>
<p>但是，为了能够在两者之间来回切换，接口必须作为
<code>SelectableChannel
</code> 实现。对于套接字而言，为了实现这种能力必须使用
<code>SocketChannel</code>
而不是
<code>Socket</code> 。
</p>
<p>回顾一下，要创建套接字，首先必须像通常使用
<code>Socket</code> 类那样创建一个套接字。套接字连接之后，使用
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#listing2">清单
2</a>中的两行代码把流转换成通道。
</p>
<br>
<a name="listing3">
<span style="font-weight: bold;">清单 3. 创建套接字的另一种方法</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>Socket s = new Socket("www.ibm.com", 80);<br>ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());<br>WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>如前所述，这样并不能实现非阻塞套接字通信――所有的通信仍然在阻塞模式下。在这种情况下，非阻塞通信必须模拟实现。模拟层不需要多少代码。让我们来看一看。
</p>
<p>
<a name="N10155">
<span class="smalltitle">从模拟层读数据</span>
</a>
</p>
<p>
模拟层在尝试读操作之前首先检查数据的可用性。如果数据可读则开始读。如果没有数据可用，可能是因为套接字被关闭，则返回表示这种情况的代码。在清单
4 中要注意仍然使用了
<code>ReadableByteChannel</code> 读，尽管
<code>InputStream</code>
完全可以执行这个动作。为什么这样做呢？为了造成是 NIO 而不是模拟层执行通信的假象。此外，还可以使模拟层与其他通道更容易结合，比如向文件通道内写入数据。
</p>
<br>
<a name="listing4">
<span style="font-weight: bold;">清单 4. 模拟非阻塞的读操作</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>/* The checkConnection method returns the character read when <br>   determining if a connection is open.<br> */<br><br>y = checkConnection();<br>if(y &lt;= 0) return y;<br><br>buffer.putChar((char ) y);<br>return rbc.read(buffer); <br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>
<a name="N10170">
<span class="smalltitle">向模拟层写入数据</span>
</a>
</p>
<p>
对于非阻塞通信，写操作只写入能够写的数据。发送缓冲区的大小和一次可以写入的数据多少有很大关系。缓冲区的大小可以通过调用
<code>Socket</code>
对象的
<code>getSendBufferSize()</code>
方法确定。在尝试非阻塞写操作时必须考虑到这个大小。如果尝试写入比缓冲块更大的数据，必须拆开放到多个非阻塞写操作中。太大的单个写操作可能被阻塞。
</p>
<br>
<a name="listing5">
<span style="font-weight: bold;">清单 5. 模拟非阻塞的写操作</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>int x, y = s.getSendBufferSize(), z = 0;<br>int expectedWrite;<br>byte [] p = buffer.array();<br>ByteBuffer buf = ByteBuffer.allocateDirect(y);<br><br>/* If there isn't any data to write, return, otherwise flush the stream */<br><br>if(buffer.remaining() == 0) return 0;<br>os.flush()<br><br>for(x = 0; x &lt; p.length; x += y)<br>{<br>    if(p.length - x &lt; y)<br>    {<br>        buf.put(p, x, p.length - x);<br>        expectedWrite = p.length - x;<br>    }<br>    else<br>    {<br>        buf.put(p, x, y);<br>        expectedWrite = y;<br>    }<br><br>    /* Check the status of the socket to make sure it's still open */<br><br>    if(!s.isConnected()) break;<br><br>    /* Write the data to the stream, flushing immediately afterward */<br><br>    buf.flip(); <br>    z = wbc.write(buf); os.flush();<br>    if(z &lt; expectedWrite) break;<br>    buf.clear();<br><br>}<br>if(x &gt; p.length) return p.length;<br>else if(x == 0) return -1;<br>else return x + z;<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>与读操作类似，首先要检查套接字是否仍然连接。但是如果把数据写入
<code>WritableByteBuffer</code>
对象，就像清单 5
那样，该对象将自动进行检查并在没有连接时抛出必要的异常。在这个动作之后开始写数据之前，流必须立即被清空，以保证发送缓冲区中有发送数据的空间。任何
写操作都要这样做。发送到块中的数据与发送缓冲区的大小相同。执行清除操作可以保证发送缓冲不会溢出而导致写操作被阻塞。 </p>
<p>因为假定写操作只能写入能够写的内容，这个过程还必须检查套接字保证它在每个数据块写入后仍然是打开的。如果在写入数据时套接字被关闭，则必须中止写操作并返回套接字关闭之前能够发送的数据量。
</p>
<p>
<code>BufferedOutputReader</code>
可用于模拟非阻塞写操作。如果试图写入超过缓冲区两倍长度的数据，则直接写入缓冲区整倍数长度的数据（缓冲余下的数据）。比如说，如果缓冲区的长度是
256 字节而需要写入 529 字节的数据，则该对象将清除当前缓冲区、发送 512 字节然后保存剩下的 17 字节。 </p>
<p>对于非阻塞写而言，这并非我们所期望的。我们希望分次把数据写入同样大小的缓冲区中，并最终把全部数据都写完。如果发送的大块数据留下一些数据被缓冲，那么在所有数据被发送的时候，写操作就会被阻塞。
</p>
<p>
<a name="N1019F">
<span class="smalltitle">模拟层类模板</span>
</a>
</p>
<p>
整个模拟层可以放到一个类中，以便更容易和应用程序集成。如果要这样做，我建议从
<code>ByteChannel
</code> 派生这个类。这个类可以强制转换成单独的
<code>ReadableByteChannel</code>
和
<code>WritableByteChannel</code> 类。
</p>
<p>
清单 6 给出了从
<code>ByteChannel</code> 派生的模拟层类模板的一个例子。本文后面将一直使用这个类表示通过阻塞连接执行的非阻塞操作。
</p>
<br>
<a name="listing6">
<span style="font-weight: bold;">清单 6. 模拟层的类模板</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>public class nbChannel implements ByteChannel<br>{<br>    Socket s;<br>    InputStream is; OutputStream os;<br>    ReadableByteChannel rbc;<br>    WritableByteChannel wbc;<br><br>    public nbChannel(Socket socket);<br>    public int read(ByteBuffer dest);<br>    public int write(ByteBuffer src);<br>    public void close();<br><br>    protected int checkConnection();<br>}<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>
<a name="N101C5">
<span class="smalltitle">使用模拟层创建套接字</span>
</a>
</p>
<p>
使用新建的模拟层创建套接字非常简单。只要像通常那样创建
<code>Socket</code> 对象，然后创建
<code>nbChannel</code>
对象就可以了，如清单 7 所示：
</p>
<br>
<a name="listing7">
<span style="font-weight: bold;">清单 7. 使用模拟层</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>Socket s = new Socket("www.ibm.com", 80);<br>nbChannel socketChannel = new nbChannel(s);<br>ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;<br>WritableByteChannel wbc = (WritableByteChannel)socketChannel;<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="4">
<span class="atitle">创建传统的非阻塞服务器套接字</span>
</a>
</p>
<p>
服务器端的非阻塞套接字和客户端上的没有很大差别。稍微麻烦一点的只是建立接受输入连接的套接字。套接字必须通过从服务器套接字通道派生一个阻塞的服务器套接字绑定到阻塞模式。清单
8 列出了需要做的步骤。 </p>
<br>
<a name="listing8">
<span style="font-weight: bold;">清单 8. 创建非阻塞的服务器套接字（SocketChannel）</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>ServerSocketChannel ssc = ServerSocketChannel.open();<br>ServerSocket ss = ssc.socket();<br>ss.bind(new InetSocketAddress(port));<br>SocketChannel sc = ssc.accept();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>与客户机套接字通道相似，服务器套接字通道也必须打开而不是使用
<code>new</code> 操作符或者构造函数。在打开之后，必须派生服务器套接字对象以便把套接字通道绑定到一个端口。一旦套接字被绑定，服务器套接字对象就可以丢弃了。
</p>
<p>通道使用
<code>accept()</code>
方法接收到来的连接并把它们转给套接字通道。一旦接收了到来的连接并转给套接字通道对象，通信就可以通过
<code>read()</code>
和
<code>write()</code> 方法开始进行了。
</p>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="5">
<span class="atitle">创建替代的非阻塞服务器套接字</span>
</a>
</p>
<p>
实际上，并非真正的替代。因为服务器套接字通道必须使用服务器套接字对象绑定，为何不完全绕开服务器套接字通道而仅使用服务器套接字对象呢？不过这里的通信不使用
<code>SocketChannel</code> ，而要使用模拟层
<code>nbChannel。</code></p>
<br>
<a name="listing9">
<span style="font-weight: bold;">清单 9. 建立服务器套接字的另一种方法</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>ServerSocket ss = new ServerSocket(port);<br>Socket s = ss.accept();<br>nbChannel socketChannel = new nbChannel(s);<br>ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;<br>WritableByteChannel wbc = (WritableByteChannel)socketChannel;<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="6">
<span class="atitle">创建 SSL 连接</span>
</a>
</p>
<p>
创建SSL连接，我们要分别从客户端和服务器端考察。
</p>
<p>
<a name="N1022D">
<span class="smalltitle">从客户端</span>
</a>
</p>
<p>
创建 SS L连接的传统方法涉及到使用套接字工厂和其他一些东西。我将不会详细讨论如何创建SSL连接，不过有一本很好的教程，&#8220;Secure your
sockets with JSSE&#8221;（请参阅
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#resources">参考资料</a>），从中您可以了解到更多的信息。
</p>
<p>创建 SSL 套接字的默认方法非常简单，只包括几个很短的步骤：</p>
<ol>
    <li>创建套接字工厂。</li>
    <li>创建连接的套接字。</li>
    <li>开始握手。</li>
    <li>派生流。</li>
    <li>通信。</li>
</ol>
<p>清单 10 说明了这些步骤：</p>
<br>
<a name="listing10">
<span style="font-weight: bold;">清单 10. 创建安全的客户机套接字</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>SSLSocketFactory sslFactory = <br>  (SSLSocketFactory)SSLSocketFactory.getDefault();<br>SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);<br>ssl.startHandshake();<br>InputStream is = ssl.getInputStream();<br>OutputStream os = ssl.getOutputStream();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>默认方法不包括客户验证、用户证书和其他特定连接可能需要的东西。
</p>
<p>
<a name="N1025F">
<span class="smalltitle">从服务器端</span>
</a>
</p>
<p>
建立SSL服务器连接的传统方法稍微麻烦一点，需要加上一些类型转换。因为这些超出了本文的范围，我将不再进一步介绍，而是说说支持SSL服务器连接的默认方法。
</p>
<p>创建默认的 SSL 服务器套接字也包括几个很短的步骤：</p>
<ol>
    <li>创建服务器套接字工厂。</li>
    <li>创建并绑定服务器套接字。</li>
    <li>接受传入的连接。</li>
    <li>开始握手。</li>
    <li>派生流。</li>
    <li>通信。</li>
</ol>
<p>尽管看起来似乎与客户端的步骤相似，要注意这里去掉了很多安全选项，比如客户验证。
</p>
<p>清单 11 说明这些步骤：</p>
<br>
<a name="listing11">
<span style="font-weight: bold;">清单 11. 创建安全的服务器套接字</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>SSLServerSocketFactory sslssf = <br>  (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();<br>SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);<br>SSLSocket ssls = (SSLSocket)sslss.accept();<br>ssls.startHandshake();<br>InputStream is = ssls.getInputStream();<br>OutputStream os = ssls.getOutputStream();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="7">
<span class="atitle">创建安全的非阻塞连接</span>
</a>
</p>
<p>
要精心实现安全的非阻塞连接，也需要分别从客户端和服务器端来看。
</p>
<p>
<a name="N10299">
<span class="smalltitle">从客户端</span>
</a>
</p>
<p>
在客户端建立安全的非阻塞连接非常简单： </p>
<ol>
    <li>创建并连接
    <code>Socket</code> 对象。
    </li>
    <li>把
    <code>Socket</code> 对象添加到模拟层上。
    </li>
    <li>通过模拟层通信。</li>
</ol>
<p>清单 12 说明了这些步骤：</p>
<br>
<a name="listing12">
<span style="font-weight: bold;">清单 12. 创建安全的客户机连接</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>/* Create the factory, then the secure socket */<br><br>SSLSocketFactory sslFactory = <br>  (SSLSocketFactory)SSLSocketFactory.getDefault();<br>SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);<br><br>/* Start the handshake.  Should be done before deriving channels */<br><br>ssl.startHandshake();<br><br>/* Put it into the emulation layer and create separate channels */<br><br>nbChannel socketChannel = new nbChannel(ssl);<br>ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;<br>WritableByteChannel wbc = (WritableByteChannel)socketChannel;<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>利用前面给出的
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#emulclass">模拟层类</a>
就可以实现非阻塞的安全连接。因为安全套接字通道不能使用
<code>SocketChannel</code>
类打开，而 Java API
中又没有完成这项工作的类，所以创建了一个模拟类。模拟类可以实现非阻塞通信，无论使用安全套接字连接还是非安全套接字连接。
</p>
<p>列出的步骤包括默认的安全设置。对于更高级的安全性，比如用户证书和客户验证，
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#resources">参考资料</a>
部分提供了说明如何实现的文章。
</p>
<p>
<a name="N102D5">
<span class="smalltitle">从服务器端</span>
</a>
</p>
<p>
在服务器端建立套接字需要对默认安全稍加设置。但是一旦套接字被接收和路由，设置必须与客户端的设置完全相同，如清单 13 所示： </p>
<br>
<a name="listing13">
<span style="font-weight: bold;">清单 13. 创建安全的非阻塞服务器套接字</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>/* Create the factory, then the socket, and put it into listening mode */               <br><br>SSLServerSocketFactory sslssf = <br>  (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();<br>SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);<br>SSLSocket ssls = (SSLSocket)sslss.accept();<br><br>/* Start the handshake on the new socket */<br><br>ssls.startHandshake();<br><br>/* Put it into the emulation layer and create separate channels */<br><br>nbChannel socketChannel = new nbChannel(ssls);<br>ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;<br>WritableByteChannel wbc = (WritableByteChannel)socketChannel;<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>同样，要记住这些步骤使用的是默认安全设置。
</p>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="8">
<span class="atitle">集成安全的和非安全的客户机连接</span>
</a>
</p>
<p>
多数 Internet 客户机应用程序，无论使用 Java 语言还是其他语言编写，都需要提供安全和非安全连接。Java Secure Socket
Extensions 库使得这项工作非常容易，我最近在编写一个 HTTP 客户库时就使用了这种方法。 </p>
<p>
<code>SSLSocket</code> 类派生自
<code>Socket。</code> 您可能已经猜到我要怎么做了。所需要的只是该对象的一个
<code>Socket</code> 指针。如果套接字连接不使用SSL，则可以像通常那样创建套接字。如果要使用 SSL，就稍微麻烦一点，但此后的代码就很简单了。清单
14 给出了一个例子：
</p>
<br>
<a name="listing14">
<span style="font-weight: bold;">清单 14. 集成安全的和非安全的客户机连接</span>
</a>
<br>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre>												<code class="section">
            <br>Socket s;<br>ReadableByteChannel rbc;<br>WritableByteChannel wbc;<br>nbChannel socketChannel;<br><br>if(!useSSL) s = new Socket(host, port);<br>else<br>{<br>    SSLSocketFactory sslsf = SSLSocketFactory.getDefault();<br>    SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port);<br>    ssls.startHandshake();<br>    s = ssls;<br>}<br><br>socketChannel = new nbChannel(s);<br>rbc = (ReadableByteChannel)socketChannel;<br>wbc = (WritableByteChannel)socketChannel;<br><br>...<br><br>s.close();<br></code>
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>创建通道之后，如果套接字使用了SSL，那么就是安全通信，否则就是普通通信。如果使用了 SSL，关闭套接字将导致握手中止。</p>
<p>这种设置的一种可能是使用两个单独的类。一个类负责处理通过套接字沿着与非安全套接字的连接进行的所有通信。一个单独的类应该负责创建安全的连接，包括安全连接的所有必要设置，无论是否是默认的。安全类应该直接插入通信类，只有在使用安全连接时被调用。
</p>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="9">
<span class="atitle">最简单的集成方法</span>
</a>
</p>
<p>
本文提出的方法是我所知道的把 JSSE 和 NIO 集成到同一代码中以提供非阻塞安全通信的最简单方法。尽管还有其他方法，但是都需要准备实现这一过程的程序员花费大量的时间和精力。
</p>
<p>一种可能是使用 Java Cryptography Extensions 在 NIO 上实现自己的 SSL 层。另一种方法是修改现有的称为
EspreSSL （以前称为 jSSL）的定制 SSL 层， 把它改到 NIO 库中。我建议只有在您有很充裕的时间时才使用这两种方法。 </p>
<p>
<a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#resources">参考资料</a>
部分的可下载 zip
文件提供了示例代码，帮助您实践本文所述的技术，其中包括：
</p>
<ul>
    <li>nbChannel，清单 7 所介绍的模拟层的源代码。</li>
    <li>连接到 Verisign's Web 站点并下载主页的示例 HTTPS 客户程序。</li>
    <li>一个简单的非阻塞安全服务器 （Secure Daytime Server）。</li>
    <li>集成的安全和非安全客户程序。</li>
</ul>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="resources">
<span class="atitle">参考资料 </span>
</a>
</p>
<ul>
    <li>您可以参阅本文在 developerWorks 全球站点上的
    <a href="http://www.ibm.com/developerworks/library/j-sslnb.html">英文原文</a>.
    <br><br></li>
    <li>下载本文的
    <a href="ftp://www6.software.ibm.com/software/developer/library/j-sslnb.zip">源代码</a>。
    <br><br><br></li>
    <li>"
    <a href="ftp://ftp.pgpi.org/pub/pgp/7.0/docs/english/IntroToCrypto.pdf">Introduction
    to Cryptography</a>" 通过介绍 SSL 提供了加密的基本原理（PDF 格式）。
    <br><br><br></li>
    <li>"
    <a href="http://www.onjava.com/pub/a/onjava/2001/05/03/java_security.html">Secure
    Your Sockets with JSSE</a>" 是应用 JSSE 的入门指南。
    <br><br><br></li>
    <li>"
    <a href="http://www-128.ibm.com/developerworks/cn/java/j-javaio/">Merlin 给 Java 平台带来了非阻塞 I/O</a>" (
    <span style="font-style: italic;">developerWorks</span>，2002
    年 3 月)介绍了 Merlin 的 NIO 包的非阻塞特性，并提供了套接字编程的例子。
    <br><br><br></li>
    <li>"
    <a href="http://www-128.ibm.com/developerworks/cn/java/j-mer03253/">Merlin 的新 I/O 缓冲区的输入和输出</a>" (
    <span style="font-style: italic;">developerWorks</span>，2003
    年 3 月) 展示了如何为读/写基本数据类型这样的任务操作数据缓冲区和使用内存映射文件。
    <br><br><br></li>
    <li>"
    <a href="http://www-128.ibm.com/developerworks/cn/java/j-customssl/">为高级 JSSE 开发人员定制 SSL</a>" (
    <span style="font-style: italic;">developerWorks</span>，2002
    年 9 月) 示范了如何编程绕过 SSL 的一些限制。
    <br><br><br></li>
    <li>教程 "
    <a href="http://www.ibm.com/developerworks/java/edu/j-dw-java-nio-i.html">Getting
    started with new I/O (NIO)</a>" (
    <span style="font-style: italic;">developerWorks</span>，2003
    年 7 月)非常详细地讨论了 NIO 库，从高级概念到隐藏的编程细节。
    <br><br><br></li>
    <li>
    <a href="http://www.ibm.com/servers/eserver/zseries/software/java/jsse.html">IBMJSSE
    Overview</a>概要介绍了 Java Secure Socket Extension。
    <br><br><br></li>
    <li>
    <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html">JSSE
    reference guide</a>是该技术巨细无遗的参考资料。
    <br><br><br></li>
    <li>Ron Hitchens 所著的
    <a href="http://www.oreilly.com/catalog/javanio/index.html"><span style="font-style: italic;">Java
    NIO</span></a>(O'Reilly &amp; Associates, 2002)，深入探索了 1.4 版的新I/O功能。
    <br><br><br></li>
    <li>
    <span style="font-style: italic;">Java
    NIO</span>还有一个支持网站，
    <a href="http://www.javanio.info/">JavaNIO.info</a>,
    提供了代码开发的幻灯片、GUI 演示工具和该书的其他补充资料。
    <br><br><br></li>
    <li>在
    <a href="http://www-128.ibm.com/developerworks/cn/java"><span style="font-style: italic;">developerWorks</span>
    Java 技术专区
    </a>上可以找到数百篇关于 Java 各个方面的文章。
    <br><br></li>
</ul>
<br>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%">
            <br>
            <img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8">
            </td>
        </tr>
    </tbody>
</table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
    <tbody>
        <tr align="right">
            <td>
            <img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%">
            <br>
            <table border="0" cellpadding="0" cellspacing="0">
                <tbody>
                    <tr>
                        <td valign="middle">
                        <img src="http://www.ibm.com/i/v14/icons/u_bold.gif" alt="" border="0" height="16" width="16">
                        <br>
                        </td>
                        <td align="right" valign="top">
                        <a href="http://www-128.ibm.com/developerworks/cn/java/j-sslnb/#main" class="fbox">
                        <span style="font-weight: bold;">回页首</span>
                        </a>
                        </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br>
<br>
<p>
<a name="author">
<span class="atitle">关于作者</span>
</a>
</p>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td colspan="3">
            <img alt="" src="http://www.ibm.com/i/c.gif" height="5" width="100%">
            </td>
        </tr>
        <tr align="left" valign="top">
            <td>
            <br>
            </td>
            <td>
            <img alt="" src="http://www.ibm.com/i/c.gif" height="5" width="4">
            </td>
            <td width="100%">
            <p>Kenneth Ballard 目前在内布拉斯加州珀鲁州立大学学习计算机科学。他还是校内的学生报纸
            <span style="font-style: italic;">Peru
            State Times </span>的职业作者。他从爱荷华州 Creston 的 Southwestern Community College
            获得了计算机编程 Associate of Science 学位，在那里作为 PC 技术人员参加了一个工作效率研究项目。他的研究范围包括
            Java 技术、C++、COBOL、Visual Basic 和网络。可以通过
            <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#107;&#101;&#110;&#110;&#101;&#116;&#104;&#46;&#98;&#97;&#108;&#108;&#97;&#114;&#100;&#64;&#112;&#116;&#107;&#46;&#111;&#114;&#103;">kenneth.ballard@ptk.org</a>
            与 Kenneth 联系。
            </p>
            </td>
        </tr>
    </tbody>
</table><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94789.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:02 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94789.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JDOM 介绍及使用指南</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94788.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:01:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94788.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94788.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94788.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94788.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94788.html</trackback:ping><description><![CDATA[一、JDOM&nbsp;简介<br>JDOM是一个开源项目，它基于树型结构，利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。<br>JDOM&nbsp;直接为JAVA编程服务。它利用更为强有力的JAVA语言的诸多特性（方法重载、集合概念以及映射），把SAX和DOM的功能有效地结合起来。<br>在使用设计上尽可能地隐藏原来使用XML过程中的复杂性。利用JDOM处理XML文档将是一件轻松、简单的事。<br>JDOM&nbsp;在2000年的春天被Brett&nbsp;McLaughlin和Jason&nbsp;Hunter开发出来，以弥补DOM及SAX在实际应用当中的不足之处。<br>这些不足之处主要在于SAX没有文档修改、随机访问以及输出的功能，而对于DOM来说，JAVA程序员在使用时来用起来总觉得不太方便。<br>DOM
的缺点主要是来自于由于Dom是一个接口定义语言（IDL）,它的任务是在不同语言实现中的一个最低的通用标准，并不是为JAVA特别设计的。JDOM的
最新版本为JDOM&nbsp;Beta&nbsp;9。最近JDOM被收录到JSR-102内，这标志着JDOM成为了JAVA平台组成的一部分。<br><br><br>二、JDOM&nbsp;包概览<br>JDOM是由以下几个包组成的<br>org.JDOM<br>org.JDOM.input<br>org.JDOM.output<br>org.JDOM.adapters<br>org.JDOM.transform<br><br>三、JDOM&nbsp;类说明<br><br>org.JDOM<br>这个包里的类是你解析xml文件后所要用到的所有数据类型。<br>Attribute<br>CDATA<br>Coment<br>DocType<br>Document<br>Element<br>EntityRef<br>Namespace<br>ProscessingInstruction<br>Text<br><br>org.JDOM.transform<br>在涉及xslt格式转换时应使用下面的2个类<br>JDOMSource<br>JDOMResult<br><br>org.JDOM.input<br>输入类，一般用于文档的创建工作<br>SAXBuilder<br>DOMBuilder<br>ResultSetBuilder<br><br>org.JDOM.output<br>输出类，用于文档转换输出<br>XMLOutputter<br>SAXOutputter<br>DomOutputter<br>JTreeOutputter<br><br>使用前注意事项：<br>1.JDOM对于JAXP&nbsp;以及&nbsp;TRax&nbsp;的支持<br>JDOM&nbsp;支持JAXP1.1：你可以在程序中使用任何的parser工具类,默认情况下是JAXP的parser。<br>制定特别的parser可用如下形式<br>SAXBuilder&nbsp;parser&nbsp;<br>=&nbsp;new&nbsp;SAXBuilder("org.apache.crimson.parser.XMLReaderImpl");<br>Document&nbsp;doc&nbsp;=&nbsp;parser.build("http://www.cafeconleche.org/");<br>//&nbsp;work&nbsp;with&nbsp;the&nbsp;..<br>JDOM也支持TRaX：XSLT可通过JDOMSource以及JDOMResult类来转换（参见以后章节）<br>2.注意在JDOM里文档（Document）类由org.JDOM.Document&nbsp;来表示。这要与org.w3c.dom中的Document区别开，这2种格式如何转换在后面会说明。<br>以下如无特指均指JDOM里的Document。<br><br><br>四、JDOM主要使用方法<br>1.Ducument类<br>(1)Document的操作方法：<br>Element&nbsp;root&nbsp;=&nbsp;new&nbsp;Element("GREETING");<br>Document&nbsp;doc&nbsp;=&nbsp;new&nbsp;Document(root);<br>root.setText("Hello&nbsp;JDOM!");<br>或者简单的使用Document&nbsp;doc&nbsp;=&nbsp;new&nbsp;Document(new&nbsp;Element("GREETING").setText("Hello&nbsp;JDOM!t"));<br><br>这点和DOM不同。Dom则需要更为复杂的代码，如下：<br>DocumentBuilderFactory&nbsp;factory&nbsp;=DocumentBuilderFactory.newInstance();<br>DocumentBuilder&nbsp;builder&nbsp;=factory.newDocumentBuilder();<br>Document&nbsp;doc&nbsp;=&nbsp;builder.newDocument();<br>Element&nbsp;root&nbsp;=doc.createElement("root");<br>Text&nbsp;text&nbsp;=&nbsp;doc.createText("This&nbsp;is&nbsp;the&nbsp;root");<br>root.appendChild(text);<br>doc.appendChild(root);<br><br><br>注意事项：JDOM不允许同一个节点同时被2个或多个文档相关联，要在第2个文档中使用原来老文档中的节点的话。首先需要使用detach()把这个节点分开来。<br><br>(2)从文件、流、系统ID、URL得到Document对象：<br>DOMBuilder&nbsp;builder&nbsp;=&nbsp;new&nbsp;DOMBuilder();<br>Document&nbsp;doc&nbsp;=&nbsp;builder.build(new&nbsp;File("jdom_test.xml"));<br><br>SAXBuilder&nbsp;builder&nbsp;=&nbsp;new&nbsp;SAXBuilder();<br>Document&nbsp;doc&nbsp;=&nbsp;builder.build(url);<br>在新版本中DOMBuilder&nbsp;已经Deprecated掉&nbsp;DOMBuilder.builder(url)，用SAX效率会比较快。<br><br>这里举一个小例子，为了简单起见，使用String对象直接作为xml数据源：<br><br>public&nbsp;jdomTest()&nbsp;{<br>&nbsp;&nbsp;String&nbsp;textXml&nbsp;=&nbsp;null;<br>&nbsp;&nbsp;textXml&nbsp;=&nbsp;"&lt;note&gt;";<br>&nbsp;&nbsp;textXml&nbsp;=&nbsp;textXml&nbsp;+<br>&nbsp;&nbsp;&nbsp;&nbsp;"&lt;to&gt;aaa&lt;/to&gt;&lt;from&gt;bbb&lt;/from&gt;&lt;heading&gt;ccc&lt;/heading&gt;&lt;body&gt;ddd&lt;/body&gt;";<br>&nbsp;&nbsp;textXml&nbsp;=&nbsp;textXml&nbsp;+&nbsp;"&lt;/note&gt;";<br>&nbsp;&nbsp;SAXBuilder&nbsp;builder&nbsp;=&nbsp;new&nbsp;SAXBuilder();<br>&nbsp;&nbsp;Document&nbsp;doc&nbsp;=&nbsp;null;<br>&nbsp;&nbsp;Reader&nbsp;in=&nbsp;new&nbsp;StringReader(textXml);<br>&nbsp;&nbsp;try&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;doc&nbsp;=&nbsp;builder.build(in);<br>&nbsp;&nbsp;&nbsp;&nbsp;Element&nbsp;root&nbsp;=&nbsp;doc.getRootElement();<br>&nbsp;&nbsp;&nbsp;&nbsp;List&nbsp;ls&nbsp;=&nbsp;root.getChildren();//注意此处取出的是root节点下面的一层的Element集合<br>&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(Iterator&nbsp;iter&nbsp;=&nbsp;ls.iterator();&nbsp;iter.hasNext();&nbsp;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Element&nbsp;el&nbsp;=&nbsp;(Element)&nbsp;iter.next();<br>&nbsp;&nbsp;&nbsp;&nbsp;if(el.getName().equals("to")){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(el.getText());<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;catch&nbsp;(IOException&nbsp;ex)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;ex.printStackTrace();<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;catch&nbsp;(JDOMException&nbsp;ex)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;ex.printStackTrace();<br>&nbsp;&nbsp;}<br>}<br><br>很简单把。<br><br><br>(3)DOM的document和JDOM的Document之间的相互转换使用方法，简单！<br>DOMBuilder&nbsp;builder&nbsp;=&nbsp;new&nbsp;DOMBuilder();<br>org.jdom.Document&nbsp;jdomDocument&nbsp;=&nbsp;builder.build(domDocument);<br>//&nbsp;work&nbsp;with&nbsp;the&nbsp;JDOM&nbsp;document&#8230;<br><br>DOMOutputter&nbsp;converter&nbsp;=&nbsp;new&nbsp;DOMOutputter();<br>org.w3c.dom.Document&nbsp;domDocument&nbsp;=&nbsp;converter.output(jdomDocument);<br>//&nbsp;work&nbsp;with&nbsp;the&nbsp;DOM&nbsp;document&#8230;<br><br>2.XML文档输出<br>XMLOutPutter类：<br>JDOM的输出非常灵活,支持很多种io格式以及风格的输出<br>Document&nbsp;doc&nbsp;=&nbsp;new&nbsp;Document(...);<br>XMLOutputter&nbsp;outp&nbsp;=&nbsp;new&nbsp;XMLOutputter();<br>//&nbsp;Raw&nbsp;output<br>outp.output(doc,&nbsp;fileOutputStream);<br>//&nbsp;Compressed&nbsp;output<br>outp.setTextTrim(true);<br>outp.output(doc,&nbsp;socket.getOutputStream());<br>//&nbsp;Pretty&nbsp;output<br>outp.setIndent("&nbsp;");<br>outp.setNewlines(true);<br>outp.output(doc,&nbsp;System.out);<br>......<br>详细请参阅最新的JDOM&nbsp;API手册<br><br><br>3.Element&nbsp;类：<br>(1)浏览Element树<br>//获得根元素element<br>Element&nbsp;root&nbsp;=&nbsp;doc.getRootElement();<br>//&nbsp;获得所有子元素的一个list<br>List&nbsp;allChildren&nbsp;=&nbsp;root.getChildren();<br>//&nbsp;获得指定名称子元素的list<br>List&nbsp;namedChildren&nbsp;=&nbsp;root.getChildren("name");<br>//获得指定名称的第一个子元素<br>Element&nbsp;child&nbsp;=&nbsp;root.getChild("name");<br>（这里的List是java.util.List）<br><br>JDOM给了我们很多很灵活的使用方法来管理子元素<br>List&nbsp;allChildren&nbsp;=&nbsp;root.getChildren();<br>//&nbsp;删除第四个子元素<br>allChildren.remove(3);<br>//&nbsp;删除叫&#8220;jack&#8221;的子元素<br>allChildren.removeAll(root.getChildren("jack"));<br><br>root.removeChildren("jack");&nbsp;//&nbsp;便捷写法<br>//&nbsp;加入<br>allChildren.add(new&nbsp;Element("jane"));<br><br>root.addContent(new&nbsp;Element("jane"));&nbsp;//&nbsp;便捷写法<br>allChildren.add(0,&nbsp;new&nbsp;Element("first"));<br><br><br>(2)移动Elements:<br>在JDOM里很简单<br>Element&nbsp;movable&nbsp;=&nbsp;new&nbsp;Element("movable");<br>parent1.addContent(movable);&nbsp;//&nbsp;place<br>parent1.removeContent(movable);&nbsp;//&nbsp;remove<br>parent2.addContent(movable);&nbsp;//&nbsp;add<br><br>在Dom里<br>Element&nbsp;movable&nbsp;=&nbsp;doc1.createElement("movable");<br>parent1.appendChild(movable);&nbsp;//&nbsp;place<br>parent1.removeChild(movable);&nbsp;//&nbsp;remove<br>parent2.appendChild(movable);&nbsp;//&nbsp;出错!<br><br>补充：<br>纠错性<br>JDOM的Element构造函数（以及它的其他函数）会检查element是否合法。<br>而它的add/remove方法会检查树结构，检查内容如下：<br>1.在任何树中是否有回环节点<br>2.是否只有一个根节点<br>3.是否有一致的命名空间（Namespaces）<br><br><br><br>(3)Element的text内容读取<br>&lt;deion&gt;<br>A&nbsp;cool&nbsp;demo<br>&lt;/deion&gt;<br><br>//&nbsp;The&nbsp;text&nbsp;is&nbsp;directly&nbsp;available<br>//&nbsp;Returns&nbsp;"\n&nbsp;A&nbsp;cool&nbsp;demo\n"<br>String&nbsp;desc&nbsp;=&nbsp;element.getText();<br><br>//&nbsp;There's&nbsp;a&nbsp;convenient&nbsp;shortcut<br>//&nbsp;Returns&nbsp;"A&nbsp;cool&nbsp;demo"<br>String&nbsp;desc&nbsp;=&nbsp;element.getTextTrim();<br><br>(4)Elment内容修改<br>element.setText("A&nbsp;new&nbsp;deion");<br>3.可正确解释特殊字符<br>element.setText("&lt;xml&gt;&nbsp;content");<br>4.CDATA的数据写入、读出<br>element.addContent(new&nbsp;CDATA("&lt;xml&gt;&nbsp;content"));<br>String&nbsp;noDifference&nbsp;=&nbsp;element.getText();<br><br>混合内容<br>element可能包含很多种内容，比如说<br><br>&lt;table&gt;<br>&lt;!--&nbsp;Some&nbsp;comment&nbsp;--&gt;<br>Some&nbsp;text<br>&lt;tr&gt;Some&nbsp;child&nbsp;element&lt;/tr&gt;<br>&lt;/table&gt;<br><br>取table的子元素tr<br>String&nbsp;text&nbsp;=&nbsp;table.getTextTrim();<br>Element&nbsp;tr&nbsp;=&nbsp;table.getChild("tr");<br><br>也可使用另外一个比较简单的方法<br>List&nbsp;mixedCo&nbsp;=&nbsp;table.getContent();<br>Iterator&nbsp;itr&nbsp;=&nbsp;mixedCo.iterator();<br>while&nbsp;(itr.hasNext())&nbsp;{<br>Object&nbsp;o&nbsp;=&nbsp;i.next();<br>if&nbsp;(o&nbsp;instanceof&nbsp;Comment)&nbsp;{<br>...<br>}<br>//&nbsp;这里可以写成Comment,&nbsp;Element,&nbsp;Text,&nbsp;CDATA,ProcessingInstruction,&nbsp;或者是EntityRef的类型<br>}<br>//&nbsp;现在移除Comment,注意这里游标应为1。这是由于回车键也被解析成Text类的缘故,所以Comment项应为1。<br>mixedCo.remove(1);<br><br><br><br>4.Attribute类<br>&lt;table&nbsp;width="100%"&nbsp;border="0"&gt;&nbsp;&lt;/table&gt;<br>//获得attribute<br>String&nbsp;width&nbsp;=&nbsp;table.getAttribute("width");<br>int&nbsp;border&nbsp;=&nbsp;table.getAttribute("width").getInt();<br>//设置attribute<br>table.setAttribute("vspace",&nbsp;"0");<br>//&nbsp;删除一个或全部attribute<br>table.removeAttribute("vspace");<br>table.getAttributes().clear();<br><br><br><br>5.处理指令(Processing&nbsp;Instructions)操作<br>一个Pls的例子<br>&lt;?br?&gt;<br>&lt;?cocoon-process&nbsp;type="xslt"?&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;目标&nbsp;&nbsp;&nbsp;数据<br><br>处理目标名称(Target)<br>String&nbsp;target&nbsp;=&nbsp;pi.getTarget();<br>获得所有数据（data），在目标（target）以后的所有数据都会被返回。<br>String&nbsp;data&nbsp;=&nbsp;pi.getData();<br>获得指定属性的数据<br>String&nbsp;type&nbsp;=&nbsp;pi.get("type");<br>获得所有属性的名称<br>List&nbsp;ls&nbsp;=&nbsp;pi.getNames();<br><br>6.命名空间操作<br>&lt;xhtml:html&nbsp;<br>xmlns:xhtml="http://www.w3.org/1999/xhtml"&gt;<br>&lt;xhtml:title&gt;Home&nbsp;Page&lt;/xhtml:title&gt;<br>&lt;/xhtml:html&gt;<br><br>Namespace&nbsp;xhtml&nbsp;=&nbsp;Namespace.getNamespace("xhtml",&nbsp;"http://www.w3.org/1999/xhtml");<br>List&nbsp;kids&nbsp;=&nbsp;html.getChildren("title",&nbsp;xhtml);<br>Element&nbsp;kid&nbsp;=&nbsp;html.getChild("title",&nbsp;xhtml);<br>kid.addContent(new&nbsp;Element("table",&nbsp;xhtml));<br><br>7.XSLT格式转换<br>使用以下函数可对XSLT转换<br>最后如果你需要使用w3c的Document则需要转换一下。<br>public&nbsp;static&nbsp;Document&nbsp;transform(String&nbsp;stylesheet，Document&nbsp;in)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throws&nbsp;JDOMException&nbsp;{<br>&nbsp;&nbsp;try&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Transformer&nbsp;transformer&nbsp;=&nbsp;TransformerFactory.newInstance()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.newTransformer(new&nbsp;StreamSource(stylesheet));<br>&nbsp;&nbsp;&nbsp;&nbsp;JDOMResult&nbsp;out&nbsp;=&nbsp;new&nbsp;JDOMResult();<br>&nbsp;&nbsp;&nbsp;&nbsp;transformer.transform(new&nbsp;JDOMSource(in),&nbsp;out);<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;out.getDeocument();<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;catch&nbsp;(TransformerException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;JDOMException("XSLT&nbsp;Trandformation&nbsp;failed",&nbsp;e);<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br><br>参考书目：<br><br>1.JDOM官方网站：&nbsp;http://www.jdom.org<br><br>2.&lt;&lt;Processing&nbsp;XML&nbsp;with&nbsp;Java&gt;&gt;&nbsp;Elliotte&nbsp;Rusty&nbsp;Harold&nbsp;2002<br><br>3.JDOM&nbsp;API&nbsp;Documentation<br><br>4.&lt;&lt;JDOM&nbsp;Makes&nbsp;XML&nbsp;Easy&gt;&gt;Jason&nbsp;Hunter&nbsp;Co-Creator&nbsp;JDOM&nbsp;Project<br><br>5.WSDP&nbsp;Tutorial  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94788.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:01 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94788.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java NIO原理和使用</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94786.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:00:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94786.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94786.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94786.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94786.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94786.html</trackback:ping><description><![CDATA[<p align="left">Java　NIO非堵塞应用通常适用用在I/O读写等方面，我们知道，系统运行的性能瓶颈通常在I/O读写，包括对端口
和文件的操作上，过去，在打开一个I/O通道后，read()将一直等待在端口一边读取字节内容，如果没有内容进来，read()也是傻傻的等，这会影响
我们程序继续做其他事情，那么改进做法就是开设线程，让线程去等待，但是这样做也是相当耗费资源的。</p>
<p align="left">Java NIO非堵塞技术实际是采取Reactor模式，或者说是Observer模式为我们监察I/O端口，如果有内容进来，会自动通知我们，这样，我们就不必开启多个线程死等，从外界看，实现了流畅的I/O读写，不堵塞了。</p>
<p align="left">Java NIO出现不只是一个技术性能的提高，你会发现网络上到处在介绍它，因为它具有里程碑意义，从JDK1.4开始，Java开始提高性能相关的功能，从而使得Java在底层或者并行分布式计算等操作上已经可以和C或Perl等语言并驾齐驱。</p>
<p align="left">如
果你至今还是在怀疑Java的性能，说明你的思想和观念已经完全落伍了，Java一两年就应该用新的名词来定义。从JDK1.5开始又要提供关于线程、并
发等新性能的支持，Java应用在游戏等适时领域方面的机会已经成熟，Java在稳定自己中间件地位后，开始蚕食传统C的领域。</p>
<p align="left">本文主要简单介绍NIO的基本原理，在下一篇文章中，将结合Reactor模式和著名线程大师<a href="http://gee.cs.oswego.edu/dl/">Doug
Lea</a>的一篇文章深入讨论。</p>
<p align="left">NIO主要原理和适用。</p>
<p align="left">NIO
有一个主要的类Selector,这个类似一个观察者，只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情，当有
事件发生时，他会通知我们，传回一组Selecti,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后，我们从
这个Channel中读取数据，放心，包准能够读到，接着我们可以处理这些数据。</p>
<p align="left">Selector内部原理实际是在做一个对所注册的channel的轮询访问，不断的轮询(目前就这一个算法)，一旦轮询到一个channel有所注册的事情发生，比如数据来了，他就会站起来报告，交出一把钥匙，让我们通过这把钥匙来读取这个channel的内容。</p>
<p align="left">了解了这个基本原理，我们结合代码看看使用，在使用上，也在分两个方向，一个是线程处理，一个是用非线程，后者比较简单，看下面代码：</p>
<table bgcolor="#cccccc" border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <br>
            import java.io.*;<br>
            import java.nio.*;<br>
            import java.nio.channels.*;<br>
            import java.nio.channels.spi.*;<br>
            import java.net.*;<br>
            import java.util.*;
            <p>/**<br>
            *<br>
            * @author Administrator<br>
            * @version<br>
            */<br>
            public class NBTest {</p>
            <p><br>
            /** Creates new NBTest */<br>
            public NBTest()<br>
            {<br>
            }</p>
            <p> 　　public void startServer() throws Exception<br>
            {<br>
            int channels = 0;<br>
            int nKeys = 0;<br>
            int currentSelector = 0;<br><br>
            //使用Selector<br>
            Selector selector = Selector.open();<br><br>
            //建立Channel 并绑定到9000端口<br>
            ServerSocketChannel ssc = ServerSocketChannel.open();<br>
            InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),9000);
            <br>
            ssc.socket().bind(address);<br><br>
            //使设定non-blocking的方式。<br>
            ssc.configureBlocking(false);</p>
            <p> 　　//向Selector注册Channel及我们有兴趣的事件<br>
            Selecti s = ssc.register(selector, Selecti.OP_ACCEPT);<br>
            printKeyInfo(s);</p>
            <p> 　　while(true) //不断的轮询<br>
            {<br>
            debug("NBTest: Starting select");<br><br>
            //Selector通过select方法通知我们我们感兴趣的事件发生了。<br>
            nKeys = selector.select();<br>
            //如果有我们注册的事情发生了，它的传回值就会大于0<br>
            if(nKeys &gt; 0)<br>
            {<br>
            debug("NBTest: Number of keys after select
            operation: " +nKeys);<br><br>
            //Selector传回一组Selectis<br>
            //我们从这些key中的channel()方法中取得我们刚刚注册的channel。<br>
            Set selectedKeys = selector.selectedKeys();<br>
            Iterator i = selectedKeys.iterator();<br>
            while(i.hasNext())<br>
            {<br>
            s = (Selecti) i.next();<br>
            printKeyInfo(s);<br>
            debug("NBTest: Nr Keys in selector: "
            +selector.keys().size());<br><br>
            //一个key被处理完成后，就都被从就绪关键字（ready keys）列表中除去<br>
            i.remove();<br>
            if(s.isAcceptable())<br>
            {<br>
            // 从channel()中取得我们刚刚注册的channel。<br>
            Socket socket = ((ServerSocketChannel)s.channel()).accept().socket();<br>
            SocketChannel sc = socket.getChannel();<br><br>
            sc.configureBlocking(false);<br>
            sc.register(selector, Selecti.OP_READ
            |Selecti.OP_WRITE);<br>
            System.out.println(++channels);<br>
            }<br>
            else<br>
            {<br>
            debug("NBTest: Channel not acceptable");<br>
            }<br>
            }<br>
            }<br>
            else<br>
            {<br>
            debug("NBTest: Select finished without any
            keys.");<br>
            }</p>
            <p> 　 }<br><br>
            }</p>
            <p><br>
            private static void debug(String s)<br>
            {<br>
            System.out.println(s);<br>
            }</p>
            <p><br>
            private static void printKeyInfo(Selecti sk)<br>
            {<br>
            String s = new String();</p>
            <p> 　 s = "Att: " + (sk.attachment() == null
            ? "no" : "yes");<br>
            s += ", Read: " + sk.isReadable();<br>
            s += ", Acpt: " + sk.isAcceptable();<br>
            s += ", Cnct: " + sk.isConnectable();<br>
            s += ", Wrt: " + sk.isWritable();<br>
            s += ", Valid: " + sk.isValid();<br>
            s += ", Ops: " + sk.interestOps();<br>
            debug(s);<br>
            }</p>
            <p><br>
            /**<br>
            * @param args the command line arguments<br>
            */<br>
            public static void main (String args[])<br>
            {<br>
            NBTest nbTest = new NBTest();<br>
            try<br>
            {<br>
            nbTest.startServer();<br>
            }<br>
            catch(Exception e)<br>
            {<br>
            e.printStackTrace();<br>
            }<br>
            }</p>
            <p>}</p>
            <p><br></p>
            </td>
        </tr>
    </tbody>
</table>
<p align="left">这是一个守候在端口9000的noblock server例子，如果我们编制一个客户端程序，就可以对它进行互动操作，或者使用telnet
主机名　90000 可以链接上。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94786.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:00 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94786.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JDOM处理XML快速上手</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94787.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:00:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94787.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94787.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94787.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94787.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94787.html</trackback:ping><description><![CDATA[在 JDOM 中，<a href="java:;" target="_self"><span style="font-weight: bold; text-decoration: underline;">XML</span></a> 元素就是 Element 的实例，XML 属性就是 Attribute 的实例，XML 文档本身就是 Document 的实例。<br>&nbsp;&nbsp;&nbsp; 因为 JDOM 对象就是像 Document、Element 和 Attribute 这些类的直接实例，因此创建一个新 JDOM 对象就如在 Java 语言中使用 new 操作符一样容易。JDOM 的使用是直截了当的。<br>&nbsp;&nbsp;&nbsp; JDOM 使用标准的 Java 编码模式。只要有可能，它使用 Java new 操作符而不故弄玄虚使用复杂的工厂化模式，使对象操作即便对于初学用户也很方便。<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 本文分两步对JDOM的应用加以介绍：XML创建 和 XML解析<br>一、XML文档创建<br>&nbsp;&nbsp;&nbsp; 我们由零开始利用JDOM生成一个XML文档。最后的结果(样本文档)看起来象这样：<br>&nbsp;&nbsp;&nbsp; &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&nbsp;&nbsp;&nbsp; &lt;MyInfo comment="introduce myself"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;name&gt;kingwong&lt;/name&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;sex ="male"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;contact&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;telephone&gt;87654321&lt;/telephone&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/contact&gt;<br>&nbsp;&nbsp;&nbsp; &lt;/MyInfo&gt;<br>&nbsp;&nbsp;&nbsp; 1.以 MyInfo 为根元素创建文档<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Element rootElement = new Element("MyInfo");//所有的XML元素都是 Element 的实例。根元素也不例外：）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Document myDocument = new Document(rootElement);//以根元素作为参数创建Document对象。一个Document只有一个根，即root元素。<br>&nbsp;&nbsp;&nbsp; 2.给根元素添加属性<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Attribute rootAttri = new Attribute("comment","introduce myself");//创建名为 commnet,值为 introduce myself 的属性。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rootElement.setAttribute(rootAttri);//将刚创建的属性添加到根元素。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这两行代码你也可以合成一行来写，象这样：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rootElement.setAttribute(new Attribute("comment","introduce myself"));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 或者<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rootElement.setAttribute("comment","introduce myself");<br>&nbsp;&nbsp;&nbsp; 3.添加元素和子元素<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JDOM里子元素是作为 content（内容）添加到父元素里面去的,所谓content就是类似上面样本文档中&lt;name&gt;&lt;/name&gt;之间的东东，即kingwong。罗嗦了点是吧：）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Element nameElement = new Element("name");//创建 name 元素<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nameElement.addContent("kingwong");//将kingwong作为content添加到name元素<br>&nbsp;rootElement.addContent(nameElement);//将name元素作为content添加到根元素<br>&nbsp;<br>&nbsp;这三行你也可以合为一句，象这样：<br>&nbsp;rootElement.addContent((Content)(new
Element("name").addContent("kingwong")));//因为addContent(Content
child)方法返回的是一个Parent接口，而Element类同时继承了Content类和实现了Parent接口，所以我们把它造型成
Content。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们用同样的方法添加带属性的子元素&lt;sex ="male"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
rootElement.addContent(new
Element("sex").setAttribute("","male"));//注意这里不需要转型，因为addAttribute
(String name,String )返回值就是一个 Element。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 同样的，我们添加&lt;contract /&gt;元素到根元素下，用法上一样，只是稍微复杂了一些：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
rootElement.addContent((Content)(new
Element("contact").addContent((Content)(new
Element("telephone").addContent("87654321")))));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果你对这种简写形式还不太习惯，你完全可以分步来做，就象本节刚开始的时候一样。事实上如果层次比较多，写成分步的形式更清晰些，也不容易出错。<br>&nbsp;&nbsp;&nbsp; 4.删除子元素<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这个操作比较简单：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rootElement.removeChild("sex");//该方法返回一个布尔值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 到目前为止，我们<a href="java:;" target="_self"><span style="font-weight: bold; text-decoration: underline;">学习</span></a>了一下JDOM文档生成操作。上面建立了一个样本文档，可是我们怎么知道对不对呢？因此需要输出来看一下。我们将JDOM生成的文档输出到控制台，使用 JDOM 的 XMLOutputter 类。<br>&nbsp;&nbsp;&nbsp; 5.&nbsp; 将 JDOM 转化为 XML 文本<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; XMLOutputter xmlOut = new XMLOutputter("&nbsp; ",true);<br>&nbsp;try {<br>&nbsp;&nbsp;xmlOut.output(myDocument,System.out);<br>&nbsp;} catch (IOException e) {<br>&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;}<br>&nbsp;XMLOutputter 有几个格式选项。这里我们已指定希望子元素从父元素缩进两个空格，并且希望元素间有空行。<br>&nbsp;new
XMLOutputter(java.lang.String indent, boolean
newlines)这个方法在最新版本中已经不建议使用。JDOM有一个专门的用来定义格式化输出的类：org.jdom.output.Format，
如果你没有特殊的要求，有时候使用里面的几个静态方法（应该可以说是预定义格式）如
getPrettyFormat()就可以了。我们把上面的输出格式稍微改一下，就象这样：<br>&nbsp;XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());&nbsp; <br>&nbsp;&nbsp;&nbsp; 6.将JDOM文档转化为其他形式<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; XMLOutputter 还可输出到 Writer 或 OutputStream。为了输出JDOM文档到一个文本文件，我们可以这样做：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FileWriter writer = new FileWriter("/some/directory/myFile.xml");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outputter.output(myDocument, writer);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; writer.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; XMLOutputter 还可输出到字符串,以便程序后面进行再处理:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Strng ōutString = xmlOut.outputString(myDocument);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当然，在输出的时候你不一定要输出所有的整个文档，你可以选择元素进行输出：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xmlOut.output(rootElement.getChild("name"),System.out);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一句话，JDOM非常灵活方便！如果你想进一步研究JDOM，请到官方网站去看一看：<a href="http://www.jdom.org/"><span style="color: #800080;">http://www.jdom.org</span></a>
<p>&nbsp;&nbsp;&nbsp; 本节示例源码：<br>package com.cyberobject.study;</p>
<p>import java.io.IOException;</p>
<p>import org.jdom.Attribute;<br>import org.jdom.Content;<br>import org.jdom.Document;<br>import org.jdom.Element;<br>import org.jdom.output.Format;<br>import org.jdom.output.XMLOutputter;</p>
<p>/**<br>&nbsp;* @author kingwong<br>&nbsp;*<br>&nbsp;* TODO To change the template for this generated type comment go to<br>&nbsp;* Window - Preferences - Java - Code Style - Code Templates<br>&nbsp;*/<br>public class TestJDOM {</p>
<p>&nbsp;public static void main(String[] args)<br>&nbsp;{<br>&nbsp;&nbsp;Element rootElement = new Element("MyInfo");<br>&nbsp;&nbsp;Document myDocument = new Document(rootElement);<br>&nbsp;&nbsp;<br>//&nbsp;&nbsp;Attribute rootAttri = new Attribute("comment","introduce myself");<br>//&nbsp;&nbsp;rootElement.setAttribute(rootAttri);<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;rootElement.setAttribute("comment","introduce myself");<br>&nbsp;&nbsp;//rootElement.setAttribute(new Attribute("comment","introduce myself"));<br>//&nbsp;&nbsp;Element sexElement = new Element("sex");<br>//&nbsp;&nbsp;rootElement.addContent(sexElement);<br>&nbsp;&nbsp;<br>//&nbsp;&nbsp;Element nameElement = new Element("name");<br>//&nbsp;&nbsp;nameElement.addContent("kingwong");<br>//&nbsp;&nbsp;rootElement.addContent(nameElement);<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;rootElement.addContent((Content)(new Element("name").addContent("kingwong")));<br>&nbsp;&nbsp;rootElement.addContent(new Element("sex").setAttribute("","male"));<br>&nbsp;&nbsp;rootElement.addContent((Content)(new
Element("contract").addContent((Content)(new
Element("telephone").addContent("87654321")))));<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;rootElement.removeChild("sex");<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());<br>&nbsp;&nbsp;try {<br>&nbsp;&nbsp;&nbsp;xmlOut.output(myDocument,System.out);<br>&nbsp;&nbsp;&nbsp;//xmlOut.output(rootElement.getChild("name"),System.out);<br>&nbsp;&nbsp;&nbsp;//String ōutString = xmlOut.outputString(myDocument);<br>&nbsp;&nbsp;} catch (IOException e) {<br>&nbsp;&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;&nbsp;}<br>&nbsp;}<br>}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>二、XML文档解析<br>&nbsp;&nbsp;&nbsp; JDOM 不光可以很方便的建立XML文档，它的另一个用处是它能够读取并操作现有的 XML 数据。<br>&nbsp;&nbsp;&nbsp;
JDOM的解析器在org.jdom.input.*这个包里，其中的DOMBuilder的功能是将DOM模型的Document解析成JDOM模型的
Document；SAXBuilder的功能是从文件或流中解析出符合JDOM模型的XML树。由于我们经常要从一个文件里读取数据，因此我们应该采用
后者作为解析工具。<br>解析一个xml文档，基本可以看成以下几个步骤：<br>&nbsp;&nbsp;&nbsp; 1.实例化一个合适的解析器对象<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本例中我们使用SAXBuilder:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SAXBuilder sb = new SAXBuilder();<br>&nbsp;&nbsp;&nbsp; 2.以包含XML数据的文件为参数，构建一个文档对象myDocument<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Document myDocument = sb.build(/some/directory/myFile.xml);<br>&nbsp;&nbsp;&nbsp; 3.获到根元素<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Element rootElement = mygetRootElement();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一旦你获取了根元素，你就可以很方便地对它下面的子元素进行操作了，下面对Element对象的一些常用方法作一下简单说明：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getChild("childname") 返回指定名字的子节点,如果同一级有多个同名子节点，则只返回第一个；如果没有返回null值。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getChildren("childname") 返回指定名字的子节点List集合。这样你就可以遍历所有的同一级同名子节点。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAttribute("name") 返回指定属性名字的值。如果没有该属性则返回null,有该属性但是值为空，则返回空字符串。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getChildText("childname") 返回指定子节点的内容文本值。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getText() 返回该元素的内容文本值。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 还有其他没有罗列出来的方法，如果需要的话，可以随时查阅JDOM的在线文档：<a href="http://www.jdom.org/docs/apidocs/index.html"><span style="color: #800080;">http://www.jdom.org/docs/apidocs/index.html</span></a>。当然你可以在你需要的地方添加、删除元素操作，还记得上面的创建XML的方法吗？呵呵~~~<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 学习新东东还是从实例学起最为快捷，下面简单举个例子，就以上面的XML样本代码来学习JDOM的XML解析。本例中读取了样本XML文件里一些属性和content，最后我们还在contact元素里插入了一个新元素&lt;email ="<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#119;&#97;&#110;&#103;&#104;&#117;&#97;&#64;&#99;&#121;&#98;&#101;&#114;&#111;&#98;&#106;&#101;&#99;&#116;&#46;&#99;&#111;&#109;">wanghua@cyberobject.com</a>" /&gt;。尽管我们实现了对于XML的基本操作，细心的朋友可能会<br>有
疑问：如果XML文档的层次稍微复杂一些，如果嵌套多达几十上百层的话（开个玩笑），如果靠这样从根元素一级一级地通过getChild
("childname")来访问子元素的话，将会非常痛苦！是的，的确是这样，但是我们有另一个有力的工具XPath，为什么不用呢？这是后话！先卖个
关子（手敲累啦，下回吧，呵呵）。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>/*<br>&nbsp;* Created on 2004-8-21<br>&nbsp;*<br>&nbsp;* TODO To change the template for this generated file go to<br>&nbsp;* Window - Preferences - Java - Code Style - Code Templates<br>&nbsp;*/<br>package com.cyberobject.study;</p>
<p>import org.jdom.Document;<br>import org.jdom.Element;<br>import org.jdom.input.SAXBuilder;<br>import org.jdom.output.Format;<br>import org.jdom.output.XMLOutputter;</p>
/**<br>&nbsp;* @author kingwong<br>&nbsp;*<br>&nbsp;* TODO To change the template for this generated type comment go to<br>&nbsp;* Window - Preferences - Java - Code Style - Code Templates<br>&nbsp;*/<br>public class TestJDOM2 {<br>&nbsp;public static void main(String[] args){<br>&nbsp;SAXBuilder sb = new SAXBuilder();<br>&nbsp;&nbsp;&nbsp; try<br>&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; Document doc = sb.build("myFile.xml");<br>&nbsp;&nbsp;Element root = doc.getRootElement();<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;String str1 = root.getAttribute("comment");<br>&nbsp;&nbsp;System.out.println("Root Element's comment attribute is : " + str1);<br>&nbsp;&nbsp;String str2 = root.getChild("sex").getAttribute("");<br>&nbsp;&nbsp;System.out.println("sex Element's  attribute is : " + str2);<br>&nbsp;&nbsp;String str3 = root.getChildText("name");<br>&nbsp;&nbsp;System.out.println("name Element's content is :" + str3);<br>&nbsp;&nbsp;String str4 = root.getChild("contact").getChildText("telephone");<br>&nbsp;&nbsp;System.out.println("contact Element's telephone subelement content is : " + str4 + "\n");<br>&nbsp;&nbsp;Element inputElement = root.getChild("contact");&nbsp;<br>&nbsp;&nbsp;inputElement.addContent(new Element("email").setAttribute("","<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#119;&#97;&#110;&#103;&#104;&#117;&#97;&#64;&#99;&#121;&#98;&#101;&#114;&#111;&#98;&#106;&#101;&#99;&#116;&#46;&#99;&#111;&#109;">wanghua@cyberobject.com</a>"));<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());<br>&nbsp;&nbsp;&nbsp;&nbsp; String ōutStr = xmlOut.outputString(root);<br>&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(outStr);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; catch(Exception e)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<br>&nbsp;&nbsp;&nbsp; }<br>}<br>}  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94787.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:00 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94787.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Reactor模式和NIO</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94785.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:59:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94785.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94785.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94785.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94785.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94785.html</trackback:ping><description><![CDATA[		<br>
<p>本文可看成是对<a href="http://gee.cs.oswego.edu/" target="_blank">Doug
Lea Scalable IO in Java</a>一文的翻译。</p>
<p>当前分布式计算　Web Services盛行天下，这些网络服务的底层都离不开对socket的操作。他们都有一个共同的结构：<br>
1. Read request<br>
2. Decode request<br>
3. Process service<br>
4. Encode reply<br>
5. Send reply</p>
<p>经典的网络服务的设计如下图，在每个线程中完成对数据的处理：<br><img src="http://www.jdon.com/concurrent/images/classic.jpg" height="167" width="400"></p>
<p>但这种模式在用户负载增加时，性能将下降非常的快。我们需要重新寻找一个新的方案，保持数据处理的流畅，很显然，事件触发机制是最好的解决办法，当有事件发生时，会触动handler,然后开始数据的处理。</p>
<p>Reactor模式类似于AWT中的Event处理：<br><img src="http://www.jdon.com/concurrent/images/awt.jpg" height="271" width="345"></p>
<p>
<span style="font-weight: bold;">Reactor模式参与者</span>
</p>
<p>1.Reactor 负责响应IO事件，一旦发生，广播发送给相应的Handler去处理,这类似于AWT的thread<br>
2.Handler 是负责非堵塞行为，类似于AWT ActionListeners；同时负责将handlers与event事件绑定，类似于AWT
addActionListener</p>
<p>如图：<br><img src="http://www.jdon.com/concurrent/images/reactor.jpg" height="174" width="400"></p>
<p>Java的NIO为reactor模式提供了实现的基础机制，它的Selector当发现某个channel有数据时，会通过SlectorKey来告知我们，在此我们实现事件和handler的绑定。</p>
<p>我们来看看Reactor模式代码:</p>
<table bgcolor="#cccccc" border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <br>
            public class Reactor implements Runnable{
            <p> 　　final Selector selector;<br>
            final ServerSocketChannel serverSocket;<br></p>
            <p>　　Reactor(int port) throws IOException {<br>
            selector = Selector.open();<br>
            serverSocket = ServerSocketChannel.open();<br>
            InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);<br>
            serverSocket.socket().bind(address);<br><br>
            serverSocket.configureBlocking(false);<br>
            //向selector注册该channel<br>
            Selecti sk =serverSocket.register(selector,Selecti.OP_ACCEPT);<br><br>
            logger.debug("--&gt;Start serverSocket.register!");<br><br>
            //利用sk的attache功能绑定Acceptor 如果有事情，触发Acceptor<br>
            sk.attach(new Acceptor());<br>
            logger.debug("--&gt;attach(new Acceptor()!");<br>
            }</p>
            <p><br>
            public void run() { // normally in a new Thread<br>
            try {<br>
            while (!Thread.interrupted())<br>
            {<br>
            selector.select();<br>
            Set selected = selector.selectedKeys();<br>
            Iterator it = selected.iterator();<br>
            //Selector如果发现channel有OP_ACCEPT或READ事件发生，下列遍历就会进行。<br>
            while (it.hasNext())<br>
            //来一个事件 第一次触发一个accepter线程<br>
            //以后触发SocketReadHandler<br>
            dispatch((Selecti)(it.next()));<br>
            selected.clear();<br>
            }<br>
            }catch (IOException ex) {<br>
            logger.debug("reactor stop!"+ex);<br>
            }<br>
            }</p>
            <p> 　　//运行Acceptor或SocketReadHandler<br>
            void dispatch(Selecti k) {<br>
            Runnable r = (Runnable)(k.attachment());<br>
            if (r != null){<br>
            // r.run();</p>
            <p> 　　　　}<br>
            } </p>
            <p> 　　class Acceptor implements Runnable { // inner<br>
            public void run() {<br>
            try {<br>
            logger.debug("--&gt;ready for accept!");<br>
            SocketChannel c = serverSocket.accept();<br>
            if (c != null)<br>
            //调用Handler来处理channel<br>
            new SocketReadHandler(selector, c);<br>
            }<br>
            catch(IOException ex) {<br>
            logger.debug("accept stop!"+ex);<br>
            }<br>
            }<br>
            }<br>
            }<br></p>
            </td>
        </tr>
    </tbody>
</table>
<p>以上代码中巧妙使用了SocketChannel的attach功能，将Hanlder和可能会发生事件的channel链接在一起，当发生事件时，可以立即触发相应链接的Handler。</p>
<p>再看看Handler代码:</p>
<table bgcolor="#cccccc" border="0" cellpadding="0" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>public class SocketReadHandler implements Runnable {
            <p> 　　public static Logger logger = Logger.getLogger(SocketReadHandler.class);</p>
            <p> 　　private Test test=new Test();</p>
            <p> 　　final SocketChannel socket;<br>
            final Selecti sk;<br><br>
            static final int READING = 0, SENDING = 1;<br>
            int state = READING;</p>
            <p> 　　public SocketReadHandler(Selector sel, SocketChannel
            c)<br>
            throws IOException {</p>
            <p> 　　　　socket = c;</p>
            <p> 　　　　socket.configureBlocking(false);<br>
            sk = socket.register(sel, 0);</p>
            <p> 　　　　//将Selecti绑定为本Handler 下一步有事件触发时，将调用本类的run方法。<br>
            //参看dispatch(Selecti k)<br>
            sk.attach(this);<br><br>
            //同时将Selecti标记为可读，以便读取。<br>
            sk.interestOps(Selecti.OP_READ);<br>
            sel.wakeup();<br>
            }</p>
            <p> 　　public void run() {<br>
            try{<br>
            // test.read(socket,input);<br>
            readRequest() ;<br>
            }catch(Exception ex){<br>
            logger.debug("readRequest error"+ex);<br>
            }<br>
            }</p>
            <p><br>
            /**<br>
            * 处理读取data<br>
            * @param key<br>
            * @throws Exception<br>
            */<br>
            private void readRequest() throws Exception {</p>
            <p> 　　ByteBuffer input = ByteBuffer.allocate(1024);<br>
            input.clear();<br>
            try{</p>
            <p> 　　　　int bytesRead = socket.read(input);</p>
            <p>　　　　...... </p>
            <p>　　　　//激活线程池 处理这些request<br>
            requestHandle(new Request(socket,btt));</p>
            <p>　　　　.....</p>
            <p><br>
            }catch(Exception e) {<br>
            }<br><br>
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<p>注意在Handler里面又执行了一次attach，这样，覆盖前面的Acceptor，下次该Handler又有READ事件发生时，将直接触发Handler.从而开始了数据的读　处理　写　发出等流程处理。</p>
<p>将数据读出后，可以将这些数据处理线程做成一个线程池，这样，数据读出后，立即扔到线程池中，这样加速处理速度：</p>
<p>
<img src="http://www.jdon.com/concurrent/images/pool.jpg" height="332" width="500">
</p>
<p>更进一步，我们可以使用多个Selector分别处理连接和读事件。</p>
<p>一个高性能的Java网络服务机制就要形成，激动人心的集群并行计算即将实现。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94785.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:59 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94785.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 网络文件传输</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94784.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:58:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94784.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94784.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94784.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94784.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94784.html</trackback:ping><description><![CDATA[<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">读者范围</span>
<span lang="EN-US">:<o:p></o:p></span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">本文是一篇简短入门文章</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">本文假设读者对</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">系统和</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">的网络系统有所了解</span>
<span lang="EN-US">.</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">正文</span>
<span lang="EN-US">:<o:p></o:p></span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">关于文件传输的问题</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">实际也是一种</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">读写的基本问题</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">对于网络而言也是一种</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">读写问题</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">因此所谓网络的文件传输实际是两种</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">问题的综合讨论</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">这里我们首先分析一个图示</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">然后围绕这个图示来讨论</span>
<span lang="EN-US">:</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<v:shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600">
<v:stroke joinstyle="miter">
</v:stroke>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0">
</v:f>
<v:f eqn="sum @0 1 0">
</v:f>
<v:f eqn="sum 0 0 @1">
</v:f>
<v:f eqn="prod @2 1 2">
</v:f>
<v:f eqn="prod @3 21600 pixelWidth">
</v:f>
<v:f eqn="prod @3 21600 pixelHeight">
</v:f>
<v:f eqn="sum @0 0 1">
</v:f>
<v:f eqn="prod @6 1 2">
</v:f>
<v:f eqn="prod @7 21600 pixelWidth">
</v:f>
<v:f eqn="sum @8 21600 0">
</v:f>
<v:f eqn="prod @7 21600 pixelHeight">
</v:f>
<v:f eqn="sum @10 21600 0">
</v:f>
</v:formulas>
<v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f">
</v:path>
<o:lock aspectratio="t" v:ext="edit">
</o:lock>
</v:shapetype>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>
<img alt="" src="http://kuxiaoku.52blog.net/c:%5CDocuments%20and%20Settings%5CAdministrator%5CMy%20Documents%5Cnet001.JPG" align="bottom" border="0" hspace="0">&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">图</span>
<span lang="EN-US">1</span>
</span>
<span lang="EN-US">:</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">分析图</span>
<span lang="EN-US">1</span>
<span style="font-family: 宋体;">我们基本可以知道从服务器文件系统中通过流把文件中的数据写入到服务器的进程中</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">然后把进程中的数据通过网络</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">系统传递到客户机</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">这个阶段</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">网络中的数据以字节流的形式保存</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">当该字节流被客户进程接受后</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">客户进程通过客户本地文件流写入客户本地的文件系统中</span>
<span lang="EN-US">.</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">根据以上分析</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">我们基本可以确定我所需要处理的问题了</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">首先我们需要可以对本地文件系统</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">操作的操作接口</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">然后是一个可以对网络</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">系统进行操作的操作接口</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">已经一个可以把数据包装成字节流的操作接口</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">他们分别可以提供客户和服务器两个进程进行读写的操作</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">如下图所示</span>
<span lang="EN-US">:<br></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">
<img alt="" src="http://kuxiaoku.52blog.net/c:%5CDocuments%20and%20Settings%5CAdministrator%5CMy%20Documents%5Cnet002.JPG" align="bottom" border="0" hspace="0">
<br>
<br>图</span>
<span lang="EN-US">2:<o:p></o:p></span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">根据以上分析</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">我们可以把问题归结到对以下编程接口的需求上</span>
<span lang="EN-US">:</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt;">
<span lang="EN-US">
<span>1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span style="font-family: 宋体;">字节包装器和字节解包器</span>
<span lang="EN-US">,</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt;">
<span lang="EN-US">
<span>2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span style="font-family: 宋体;">网络传输器和网络接收器</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt;">
<span lang="EN-US">
<span>3.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span style="font-family: 宋体;">本地文件读</span>
<span lang="EN-US">/</span>
<span style="font-family: 宋体;">写器</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 18pt;">
<span style="font-family: 宋体;">而这些</span>
<span lang="EN-US">Java</span>
<span style="font-family: 宋体;">本身的</span>
<span lang="EN-US">API</span>
<span style="font-family: 宋体;">就已经提供</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">他们都被包装到</span>
<span lang="EN-US">java.io</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">java.net</span>
<span style="font-family: 宋体;">这两个包里</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">这里我提供一个基于</span>
<span lang="EN-US">TCP/IP</span>
<span style="font-family: 宋体;">的实现版本</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">使用基于连接的方式来完成工作</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">我们首先介绍几个相关的</span>
<span lang="EN-US">JDK</span>
<span style="font-family: 宋体;">中的类来完成以上任务</span>
<span lang="EN-US">,</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
<span lang="EN-US">
<span>1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span lang="EN-US">DataOutputStream</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">DataInputStream</span>
<span style="font-family: 宋体;">实现类提供了上面的字节包装和解包器的实现</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
<span lang="EN-US">
<span>2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span lang="EN-US">ServerSocket</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">Socekt</span>
<span style="font-family: 宋体;">提供了基于连接的网络传输和接受接口</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
<span lang="EN-US">
<span>3.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
</span>
<span lang="EN-US">File,FileInputStream</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN-US">FileOutputStream</span>
<span style="font-family: 宋体;">提供了基本的本地文件输入输出接口</span>
<span lang="EN-US">.</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">服务器端实现代码</span>
<span lang="EN-US">:<o:p></o:p></span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">import java.io.*;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">import java.net.*;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">public class FileServer{<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>public static void main(String[] args)throws Exception{<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建文件流用来读取文件中的数据</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>File file=new File("lishengjie.jpg");<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>FileInputStream fos=new FileInputStream(file);<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建网络服务器接受客户请求</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ServerSocket ss=new ServerSocket(3108);<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Socket client=ss.accept();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建网络输出流并提供数据包装器</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>OutputStream netOut=client.getOutputStream();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>OutputStream doc=new DataOutputStream(new BufferedOutputStream(netOut));<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建文件读取缓冲区</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>byte[] buf=new byte[2048];<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int num=fos.read(buf);<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>while(num!=(-1)){//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">是否读完文件</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>doc.write(buf,0,num);//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">把文件数据写出网络缓冲区</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>doc.flush();//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">刷新缓冲区把数据写往客户端</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>num=fos.read(buf);//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">继续从文件中读取数据</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>fos.close();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>doc.close();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">客户方实现代码</span>
<span lang="EN-US">:<o:p></o:p></span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">import java.io.*;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">import java.net.*;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">public class FileClient{<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>public static void main(String[] args)throws Exception{<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">使用本地文件系统接受网络数据并存为新文件</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>File file=new File("newFile.jpg");<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>file.createNewFile();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RandomAccessFile raf=new RandomAccessFile(file,"rw");<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">通过</span>
<span lang="EN-US">Socket</span>
</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">连接文件服务器</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Socket server=new Socket(InetAddress.getLocalHost(),3108);<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建网络接受流接受服务器文件数据</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>InputStream netIn=server.getInputStream();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>InputStream in=new DataInputStream(new BufferedInputStream(netIn));<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">创建缓冲区缓冲网络数据</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>byte[] buf=new byte[2048];<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int num=in.read(buf);<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
<o:p>
</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>while(num!=(-1)){//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">是否读完所有数据</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>raf.write(buf,0,num);//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">将数据写往文件</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>raf.skipBytes(num);//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">顺序写文件字节</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>num=in.read(buf);//</span>
<span style="font-weight: bold;">
<span style="font-family: 宋体;">继续从网络中读取文件</span>
<span lang="EN-US">
<o:p>
</o:p>
</span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>in.close();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>raf.close();<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;" lang="EN-US">}<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">归结以上代码</span>
<span lang="EN-US">:</span>
</p>
<table class="MsoTableGrid" style="border-collapse: collapse;" border="1" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 213.05pt; background-color: transparent;" valign="top" width="284">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
            <span style="font-weight: bold;">
            <span style="font-family: 宋体;">服务器</span>
            <span lang="EN-US">
            <o:p>
            </o:p>
            </span>
            </span>
            </p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 213.05pt; background-color: transparent;" valign="top" width="284">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
            <span style="font-weight: bold;">
            <span style="font-family: 宋体;">客户端</span>
            <span lang="EN-US">
            <o:p>
            </o:p>
            </span>
            </span>
            </p>
            </td>
        </tr>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 213.05pt; background-color: transparent;" valign="top" width="284">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">服务器从本地文件系统读取文件</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">服务器创建网络服务连接</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>3.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">服务器提供数据包装器</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>4.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">服务器将本地文件写入数据包装器</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>5.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">服务器通过包装器写入到网络</span>
            </p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 213.05pt; background-color: transparent;" valign="top" width="284">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>1.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">客户端建立新文件准备存储来自网络的数据</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>2.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">客户端连接服务器</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>3.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">客户端通过网络接受服务器数据并进行数据解包</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>4.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">客户端将数据写入缓冲区</span>
            </p>
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
            <span lang="EN-US">
            <span>5.<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
            </span>
            <span style="font-family: 宋体;">客户端从缓冲区把数据写入客户本地文件</span>
            </p>
            </td>
        </tr>
    </tbody>
</table>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN-US">
<o:p>&nbsp;</o:p>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-weight: bold;">
<span style="font-family: 宋体;">总结</span>
<span lang="EN-US">:<o:p></o:p></span>
</span>
</p>
<span lang="EN-US">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">事实上</span>
<span lang="EN-US">java</span>
<span style="font-family: 宋体;">的开发环境为我们提供大多数的编程接口</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">为我们简化了开发工作量</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">我们通过</span>
<span lang="EN-US">java</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN-US">IO</span>
<span style="font-family: 宋体;">接口所提供的文件</span>
<span lang="EN-US">,</span>
<span style="font-family: 宋体;">数据包装器等接口非常方便的解决了我们上面的开发工作量</span>
<span lang="EN-US">.</span>
<span style="font-family: 宋体;">同时在</span>
<span lang="EN-US">java</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN-US">net</span>
<span style="font-family: 宋体;">接口所提供的套接字也使得基于连接的数据接受和发送成为非常容易的工作</span>
<span lang="EN-US">.</span> <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94784.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:58 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94784.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java socket资料</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94783.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:56:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94783.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94783.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94783.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94783.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94783.html</trackback:ping><description><![CDATA[<p>Java套接字编程(上)<br>用Java开发网络软件非常方便和强大，Java的这种力量来源于他独有的一套强大的用于网络的
API，这些API是一系列的类和接口，均位于包java.net和javax.net中。在这篇文章中我们将介绍套接字(Socket)慨念，同时以实
例说明如何使用Network API操纵套接字，在完成本文后，你就可以编写网络低端通讯软件。</p>
<p>　　什么是套接字(Socket)？</p>
<p>
Network API是典型的用于基于TCP/IP网络Java程序与其他程序通讯，Network
API依靠Socket进行通讯。Socket可以看成在两个程序进行通讯连接中的一个端点，一个程序将一段信息写入Socket中，该Socket将这
段信息发送给另外一个Socket中，使这段信息能传送到其他程序中。</p>
<p>　　我们来分析一下，Host
A上的程序A将一段信息写入Socket中，Socket的内容被Host A的网络管理软件访问，并将这段信息通过Host
A的网络接口卡发送到Host B，Host B的网络接口卡接收到这段信息后，传送给Host
B的网络管理软件，网络管理软件将这段信息保存在Host B的Socket中，然后程序B才能在Socket中阅读这段信息。</p>
<p>　　假设在
图1的网络中添加第三个主机Host C，那么Host A怎么知道信息被正确传送到Host B而不是被传送到Host
C中了呢？基于TCP/IP网络中的每一个主机均被赋予了一个唯一的IP地址，IP地址是一个32位的无符号整数，由于没有转变成二进制，因此通常以小数
点分隔，如：198.163.227.6，正如所见IP地址均由四个部分组成，每个部分的范围都是0-255，以表示8位地址。</p>
<p>　　值得注意的是IP地址都是32位地址，这是IP协议版本4（简称Ipv4）规定的，目前由于IPv4地址已近耗尽，所以IPv6地址正逐渐代替Ipv4地址，Ipv6地址则是128位无符号整数。</p>
<p>
假设第二个程序被加入图1的网络的Host B中，那么由Host
A传来的信息如何能被正确的传给程序B而不是传给新加入的程序呢？这是因为每一个基于TCP/IP网络通讯的程序都被赋予了唯一的端口和端口号，端口是一
个信息缓冲区，用于保留Socket中的输入/输出信息，端口号是一个16位无符号整数，范围是0-65535，以区别主机上的每一个程序（端口号就像房
屋中的房间号），低于256的短口号保留给标准应用程序，比如pop3的端口号就是110，每一个套接字都组合进了IP地址、端口、端口号，这样形成的整
体就可以区别每一个套接字t，下面我们就来谈谈两种套接字：流套接字和自寻址数据套接字。</p>
<p>　　流套接字（Stream Socket）</p>
<p>
无论何时，在两个网络应用程序之间发送和接收信息时都需要建立一个可靠的连接，流套接字依靠TCP协议来保证信息正确到达目的地，实际上，IP包有可能
在网络中丢失或者在传送过程中发生错误，任何一种情况发生，作为接受方的
TCP将联系发送方TCP重新发送这个IP包。这就是所谓的在两个流套接字之间建立可靠的连接。</p>
<p>　　流套接字在C/S程序中扮演一个必需的角色，客户机程序（需要访问某些服务的网络应用程序）创建一个扮演服务器程序的主机的IP地址和服务器程序（为客户端应用程序提供服务的网络应用程序）的端口号的流套接字对象。</p>
<p>
客户端流套接字的初始化代码将IP地址和端口号传递给客户端主机的网络管理软件，管理软件将IP地址和端口号通过NIC传递给服务器端主机；服务器端主
机读到经过NIC传递来的数据，然后查看服务器程序是否处于监听状态，这种监听依然是通过套接字和端口来进行的；如果服务器程序处于监听状态，那么服务器
端网络管理软件就向客户机网络管理软件发出一个积极的响应信号，接收到响应信号后，客户端流套接字初始化代码就给客户程序建立一个端口号，并将这个端口号
传递给服务器程序的套接字（服务器程序将使用这个端口号识别传来的信息是否是属于客户程序）同时完成流套接字的初始化。</p>
<p>　　如果服务器程序
没有处于监听状态，那么服务器端网络管理软件将给客户端传递一个消极信号，收到这个消极信号后，客户程序的流套接字初始化代码将抛出一个异常对象并且不建
立通讯连接，也不创建流套接字对象。这种情形就像打电话一样，当有人的时候通讯建立，否则电话将被挂起。</p>
<p>　　这部分的工作包括了相关联的三
个类：InetAddress, Socket, 和 ServerSocket。
InetAddress对象描绘了32位或128位IP地址，Socket对象代表了客户程序流套接字，ServerSocket代表了服务程序流套接
字，所有这三个类均位于包java.net中。</p>
<p>　　InetAddress类</p>
<p>　　InetAddress类在网络API套接
字编程中扮演了一个重要角色。参数传递给流套接字类和自寻址套接字类构造器或非构造器方法。InetAddress描述了32位或64位IP地址，要完成
这个功能，InetAddress类主要依靠两个支持类Inet4Address 和
Inet6Address，这三个类是继承关系，InetAddrress是父类，Inet4Address 和 Inet6Address是子类。</p>
<p>　　由于InetAddress类只有一个构造函数，而且不能传递参数，所以不能直接创建InetAddress对象，比如下面的做法就是错误的：<br>InetAddress ia = new InetAddress ();<br>　　但我们可以通过下面的5个工厂方法创建来创建一个InetAddress对象或InetAddress数组：</p>
<p>
. getAllByName(String
host)方法返回一个InetAddress对象的引用，每个对象包含一个表示相应主机名的单独的IP地址，这个IP地址是通过host参数传递的，对
于指定的主机如果没有IP地址存在那么这个方法将抛出一个UnknownHostException 异常对象。</p>
<p>　　　.
getByAddress(byte []
addr)方法返回一个InetAddress对象的引用，这个对象包含了一个Ipv4地址或Ipv6地址，Ipv4地址是一个4字节数组，Ipv6地址
是一个16字节地址数组，如果返回的数组既不是4字节的也不是16字节的，那么方法将会抛出一个UnknownHostException异常对象。</p>
<p>
. getByAddress(String host, byte []
addr)方法返回一个InetAddress对象的引用，这个InetAddress对象包含了一个由host和4字节的addr数组指定的IP地址，
或者是host和16字节的addr数组指定的IP地址，如果这个数组既不是4字节的也不是16位字节的，那么该方法将抛出一个
UnknownHostException异常对象。</p>
<p>　　　. getByName(String host)方法返回一个InetAddress对象，该对象包含了一个与host参数指定的主机相对应的IP地址，对于指定的主机如果没有IP地址存在，那么方法将抛出一个UnknownHostException异常对象。</p>
<p>　　　. getLocalHost()方法返回一个InetAddress对象，这个对象包含了本地机的IP地址，考虑到本地主机既是客户程序主机又是服务器程序主机，为避免混乱，我们将客户程序主机称为客户主机，将服务器程序主机称为服务器主机。</p>
<p>
上面讲到的方法均提到返回一个或多个InetAddress对象的引用，实际上每一个方法都要返回一个或多个
Inet4Address/Inet6Address对象的引用，调用者不需要知道引用的子类型，相反调用者可以使用返回的引用调用
InetAddress对象的非静态方法，包括子类型的多态以确保重载方法被调用。</p>
<p>　　InetAddress和它的子类型对象处理主机名
到主机IPv4或IPv6地址的转换，要完成这个转换需要使用域名系统，下面的代码示范了如何通过调用getByName(String
host)方法获得InetAddress子类对象的方法，这个对象包含了与host参数相对应的IP地址：<br>InetAddress ia = InetAddress.getByName (<a href="http://www.javajeff.com/">www.javajeff.com</a>));</p>
<p>
一但获得了InetAddress子类对象的引用就可以调用InetAddress的各种方法来获得InetAddress子类对象中的IP地址信息，
比如，可以通过调用getCanonicalHostName()从域名服务中获得标准的主机名；getHostAddress()获得IP地址，
getHostName()获得主机名，isLoopbackAddress()判断IP地址是否是一个loopback地址。</p>
<p>　　List1 是一段示范代码：InetAddressDemo<br>// InetAddressDemo.java</p>
<p>import java.net.*;</p>
<p>class InetAddressDemo<br>{<br>　public static void main (String [] args) throws UnknownHostException<br>　{<br>　　String host = "localhost";</p>
<p>　　if (args.length == 1)<br>　　　host = args [0];</p>
<p>　　InetAddress ia = InetAddress.getByName (host);</p>
<p>　　System.out.println ("Canonical Host Name = " +<br>　　　　　　　　ia.getCanonicalHostName ());<br>　　System.out.println ("Host Address = " +<br>　　　　　　　　ia.getHostAddress ());<br>　　System.out.println ("Host Name = " +<br>　　　　　　　　ia.getHostName ());<br>　　System.out.println ("Is Loopback Address = " +<br>　　　　　　　　ia.isLoopbackAddress ());<br>　}<br>}</p>
<p>　　当无命令行参数时，代码输出类似下面的结果：<br>Canonical Host Name = localhost<br>Host Address = 127.0.0.1<br>Host Name = localhost<br>Is Loopback Address = true </p>
<p>
InetAddressDemo给了你一个指定主机名作为命令行参数的选择，如果没有主机名被指定，那么将使用localhost(客户机的)，
InetAddressDemo通过调用getByName(String
host)方法获得一个InetAddress子类对象的引用，通过这个引用获得了标准主机名，主机地址，主机名以及IP地址是否是loopback地址
的输出。</p>
<p>&nbsp;</p>
<p>
<br>Socket类</p>
<p>　　当客户程序需要与服务器程序通讯的时候，客户程序在客户机创建一个
socket对象，Socket类有几个构造函数。两个常用的构造函数是 Socket(InetAddress addr, int port) 和
Socket(String host, int
port)，两个构造函数都创建了一个基于Socket的连接服务器端流套接字的流套接字。对于第一个InetAddress子类对象通过addr参数获
得服务器主机的IP地址，对于第二个函数host参数包被分配到InetAddress对象中，如果没有IP地址与host参数相一致，那么将抛出
UnknownHostException异常对象。两个函数都通过参数port获得服务器的端口号。假设已经建立连接了，网络API将在客户端基于
Socket的流套接字中捆绑客户程序的IP地址和任意一个端口号，否则两个函数都会抛出一个IOException对象。</p>
<p>　　如果创建了
一个Socket对象，那么它可能通过调用Socket的
getInputStream()方法从服务程序获得输入流读传送来的信息，也可能通过调用Socket的
getOutputStream()方法获得输出流来发送消息。在读写活动完成之后，客户程序调用close()方法关闭流和流套接字，下面的代码创建了
一个服务程序主机地址为198.163.227.6，端口号为13的Socket对象，然后从这个新创建的Socket对象中读取输入流，然后再关闭流和
Socket对象。<br>Socket s = new Socket ("198.163.227.6", 13);<br>InputStream is = s.getInputStream ();<br>// Read from the stream.<br>is.close ();<br>s.close ();</p>
<p>
接下面我们将示范一个流套接字的客户程序，这个程序将创建一个Socket对象，Socket将访问运行在指定主机端口10000上的服务程序，如果访
问成功客户程序将给服务程序发送一系列命令并打印服务程序的响应。List2使我们创建的程序SSClient的源代码：</p>
<p>　　Listing 2: SSClient.java<br>// SSClient.java</p>
<p>import java.io.*;<br>import java.net.*; </p>
<p>class SSClient<br>{<br>　public static void main (String [] args)<br>　{<br>　　String host = "localhost";</p>
<p>　　// If user specifies a command-line argument, that argument<br>　　// represents the host name.</p>
<p>　　if (args.length == 1)<br>　　　host = args [0];</p>
<p>　　BufferedReader br = null;<br>　　PrintWriter pw = null;<br>　　Socket s = null;</p>
<p>　　try<br>　　{<br>　　　// Create a socket that attempts to connect to the server<br>　　　// program on the host at port 10000.</p>
<p>　　　s = new Socket (host, 10000);</p>
<p>　　　// Create an input stream reader that chains to the socket's<br>　　　// byte-oriented input stream. The input stream reader<br>　　　// converts bytes read from the socket to characters. The<br>　　　// conversion is based on the platform's default character<br>　　　// set.</p>
<p>　　　InputStreamReader isr;<br>　　　isr = new InputStreamReader (s.getInputStream ());</p>
<p>　　　// Create a buffered reader that chains to the input stream<br>　　　// reader. The buffered reader supplies a convenient method<br>　　　// for reading entire lines of text.</p>
<p>　　　br = new BufferedReader (isr);</p>
<p>　　　// Create a print writer that chains to the socket's byte-<br>　　　// oriented output stream. The print writer creates an<br>　　　// intermediate output stream writer that converts<br>　　　// characters sent to the socket to bytes. The conversion<br>　　　// is based on the platform's default character set.</p>
<p>　　　pw = new PrintWriter (s.getOutputStream (), true);</p>
<p>　　　// Send the DATE command to the server.</p>
<p>　　　pw.println ("DATE");</p>
<p>　　　// Obtain and print the current date/time.</p>
<p>　　　System.out.println (br.readLine ());<br>　　　// Send the PAUSE command to the server. This allows several<br>　　　// clients to start and verifies that the server is spawning<br>　　　// multiple threads.</p>
<p>　　　pw.println ("PAUSE");<br>　　　// Send the DOW command to the server.</p>
<p>　　　pw.println ("DOW");</p>
<p>　　　// Obtain and print the current day of week.</p>
<p>　　　System.out.println (br.readLine ());</p>
<p>　　　// Send the DOM command to the server.<br>　<br>　　　pw.println ("DOM");</p>
<p>　　　// Obtain and print the current day of month.</p>
<p>　　　System.out.println (br.readLine ());</p>
<p>　　　// Send the DOY command to the server.</p>
<p>　　　pw.println ("DOY");</p>
<p>　　　// Obtain and print the current day of year.</p>
<p>　　　System.out.println (br.readLine ());<br>　　}<br>　　catch (IOException e)<br>　　{<br>　　　System.out.println (e.toString ());<br>　　}<br>　　finally<br>　　{<br>　　　try<br>　　　{<br>　　　　if (br != null)<br>　　　　　br.close ();</p>
<p>　　　　if (pw != null)<br>　　　　　pw.close ();</p>
<p>　　　　if (s != null)<br>　　　　　s.close ();<br>　　　}<br>　　　catch (IOException e)<br>　　　{<br>　　　　}<br>　　}<br>　}<br>}<br>　　运行这段程序将会得到下面的结果：<br>Tue Jan 29 18:11:51 CST 2002<br>TUESDAY<br>29<br>29</p>
<p>SSClient
创建了一个Socket对象与运行在主机端口10000的服务程序联系，主机的IP地址由host变量确定。SSClient将获得Socket的输入输
出流，围绕BufferedReader的输入流和PrintWriter的输出流对字符串进行读写操作就变得非常容易，SSClient个服务程序发出
各种date/time命令并得到响应，每个响应均被打印，一旦最后一个响应被打印，将执行Try/Catch/Finally结构的Finally子
串，Finally子串将在关闭Socket之前关闭BufferedReader 和 PrintWriter。</p>
<p>　　在SSClient源代码编译完成后，可以输入java SSClient 来执行这段程序，如果有合适的程序运行在不同的主机上，采用主机名/IP地址为参数的输入方式，比如<a href="http://www.sina.com.cn/">www.sina.com.cn</a>是运行服务器程序的主机，那么输入方式就是java SSClient <a href="http://www.sina.com.cn/">www.sina.com.cn</a>。</p>
<p>　　技巧</p>
<p>
Socket类包含了许多有用的方法。比如getLocalAddress()将返回一个包含客户程序IP地址的InetAddress子类对象的引
用;getLocalPort()将返回客户程序的端口号;getInetAddress()将返回一个包含服务器IP地址的InetAddress子类
对象的引用；getPort()将返回服务程序的端口号。 </p>
<p>&nbsp;</p>
<p>ServerSocket类</p>
<p>　　由于
SSClient使用了流套接字，所以服务程序也要使用流套接字。这就要创建一个ServerSocket对象，ServerSocket有几个构造函
数，最简单的是ServerSocket(int port)，当使用ServerSocket(int
port)创建一个ServerSocket对象，port参数传递端口号，这个端口就是服务器监听连接请求的端口，如果在这时出现错误将抛出
IOException异常对象，否则将创建ServerSocket对象并开始准备接收连接请求。</p>
<p>　　接下来服务程序进入无限循环之中，
无限循环从调用ServerSocket的accept()方法开始，在调用开始后accept()方法将导致调用线程阻塞直到连接建立。在建立连接后
accept()返回一个最近创建的Socket对象，该Socket对象绑定了客户程序的IP地址或端口号。</p>
<p>　　由于存在单个服务程序与
多个客户程序通讯的可能，所以服务程序响应客户程序不应该花很多时间，否则客户程序在得到服务前有可能花很多时间来等待通讯的建立，然而服务程序和客户程
序的会话有可能是很长的（这与电话类似），因此为加快对客户程序连接请求的响应，典型的方法是服务器主机运行一个后台线程，这个后台线程处理服务程序和客
户程序的通讯。</p>
<p>　　为了示范我们在上面谈到的慨念并完成SSClient程序，下面我们创建一个SSServer程序，程序将创建一个
ServerSocket对象来监听端口10000的连接请求，如果成功服务程序将等待连接输入，开始一个线程处理连接，并响应来自客户程序的命令。下面
就是这段程序的代码：</p>
<p>　　Listing 3: SSServer.java <br>// SSServer.java</p>
<p>import java.io.*;<br>import java.net.*;<br>import java.util.*;</p>
<p>class SSServer<br>{<br>　public static void main (String [] args) throws IOException<br>　{　<br>　　System.out.println ("Server starting...\n"); </p>
<p>　　// Create a server socket that listens for incoming connection<br>　　// requests on port 10000.</p>
<p>　　ServerSocket server = new ServerSocket (10000);</p>
<p>　　while (true)<br>　　{<br>　　　// Listen for incoming connection requests from client<br>　　　// programs, establish a connection, and return a Socket<br>　　　// object that represents this connection.</p>
<p>　　　Socket s = server.accept ();</p>
<p>　　　System.out.println ("Accepting Connection...\n");</p>
<p>　　　// Start a thread to handle the connection.</p>
<p>　　　new ServerThread (s).start ();<br>　　}<br>　}<br>}</p>
<p>class ServerThread extends Thread<br>{<br>　private Socket s;</p>
<p>　ServerThread (Socket s)<br>　{<br>　　this.s = s;<br>　}</p>
<p>　public void run ()<br>　{<br>　　BufferedReader br = null;<br>　　PrintWriter pw = null;</p>
<p>　　try<br>　　{<br>　　　// Create an input stream reader that chains to the socket's<br>　　　// byte-oriented input stream. The input stream reader<br>　　　// converts bytes read from the socket to characters. The<br>　　　// conversion is based on the platform's default character<br>　　　// set.</p>
<p>　　　InputStreamReader isr;<br>　　　isr = new InputStreamReader (s.getInputStream ());</p>
<p>　　　// Create a buffered reader that chains to the input stream<br>　　　// reader. The buffered reader supplies a convenient method<br>　　　// for reading entire lines of text.</p>
<p>　　　br = new BufferedReader (isr);</p>
<p>　　　// Create a print writer that chains to the socket's byte-<br>　　　// oriented output stream. The print writer creates an<br>　　　// intermediate output stream writer that converts<br>　　　// characters sent to the socket to bytes. The conversion<br>　　　// is based on the platform's default character set.</p>
<p>　　　pw = new PrintWriter (s.getOutputStream (), true);</p>
<p>　　　// Create a calendar that makes it possible to obtain date<br>　　　// and time information.</p>
<p>　　　Calendar c = Calendar.getInstance ();</p>
<p>　　　// Because the client program may send multiple commands, a<br>　　　// loop is required. Keep looping until the client either<br>　　　// explicitly requests termination by sending a command<br>　　　// beginning with letters BYE or implicitly requests<br>　　　// termination by closing its output stream.</p>
<p>　　　do<br>　　　{<br>　　　　// Obtain the client program's next command.</p>
<p>　　　　String cmd = br.readLine ();</p>
<p>　　　　// Exit if client program has closed its output stream.</p>
<p>　　　　if (cmd == null)<br>　　　　　break;<br>　　<br>　　　　// Convert command to uppercase, for ease of comparison.</p>
<p>　　　　cmd = cmd.toUpperCase ();</p>
<p>　　　　// If client program sends BYE command, terminate.</p>
<p>　　　　if (cmd.startsWith ("BYE"))<br>　　　　　break;</p>
<p>　　　　// If client program sends DATE or TIME command, return<br>　　　　// current date/time to the client program.</p>
<p>　　　　if (cmd.startsWith ("DATE") || cmd.startsWith ("TIME"))<br>　　　　　pw.println (c.getTime ().toString ());</p>
<p>　　　　// If client program sends DOM (Day Of Month) command,<br>　　　　// return current day of month to the client program.</p>
<p>　　　　if (cmd.startsWith ("DOM"))<br>　　　　　pw.println ("" + c.get (Calendar.DAY_OF_MONTH));</p>
<p>　　　　// If client program sends DOW (Day Of Week) command,<br>　　　　// return current weekday (as a string) to the client<br>　　　　// program.</p>
<p>　　　　if (cmd.startsWith ("DOW"))<br>　　　　　switch (c.get (Calendar.DAY_OF_WEEK))<br>　　　　{<br>　　　　　case Calendar.SUNDAY : pw.println ("SUNDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.MONDAY : pw.println ("MONDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.TUESDAY : pw.println ("TUESDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.WEDNESDAY: pw.println ("WEDNESDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.THURSDAY : pw.println ("THURSDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.FRIDAY : pw.println ("FRIDAY");<br>　　　　　　break;</p>
<p>　　　　　case Calendar.SATURDAY : pw.println ("SATURDAY");<br>　　　　}</p>
<p>　　　　// If client program sends DOY (Day of Year) command,<br>　　　　// return current day of year to the client program.</p>
<p>　　　　if (cmd.startsWith ("DOY"))<br>　　　　　pw.println ("" + c.get (Calendar.DAY_OF_YEAR));</p>
<p>　　　　　// If client program sends PAUSE command, sleep for three<br>　　　　　// seconds.<br>　<br>　　　　if (cmd.startsWith ("PAUSE"))<br>　　　　try<br>　　　　{<br>　　　　　Thread.sleep (3000);<br>　　　　}<br>　　　　catch (InterruptedException e)<br>　　　　{<br>　　　　}<br>　　　}<br>　　　while (true);<br>　　　{<br>　　　catch (IOException e)<br>　　　{<br>　　　　System.out.println (e.toString ());<br>　　　}<br>　　　finally<br>　　　{<br>　　　　System.out.println ("Closing Connection...\n");</p>
<p>　　　　try<br>　　　　{<br>　　　　　if (br != null)<br>　　　　　　br.close ();</p>
<p>　　　　　　if (pw != null)<br>　　　　　　　pw.close ();</p>
<p>　　　　　　if (s != null)<br>　　　　　　　s.close ();<br>　　　　}<br>　　　　catch (IOException e)<br>　　　　{<br>　　　　}<br>　　　}<br>　　}<br>}<br>运行这段程序将得到下面的输出： </p>
<p>&nbsp;</p>
<p>
<br>Server starting...<br>Accepting Connection...<br>Closing Connection...<br>SSServer
的源代码声明了一对类：SSServer
和ServerThread；SSServer的main()方法创建了一个ServerSocket对象来监听端口10000上的连接请求，如果成功,
SSServer进入一个无限循环中，交替调用ServerSocket的 accept()
方法来等待连接请求，同时启动后台线程处理连接（accept()返回的请求）。线程由ServerThread继承的start()方法开始，并执行
ServerThread的run()方法中的代码。</p>
<p>　　一旦run()方法运行，线程将创建BufferedReader,
PrintWriter和 Calendar对象并进入一个循环，这个循环由读（通过BufferedReader的
readLine()）来自客户程序的一行文本开始，文本（命令）存储在cmd引用的string对象中，如果客户程序过早的关闭输出流，会发生什么呢？
答案是：cmd将得不到赋值。</p>
<p>　　注意必须考虑到这种情况：在服务程序正在读输入流时，客户程序关闭了输出流，如果没有对这种情况进行处理，那么程序将产生异常。</p>
<p>　　一旦编译了SSServer的源代码，通过输入Java SSServer来运行程序，在开始运行SSServer后，就可以运行一个或多个SSClient程序。 </p>
<p>自寻址套接字（Datagram Sockets） </p>
<p>
，因为使用流套接字的每个连接均要花费一定的时间，要减少这种开销，网络API提供了第二种套接字：自寻址套接字（datagram
socket），自寻址使用UDP发送寻址信息（从客户程序到服务程序或从服务程序到客户程序），不同的是可以通过自寻址套接字发送多IP信息包，自寻址
信息包含在自寻址包中，此外自寻址包又包含在IP包内，这就将寻址信息长度限制在60000字节内。图2显示了位于IP包内的自寻址包的自寻址信息。 <br>
与TCP保证信息到达信息目的地的方式不同，UDP提供了另外一种方法，如果自寻址信息包没有到达目的地，，那么UDP也不会请求发送者重新发送自寻址
包，这是因为UDP在每一个自寻址包中包含了错误检测信息，在每个自寻址包到达目的地之后UDP只进行简单的错误检查，如果检测失败，UDP将抛弃这个自
寻址包，也不会从发送者那里重新请求替代者，这与通过邮局发送信件相似，发信人在发信之前不需要与收信人建立连接，同样也不能保证信件能到达收信人那里</p>
<p>
自寻址套接字工作包括下面三个类：DatagramPacket, DatagramSocket,和
MulticastSocket。DatagramPacket对象描绘了自寻址包的地址信息，DatagramSocket表示客户程序和服务程序自寻
址套接字，MulticastSocket描绘了能进行多点传送的自寻址套接字，这三个类均位于java.net包内。</p>
<p>　　DatagramPacket类</p>
<p>　　在使用自寻址包之前，你需要首先熟悉DatagramPacket类，地址信息和自寻址包以字节数组的方式同时压缩入这个类创建的对象中</p>
<p>
DatagramPacket有数个构造函数，即使这些构造函数的形式不同，但通常情况下他们都有两个共同的参数：byte [] buffer 和
int length，buffer参数包含了一个对保存自寻址数据包信息的字节数组的引用，length表示字节数组的长度。</p>
<p>　　最简单
的构造函数是DatagramPacket(byte [] buffer, int
length)，这个构造函数确定了自寻址数据包数组和数组的长度，但没有任何自寻址数据包的地址和端口信息，这些信息可以后面通过调用方法
setAddress(InetAddress addr)和setPort(int port)添加上，下面的代码示范了这些函数和方法。 <br>byte [] buffer = new byte [100];<br>DatagramPacket dgp = new DatagramPacket (buffer, buffer.length);<br>InetAddress ia = InetAddress.getByName ("<a href="http://www.disney.com/">www.disney.com</a>");<br>dgp.setAddress (ia);<br>dgp.setPort (6000); // Send datagram packet to port 6000. </p>
<p>&nbsp;</p>
<p>　　如果你更喜欢在调用构造函数的时候同时包括地址和端口号，可以使用DatagramPacket(byte [] buffer, int length, InetAddress addr, int port)函数，下面的代码示范了另外一种选择。 </p>
<p>&nbsp;</p>
<p>
<br>byte [] buffer = new byte [100];<br>InetAddress ia = InetAddress.getByName ("<a href="http://www.disney.com/">www.disney.com</a>");<br>DatagramPacket dgp = new DatagramPacket (buffer, buffer.length, ia, <br>6000); </p>
<p>&nbsp;</p>
<p>
<br>
有时候在创建了DatagramPacket对象后想改变字节数组和他的长度，这时可以通过调用setData(byte [] buffer) 和
setLength(int length)方法来实现。在任何时候都可以通过调用getData()
来得到字节数组的引用，通过调用getLength()来获得字节数组的长度。下面的代码示范了这些方法： </p>
<p>&nbsp;</p>
<p>
<br>byte [] buffer2 = new byte [256];<br>dgp.setData (buffer2);<br>dgp.setLength (buffer2.length); </p>
<p>&nbsp;</p>
<p>
<br>　　关于DatagramPacket的更多信息请参考SDK文档。 <br>DatagramSocket类</p>
<p>
DatagramSocket类在客户端创建自寻址套接字与服务器端进行通信连接，并发送和接受自寻址套接字。虽然有多个构造函数可供选择，但我发现创
建客户端自寻址套接字最便利的选择是DatagramSocket()函数，而服务器端则是DatagramSocket(int
port)函数，如果未能创建自寻址套接字或绑定自寻址套接字到本地端口，那么这两个函数都将抛出一个SocketException对象，一旦程序创建
了DatagramSocket对象，那么程序分别调用send(DatagramPacket dgp)和
receive(DatagramPacket dgp)来发送和接收自寻址数据包，</p>
<p>　　List4显示的DGSClient源代码示范了如何创建自寻址套接字以及如何通过套接字处理发送和接收信息 </p>
<p>&nbsp;</p>
<p>
<br>Listing 4: DGSClient.java<br>// DGSClient.java</p>
<p>import java.io.*;<br>import java.net.*;</p>
<p>class DGSClient<br>{<br>　public static void main (String [] args)<br>　{<br>　　String host = "localhost";</p>
<p>　　// If user specifies a command-line argument, that argument<br>　　// represents the host name.<br>　<br>　　if (args.length == 1)<br>　　　host = args [0];</p>
<p>　　DatagramSocket s = null;</p>
<p>　　try<br>　　{<br>　　　// Create a datagram socket bound to an arbitrary port.</p>
<p>　　　s = new DatagramSocket ();</p>
<p>　　　// Create a byte array that will hold the data portion of a<br>　　　// datagram packet's message. That message originates as a<br>　　　// String object, which gets converted to a sequence of<br>　　　// bytes when String's getBytes() method is called. The<br>　　　// conversion uses the platform's default character set.</p>
<p>　　　byte [] buffer;<br>　　　buffer = new String ("Send me a datagram").getBytes ();</p>
<p>　　　// Convert the name of the host to an InetAddress object.<br>　　　// That object contains the IP address of the host and is<br>　　　// used by DatagramPacket.</p>
<p>　　　InetAddress ia = InetAddress.getByName (host);</p>
<p>　　　// Create a DatagramPacket object that encapsulates a<br>　　　// reference to the byte array and destination address<br>　　　// information. The destination address consists of the<br>　　　// host's IP address (as stored in the InetAddress object)<br>　　　// and port number 10000 -- the port on which the server<br>　　　// program listens.</p>
<p>　　　DatagramPacket dgp = new DatagramPacket (buffer,<br>　　　　　　　　buffer.length,<br>　　　　　　　　ia,<br>　　　　　　　　10000);</p>
<p>　　　// Send the datagram packet over the socket.</p>
<p>　　　s.send (dgp);</p>
<p>　　　// Create a byte array to hold the response from the server.<br>　　　// program.</p>
<p>　　　byte [] buffer2 = new byte [100];</p>
<p>　　　// Create a DatagramPacket object that specifies a buffer<br>　　　// to hold the server program's response, the IP address of<br>　　　// the server program's computer, and port number 10000.</p>
<p>　　　dgp = new DatagramPacket (buffer2,<br>　　　　　　buffer.length,<br>　　　　　　ia,<br>　　　　　　10000);</p>
<p>　　　// Receive a datagram packet over the socket.</p>
<p>　　　s.receive (dgp);</p>
<p>　　　// Print the data returned from the server program and stored<br>　　　// in the datagram packet.</p>
<p>　　　System.out.println (new String (dgp.getData ()));</p>
<p>　　}<br>　　catch (IOException e)<br>　　{<br>　　　System.out.println (e.toString ());<br>　　}<br>　　finally<br>　　{<br>　　　if (s != null)<br>　　　　s.close ();　<br>　　}<br>　}<br>} </p>
<p>
<br>DGSClient
由创建一个绑定任意本地（客户端）端口好的DatagramSocket对象开始，然后装入带有文本信息的数组buffer和描述服务器主机IP地址的
InetAddress子类对象的引用，接下来，DGSClient创建了一个DatagramPacket对象，该对象加入了带文本信息的缓冲器的引
用，InetAddress子类对象的引用，以及服务端口号10000,
DatagramPacket的自寻址数据包通过方法sent()发送给服务器程序，于是一个包含服务程序响应的新的DatagramPacket对象被
创建，receive()得到响应的自寻址数据包，然后自寻址数据包的getData()方法返回该自寻址数据包的一个引用，最后关闭
DatagramSocket。</p>
<p>　　DGSServer服务程序补充了DGSClient的不足，List5是DGSServer的源代码： </p>
<p>&nbsp;</p>
<p>Listing 5: DGSServer.java<br>// DGSServer.java</p>
<p>import java.io.*;<br>import java.net.*;</p>
<p>class DGSServer<br>{<br>　public static void main (String [] args) throws IOException<br>　{<br>　　System.out.println ("Server starting ...\n");</p>
<p>　　// Create a datagram socket bound to port 10000. Datagram<br>　　// packets sent from client programs arrive at this port.</p>
<p>　　DatagramSocket s = new DatagramSocket (10000);</p>
<p>　　// Create a byte array to hold data contents of datagram<br>　　// packet.</p>
<p>　　byte [] data = new byte [100];</p>
<p>　　// Create a DatagramPacket object that encapsulates a reference<br>　　// to the byte array and destination address information. The<br>　　// DatagramPacket object is not initialized to an address <br>　　// because it obtains that address from the client program.</p>
<p>　　DatagramPacket dgp = new DatagramPacket (data, data.length);</p>
<p>　　// Enter an infinite loop. Press Ctrl+C to terminate program.</p>
<p>　　while (true)<br>　　{<br>　　　// Receive a datagram packet from the client program.</p>
<p>　　　s.receive (dgp);</p>
<p>　　　// Display contents of datagram packet.</p>
<p>　　　System.out.println (new String (data));</p>
<p>　　　// Echo datagram packet back to client program.</p>
<p>　　s.send (dgp);<br>　}<br>}<br>} </p>
<p>&nbsp;</p>
<p>
<br>　　DGSServer创建了一个绑定端口10000的自寻址套接字，然后创建一个字节数组容纳自寻址信息，并创建自寻址包，下一步，DGSServer进入一个无限循环中以接收自寻址数据包、显示内容并将响应返回客户端，自寻址套接没有关闭，因为循环是无限的。</p>
<p>
在编译DGSServer 和DGSClient的源代码后，由输入java
DGSServer开始运行DGSServer，然后在同一主机上输入Java
DGSClient开始运行DGSClient，如果DGSServer与DGSClient运行于不同主机，在输入时注意要在命令行加上服务程序的主机
名或IP地址，如：java DGSClient <a href="http://www.yesky.com/">www.yesky.com</a></p>
<p>
<br>多点传送和MulticastSocket类</p>
<p>
前面的例子显示了服务器程序线程发送单一的消息（通过流套接字或自寻址套接字）给唯一的客户端程序，这种行为被称为单点传送（unicasting），
多数情况都不适合于单点传送，比如，摇滚歌手举办一场音乐会将通过互联网进行播放，画面和声音的质量依赖于传输速度，服务器程序要传送大约10亿字节的数
据给客户端程序，使用单点传送，那么每个客户程序都要要复制一份数据，如果，互联网上有10000个客户端要收看这个音乐会，那么服务器程序通过
Internet要传送10000G的数据，这必然导致网络阻塞，降低网络的传输速度。</p>
<p>　　如果服务器程序要将同一信息发送给多个客户端，
那么服务器程序和客户程序可以利用多点传送（multicasting）方式进行通信。多点传送就是服务程序对专用的多点传送组的IP地址和端口发送一系
列自寻址数据包，通过加入操作IP地址被多点传送Socket注册，通过这个点客户程序可以接收发送给组的自寻址包（同样客户程序也可以给这个组发送自寻
址包），一旦客户程序读完所有要读的自寻址数据包，那么可以通过离开组操作离开多点传送组。</p>
<p>　　注意：IP地址224.0.0.1 到 239.255.255.255（包括）均为保留的多点传送组地址。</p>
<p>
网络API通过MulticastSocket类和MulticastSocket，以及一些辅助类（比如NetworkInterface）支持多点
传送，当一个客户程序要加入多点传送组时，就创建一个MulticastSocket对象。MulticastSocket(int
port)构造函数允许应用程序指定端口（通过port参数）接收自寻址包，端口必须与服务程序的端口号相匹配，要加入多点传送组，客户程序调用两个
joinGroup()方法中的一个，同样要离开传送组，也要调用两个leaveGroup()方法中的一个。</p>
<p>　　由于MulticastSocket扩展了DatagramSocket类，一个MulticastSocket对象就有权访问DatagramSocket方法。</p>
<p>　　List6是MCClient的源代码，这段代码示范了一个客户端加入多点传送组的例子。 </p>
<p>&nbsp;</p>
<p>
<br>Listing 6: MCClient.java<br>// MCClient.java</p>
<p>import java.io.*;<br>import java.net.*;</p>
<p>class MCClient<br>{<br>　public static void main (String [] args) throws IOException<br>　{<br>　　// Create a MulticastSocket bound to local port 10000. All<br>　　// multicast packets from the server program are received<br>　　// on that port.</p>
<p>　　MulticastSocket s = new MulticastSocket (10000);</p>
<p>　　// Obtain an InetAddress object that contains the multicast<br>　　// group address 231.0.0.1. The InetAddress object is used by<br>　　// DatagramPacket.</p>
<p>　　InetAddress group = InetAddress.getByName ("231.0.0.1");</p>
<p>　　// Join the multicast group so that datagram packets can be<br>　　// received.</p>
<p>　　s.joinGroup (group);</p>
<p>　　// Read several datagram packets from the server program.</p>
<p>　　for (int i = 0; i &lt; 10; i++)<br>　　{<br>　　　// No line will exceed 256 bytes.</p>
<p>　　　byte [] buffer = new byte [256];</p>
<p>　　　// The DatagramPacket object needs no addressing<br>　　　// information because the socket contains the address.</p>
<p>　　　DatagramPacket dgp = new DatagramPacket (buffer,<br>　　　　　　buffer.length);</p>
<p>　　　// Receive a datagram packet.</p>
<p>　　　s.receive (dgp);</p>
<p>　　　// Create a second byte array with a length that matches<br>　　　// the length of the sent data.</p>
<p>　　　byte [] buffer2 = new byte [dgp.getLength ()];</p>
<p>　　　// Copy the sent data to the second byte array.</p>
<p>　　　System.arraycopy (dgp.getData (),<br>　　　　　　　　0,<br>　　　　　　　　buffer2,<br>　　　　　　　　0,<br>　　　　　　　　dgp.getLength ());</p>
<p>　　　// Print the contents of the second byte array. (Try<br>　　　// printing the contents of buffer. You will soon see why<br>　　　// buffer2 is used.)</p>
<p>　　　System.out.println (new String (buffer2));<br>　　}</p>
<p>　　// Leave the multicast group.</p>
<p>　　s.leaveGroup (group);</p>
<p>　　// Close the socket.</p>
<p>　　s.close ();<br>　}<br>} </p>
<p>&nbsp;</p>
<p>
<br>
MCClient创建了一个绑定端口号10000的MulticastSocket对象，接下来他获得了一个InetAddress子类对象，该子类对
象包含多点传送组的IP地址231.0.0.0，然后通过joinGroup(InetAddress
addr)方法加入多点传送组中，接下来MCClient接收10个自寻址包，同时输出他们的内容，然后使用leaveGroup
(InetAddress addr)方法离开传送组，最后关闭套接字。</p>
<p>　　也许你对使用两个字节数组buffer 和
buffer2感到奇怪，当接收到一个自寻址包后，getData()方法返回一个引用，自寻址包的长度是256个字节，如果要输出所有数据，在输出完实
际数据后会有很多空格，这显然是不合理的，所以我们必须去掉这些空格，因此我们创建一个小的字节数组buffer2，buffer2的实际长度就是数据的
实际长度，通过调用DatagramPacket's getLength()方法来得到这个长度。从buffer 到
buffer2快速复制getLength()的长度的方法是调用System.arraycopy()方法。<br>List7 MCServer的源代码显示了服务程序是怎样工作的。 </p>
<p>&nbsp;</p>
<p>Listing 7: MCServer.java<br>// MCServer.java</p>
<p>import java.io.*;<br>import java.net.*;</p>
<p>class MCServer<br>{<br>　public static void main (String[] args) throws IOException<br>　{<br>　　System.out.println ("Server starting...\n"); </p>
<p>　　// Create a MulticastSocket not bound to any port.</p>
<p>　　MulticastSocket s = new MulticastSocket ();</p>
<p>　　// Because MulticastSocket subclasses DatagramSocket, it is<br>　　// legal to replace MulticastSocket s = new MulticastSocket ();<br>　　// with the following line.</p>
<p>　　 </p>
<p>write("");<br>&nbsp; // DatagramSocket s = new DatagramSocket ();</p>
<p>　　// Obtain an InetAddress object that contains the multicast<br>　　// group address 231.0.0.1. The InetAddress object is used by<br>　　// DatagramPacket.</p>
<p>　　InetAddress group = InetAddress.getByName ("231.0.0.1");</p>
<p>　　// Create a DatagramPacket object that encapsulates a reference<br>　　// to a byte array (later) and destination address<br>　　// information. The destination address consists of the<br>　　// multicast group address (as stored in the InetAddress object)<br>　　// and port number 10000 -- the port to which multicast datagram<br>　　// packets are sent. (Note: The dummy array is used to prevent a<br>　　// NullPointerException object being thrown from the<br>　　// DatagramPacket constructor.)</p>
<p>　　byte [] dummy = new byte [0];</p>
<p>　　DatagramPacket dgp = new DatagramPacket (dummy,<br>　　　　0,<br>　　　　group,<br>　　　　10000);</p>
<p>　　// Send 30000 Strings to the port.</p>
<p>　　for (int i = 0; i &lt; 30000; i++)<br>　　{<br>　　　// Create an array of bytes from a String. The platform's<br>　　　// default character set is used to convert from Unicode<br>　　　// characters to bytes.</p>
<p>　　　byte [] buffer = ("Video line " + i).getBytes ();</p>
<p>　　　// Establish the byte array as the datagram packet's<br>　　　// buffer.</p>
<p>　　　dgp.setData (buffer);</p>
<p>　　　// Establish the byte array's length as the length of the<br>　　　// datagram packet's buffer.</p>
<p>　　　dgp.setLength (buffer.length);</p>
<p>　　　// Send the datagram to all members of the multicast group<br>　　　// that listen on port 10000.</p>
<p>　　　s.send (dgp);<br>　　}</p>
<p>　　// Close the socket.</p>
<p>　　s.close ();<br>　}<br>} </p>
<p>&nbsp;</p>
<p>
<br>
MCServer创建了一个MulticastSocket对象，由于他是DatagramPacket对象的一部分，所以他没有绑定端口号，
DatagramPacket有多点传送组的IP地址（231.0.0.0），一旦创建DatagramPacket对象，MCServer就进入一个发
送30000条的文本的循环中，对文本的每一行均要创建一个字节数组，他们的引用均存储在前面创建的DatagramPacket对象中，通过send
()方法，自寻址包发送给所有的组成员。</p>
<p>　　在编译了MCServer 和 MCClient后，通过输入java MCServer开始运行MCServer，最后再运行一个或多个MCClient。</p>
<p>　　结论</p>
<p>
本文通过研究套接字揭示了Java的网络API的应用方法，我们介绍了套接自的慨念和套接字的组成，以及流套接字和自寻址套接字，以及如何使用
InetAddress, Socket, ServerSocket, DatagramPacket,
DatagramSocket和MulticastSocket类。在完成本文后就可以编写基本的底层通讯程序。 </p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94783.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:56 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94783.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用Java NIO提高服务端程序的性能</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94782.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:55:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94782.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94782.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94782.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94782.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94782.html</trackback:ping><description><![CDATA[		<span id="zoom">
<p style="font-family: courier new;">
<span class="myp111" style="font-size: 10pt;">在前面的章节里，我们讨论了Java NIO的基本概念，在这一节里，我们将结合具体的Java Socket编程，讨论使用NIO提高服务端程序的性能的问题。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;Java
NIO增加了新的SocketChannel、ServerSocketChannel等类来提供对构建高性能的服务端程序的支持。
SocketChannel、ServerSocketChannel能够在非阻塞的模式下工作，它们都是selectable的类。在构建服务器或者中
间件时，推荐使用Java NIO。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;在传统的网络编程中，我们通常使用一个专用线程（Thread）来处理一个Socket连接，通过使用NIO，一个或者很少几个Socket线程就可以处理成千上万个活动的Socket连接。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;
通常情况下，通过ServerSocketChannel.open()获得一个ServerSocketChannel的实例，通过
SocketChannel.open或者serverSocketChannel.accept()获得一个SocketChannel实例。要使
ServerSocketChannel或者SocketChannel在非阻塞的模式下操作，可以调用<br>&nbsp;&nbsp;&nbsp;&nbsp;serverSocketChannel.configureBlocking (false);<br>&nbsp;&nbsp;&nbsp;&nbsp;或者<br>&nbsp;&nbsp;&nbsp;&nbsp;socketChannel.configureBlocking (false); <br><br>&nbsp;&nbsp;&nbsp;&nbsp;语句来达到目的。通常情况下，服务端可以使用非阻塞的ServerSocketChannel，这样，服务端的程序就可以更容易地同时处理多个socket线程。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;下面我们来看一个综合例子，这个例子使用了ServerSocketChannel、SocketChannel开发了一个非阻塞的、能处理多线程的Echo服务端程序，见示例12-14。<br>&nbsp;&nbsp;&nbsp;&nbsp;【程序源代码】 <br><br><ccid_nobr></ccid_nobr>
<table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
    <tbody>
        <tr>
            <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
            <pre><ccid_code>1	// ==================== Program Diion =====================<br>2	// 程序名称：示例12-14 : SocketChannelDemo.java<br>3	// 程序目的：学习Java NIO#SocketChannel<br>4	// ==============================================================<br>5	<br>6	<br>7	import java.nio.ByteBuffer;<br>8	import java.nio.channels.ServerSocketChannel;<br>9	import java.nio.channels.SocketChannel;<br>10	import java.nio.channels.Selector;<br>11	import java.nio.channels.Selecti;<br>12	import java.nio.channels.SelectableChannel;<br>13	<br>14	import java.net.Socket;<br>15	import java.net.ServerSocket;<br>16	import java.net.InetSocketAddress;<br>17	import java.util.Iterator;<br>18<br>19	public class SocketChannelDemo<br><br>20	<br>21	{<br>22		public static int PORT_NUMBER = 23;//监听端口<br>23		ServerSocketChannel serverChannel;<br>24		ServerSocket serverSocket ;<br>25		Selector selector ;<br>26		private ByteBuffer buffer = ByteBuffer.allocateDirect (1024);<br>27	<br>28		public static void main (String [] args)<br>29			throws Exception<br>30		{<br>31			SocketChannelDemo server=new SocketChannelDemo();<br>32			server.init(args);<br>33			server.startWork();<br>34		}<br>35	<br>36	<br>37		public void init (String [] argv)throws Exception<br>38		{<br>39			int port = PORT_NUMBER;<br>40	<br>41			if (argv.length &gt; 0) {	<br>42				port = Integer.parseInt (argv [0]);<br>43			}<br>44	<br>45			System.out.println ("Listening on port " + port);<br>46	<br>47			// 分配一个ServerSocketChannel<br>48			serverChannel = ServerSocketChannel.open();<br>49			// 从ServerSocketChannel里获得一个对应的Socket<br>50			serverSocket = serverChannel.socket();<br>51			// 生成一个Selector<br>52			selector = Selector.open();<br>53	<br>54			// 把Socket绑定到端口上<br>55			serverSocket.bind (new InetSocketAddress (port));<br>56			//serverChannel为非bolck<br>57			serverChannel.configureBlocking (false);<br>58	<br>59			// 通过Selector注册ServerSocetChannel<br>60			serverChannel.register (selector, Selecti.OP_ACCEPT);	<br>61			<br>62		}<br>63	<br>64	    public void startWork()throws Exception<br><br>65	<br>66	    {<br>67	    	while (true) {<br>68				<br>69				int n = selector.select();//获得IO准备就绪的channel数量<br>70	<br>71				if (n == 0) {<br>72					continue;	// 没有channel准备就绪，继续执行<br>73				}<br>74	<br>75				// 用一个iterator返回Selector的selectedkeys<br>76				Iterator it = selector.selectedKeys().iterator();<br>77	<br>78				// 处理每一个Selecti<br>79				while (it.hasNext()) {<br>80					Selecti key = (Selecti) it.next();<br>81<br>82					// 判断是否有新的连接到达<br>83	if (key.isAcceptable()) {<br>84　　　　　　　　　　　//返回Selecti的ServerSocketChannel<br>85						ServerSocketChannel server =<br>(ServerSocketChannel) key.channel();<br>86						SocketChannel channel = server.accept();<br>87	<br>88						registerChannel (selector, channel,<br>89							Selecti.OP_READ);<br>90	<br>91						doWork (channel);<br>92					}<br>93	<br>94					// 判断是否有数据在此channel里需要读取<br>95					if (key.isReadable()) {<br>96					   <br>97						processData (key);<br>98	<br>99					}<br>100	<br>101					//删除 selectedkeys<br>102					it.remove();<br>103				}<br>104			}<br>105		}<br>106		protected void registerChannel (Selector selector,<br>107			SelectableChannel channel, int ops)<br>108			throws Exception<br>109		{<br><br>110			if (channel == null) {<br>111				return;		<br>112			}<br>113	<br>114		<br>115			channel.configureBlocking (false);<br>116	<br>117			channel.register (selector, ops);<br>118		}<br>119	<br>120	    //处理接收的数据<br>121		protected void processData (Selecti key)<br>122			throws Exception<br>123		{<br>124			<br>125		<br>126			SocketChannel socketChannel = (SocketChannel) key.channel();<br>127			int count;<br>128	<br>129			buffer.clear();			// 清空buffer<br>130	<br>131			// 读取所有的数据<br>132			while ((count = socketChannel.read (buffer)) &gt; 0) {<br>133				buffer.flip();		<br>134<br>135				// send the data, don&#8242;t assume it goes all at once<br>136				while (buffer.hasRemaining())<br>137				{<br>138					//如果收到回车键，则在返回的字符前增加[echo]$字样<br>139					if(buffer.get()==(char)13)<br>140					{<br>141						buffer.clear();<br>142						buffer.put("[echo]___FCKpd___0quot;.getBytes());<br>143						buffer.flip();<br>144						<br>145					}<br>146					socketChannel.write (buffer);//在Socket里写数据<br>147				}<br>148			<br>149				buffer.clear();		// 清空buffer<br>150			}<br>151	<br>152			if (count &lt; 0) {<br>153				// count&lt;0，说明已经读取完毕<br>154				socketChannel.close();<br><br>155			}<br>156		}<br>157	<br>158	<br>159		private void doWork (SocketChannel channel)throws Exception<br>160		{<br>161			buffer.clear();<br>162			buffer.put ("<br>Hello,I am working,please input some thing,and i will echo to you!<br>[echo] <br>___FCKpd___0quot;.getBytes());<br>163			buffer.flip();<br>164			channel.write (buffer);<br>165		}<br>166	<br>167	}</ccid_code></pre>
            </td>
        </tr>
    </tbody>
</table>
<br><br>&nbsp;&nbsp;&nbsp;&nbsp;使用：运行此程序，然后在控制台输入命令telnet localhost 23。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;【程序输出结果】如图12-1所示。 <br><br></span>
</p>
<div>
<span style="font-size: 10pt;">
<br>
<br>
<img style="border: 1px solid black;" src="http://java.ccidnet.com/col/attachment/2006/8/806737.jpg">
<br>
<br>
<br>图12-1 输出结果</span>
</div>
<span style="font-size: 10pt;">
</span>
<p style="font-family: courier new;">
<span style="font-size: 10pt;">
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;【程序注解】<br>&nbsp;&nbsp;&nbsp;&nbsp;关于程序的解释已经包含在程序里面了，在这里我们总结以下使用ServerSocket Channel开发服务端程序的过程：<br>&nbsp;&nbsp;&nbsp;&nbsp;（1）分配一个ServerSocketChannel。<br>&nbsp;&nbsp;&nbsp;&nbsp;（2）从ServerSocketChannel里获得一个对应的ServerSocket。<br>&nbsp;&nbsp;&nbsp;&nbsp;（3）生成一个Selector实例。<br>&nbsp;&nbsp;&nbsp;&nbsp;（4）把ServerSocket绑定到端口上。<br>&nbsp;&nbsp;&nbsp;&nbsp;（5）设置ServerSocketChannel为非block模式（可选）。<br>&nbsp;&nbsp;&nbsp;&nbsp;（6）在Selector里注册ServerSocetChannel。<br>&nbsp;&nbsp;&nbsp;&nbsp;（7）用一个无限循环语句始终查看Selector里是否有IO准备就绪的channel。如果有，就执行对应的处理，如果没有，继续循环。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp; <strong><span style="color: #0000cc;">小 结</span></strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;在本章我们主要介绍了Java中的网络编程。Java一开始就是一种网络编程语言，到后来才应用到各个方面，所以在Java中进行网络编程远比在C/C++中方便。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;
我们介绍了几个在网络编程中很重要的类，如InetAddress、URL、URLConnection、Socket、
ServerSocket、DatagramSocket、DatagramPacket、MulticastSocket等。这些类包含了进行基本网络
编程的所有内容。要熟练地应用这些类，关键还是要多多练习。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;基于套接字的编程基本上是客户/服务器模式，我们具体介绍了编写
这种模式的步骤。在实例方面，我们给出了一个基于TCP的套接字客户/服务器程序，与此相对应，还给出了基于UDP的客户/服务器程序。两者的模式是很相
似的，其实这也就是编写客户/服务器程序的一般模式。</span>
</p>
</span> <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94782.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:55 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94782.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java中基本数据类型和流的操作</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94779.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:53:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94779.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94779.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94779.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94779.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94779.html</trackback:ping><description><![CDATA[		<span id="zoom">
<p style="text-indent: 2em;">
<a href="http://dev.21tx.com/java/" target="_blank">Java</a>中除了二进制文件和使用文本文件外还有基于Data的数据操作，这里的Data指的是Java的基本数据类型和String。基本数据类型包括byte、int、char、long、float、double、boolean和short。<br><br>
说到Java的基本数据类型必须谈到的两个类是DataInputStream和DataOutputStream。它们提供了对Java基本数据类型
的操作，但是这些方法事实上是在两个重要的接口中定义的DataInput和DataOutput，它们的功能就是把二进制的字节流转换成Java的基本
数据类型，同时还提供了从数据中使用UTF-8编码构建String的功能。有一个重要的类Random<a href="http://dev.21tx.com/database/access/" target="_blank">Access</a>File实现了DataInput和DataOutput两个接口使得他能够对文件同时进行写和读的操作。 <br><br>
在DataInputStream和DataOutputStream两个类中的方法都很简单，基本结构为readXXXX()和writeXXXX
()其中XXXX代表基本数据类型或者String。在这里不多讲述，不过值得一提的是我们有必要读读java中unicode的编码规则，在API
doc中有比较详细的介绍。通常我们的对象有很多都是由java的基本数据类型构成的，比如一个人的信息包括姓名，电子信箱，电话号码和性别等。其实我们
可以用DataInputStream中的方法和DataOutputStream中的方法按照一定的序列把数据写入流中再按照相同的序列把他们读取出
来，这就是我们自己实现的序列化，这可以用在数据传输中，比如在<a href="http://dev.21tx.com/java/j2me/" target="_blank">J2ME</a>联网程序中使用序列化机制传输数据。下面我们看看如何自己实现序列化，首先我们要有两个构造函数其中一个参数为空。<br><br></p>
<p style="text-indent: 2em;">
</p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>public Account()<br>{<br><br>}<br><br>public Account(String userName, String email, int age, boolean gender)<br>{<br>　this.userName = userName;<br>　this.email = email;<br>　this.age = age;<br>　this.gender = gender;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　当我们进行序列化的时候也很简单，我们只是往DataOutputStream中按照顺序写入对象的成员变量。例如：<br><br>
<p style="text-indent: 2em;"></p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>public void serialize(DataOutputStream dos) throws IOException<br>{<br>　dos.writeUTF(userName);<br>　dos.writeUTF(email);<br>　dos.writeInt(age);<br>　dos.writeBoolean(gender);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　当我们进行反序列化的时候则按照相同的顺序从DataInputStream里面读取数据并赋值给成员变量。例如：<br><br>
<p style="text-indent: 2em;"></p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>public static Account deserialize(DataInputStream dis) throws IOException<br>{<br>　Account account = new Account();<br>　account.userName = dis.readUTF();<br>　account.email = dis.readUTF();<br>　account.age = dis.readInt();<br>　account.gender = dis.readBoolean();<br>　return account;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　为了便于调试我们还提供一个toString()的方法打印出对象的实际信息。这是个好的习惯。<br><br>
<p style="text-indent: 2em;"></p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>public String toString()<br>{<br>return "UserName = " + userName + " Email = " + email + " age = " + age + " gender = " + (gender ? "male" : "female");<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　为了测试序列化我们编写下面的程序进行测试，代码比较简单。<br><br>
<p style="text-indent: 2em;"></p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>package com.j2medev.mingjava;<br>import java.io.*;<br><br>public class TestDataIO<br>{<br>　public static void main(String[] args) throws IOException<br>　{<br>　　Account account = new Account("mingjava","eric.zhan@263<a href="http://dev.21tx.com/dotnet/" target="_blank">.net</a>",25,true);<br>　　System.out.println("before serialization.........");<br>　　System.out.println(account.toString());<br>　　ByteArrayOutputStream baos = new ByteArrayOutputStream();<br>　　DataOutputStream dos = new DataOutputStream(baos);<br>　　account.serialize(dos);<br>　　DataInputStream dis = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));<br>　　Account sAccount = Account.deserialize(dis);<br>　　System.out.println("after serialization..........");<br>　　System.out.println(sAccount.toString());<br>　　dos.close();<br>　　dis.close();<br>　}<br>}<br><br>package com.j2medev.mingjava;<br>import java.io.*;<br><br>public class Account<br>{<br>　private String userName = "";<br>　private String email = "";<br>　private int age = 0;<br>　private boolean gender = false;<br><br>　public Account()<br>　{}<br><br>　public Account(String userName, String email, int age, boolean gender)<br>　{<br>　　this.userName = userName;<br>　　this.email = email;<br>　　this.age = age;<br>　　this.gender = gender;<br>　}<br><br>　public void serialize(DataOutputStream dos) throws IOException<br>　{<br>　　dos.writeUTF(userName);<br>　　dos.writeUTF(email);<br>　　dos.writeInt(age);<br>　　dos.writeBoolean(gender);<br>　}<br><br>　public static Account deserialize(DataInputStream dis) throws IOException<br>　{<br>　　Account account = new Account();<br>　　account.userName = dis.readUTF();<br>　　account.email = dis.readUTF();<br>　　account.age = dis.readInt();<br>　　account.gender = dis.readBoolean();<br>　　return account;<br>　}<br><br>　public String toString()<br>　{<br>　　return "UserName = " + userName + " Email = " + email + " age = " + age + " gender = " + (gender ? "male" : "female");<br>　}<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　编译运行程序在控制台输出：<br><br>
<p style="text-indent: 2em;"></p>
<table class="txcode" align="center" border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td>before serialization.........<br>UserName = mingjava Email = eric.zhan@263.net age = 25 gender = male<br>after serialization..........<br>UserName = mingjava Email = eric.zhan@263.net age = 25 gender = male</td>
        </tr>
    </tbody>
</table>
<br>　　序列化成功，后面我将讲述如何在J2ME联网中使用序列化机制。</span> <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:53 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用Java的NIO开发网络协议</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94781.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 15:53:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94781.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94781.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94781.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94781.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94781.html</trackback:ping><description><![CDATA[		<span id="zoom">
<p>网络协议基本的通讯单位是一个一个的消息包。在用socket传输这些包的时，首先要解决的一个问题是如何解决包与包之间的边界问题。socket
传输的是流，一个send中发出的消息，在对方不一定在一个recv中收到，可能要多次recv，或者一个recv收到多个send中放出的包。所以必须
由应用层协议自己来解决包的定界问题。通常有两种方法，一种是每个包以一个特殊的字符或者字符串来结束，如http协议就是以两个'\n'作为一个消息的
结束标记；另一种方法就是，所有的消息都有一个固定长度的消息头，在消息头中指出这条消息的长度。我们的协议是采用第二种方法，这也是大部分协议采用的方
法。本文提出的框架也是解决这种协议方式的。<br>Java的New IO是在J2SE1.4引入的，主要引入了Buffer这样的概念，发送接受数据都是在Buffer上进行，而对于初学者，Buffer的操作是比较复杂的，容易出错。所以在这个框架中尽可能的把对于Buffer的操作封装起来。<br>框架主要有MessageHeader, Message, MessageFactory三个接口，两个类MessageChannel, BufferUtil，以及一个异常类MessageFormatException构成。下面说明这几个接口和类的功能。</p>
<p>1. MessageHeader接口<br>在这样一套网络协议中，总是有一个固定长度的消息头，不同的协议有不同的消息头，但是几乎所有的消息
头都定义了本消息的长度和本消息的类型。类型用于识别不同的消息包。类型相同的包，格式都是一样的，可以用同一个Java的class来表达。类型不同的
包，格式可能相同也可能不同，依赖于协议。接口定义如下：<br>public interface MessageHeader {<br>&nbsp;/**<br>&nbsp; * 返回消息类型<br>&nbsp; */<br>&nbsp;int getMessageType(); <br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 返回消息长度<br>&nbsp; */<br>&nbsp;int getMessageLength(); </p>
<p>&nbsp;/**<br>&nbsp; * 从Buffer中提取消息头<br>&nbsp; */<br>&nbsp;void buildFromBuffer(ByteBuffer buffer); </p>
<p>&nbsp;/**<br>&nbsp; * 把消息头放到Buffer中<br>&nbsp; */<br>&nbsp;void appendToBuffer(ByteBuffer buffer); <br>&nbsp;<br>}</p>
<p>2. Message接口<br>Message代表一个消息包。每个消息包有一个消息头。其定义如下：<br>public interface Message {<br>&nbsp;/**<br>&nbsp; * 设置消息头，在MessageChannel.receive中调用<br>&nbsp; */<br>&nbsp;void setHeader(MessageHeader header); </p>
<p>&nbsp;/**<br>&nbsp; * 返回消息头<br>&nbsp; */<br>&nbsp;MessageHeader getHeader(); </p>
<p>&nbsp;/**<br>&nbsp; * 从Buffer中取出消息体<br>&nbsp; */<br>&nbsp;void buildBodyFromBuffer(ByteBuffer buffer); <br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 把消息体放到Buffer中<br>&nbsp; */<br>&nbsp;void appendBodyToBuffer(ByteBuffer buffer); <br>}</p>
<p>3. MessageFactory接口<br>这个接口封装了所有真正的用于表达消息的类的创建，在MessageChannel的receive中调用。这里用了抽象工厂模式。其定义如下：<br>public interface MessageFactory {<br>&nbsp;/**<br>&nbsp; * 返回消息头的字节数<br>&nbsp; */<br>&nbsp;int getMessageHeaderLength();<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 创建一个消息头对象<br>&nbsp; */<br>&nbsp;MessageHeader createMessageHeader();</p>
<p>&nbsp;/**<br>&nbsp; * 创建一个消息对象<br>&nbsp; * @param type 消息类型，从消息头中取得<br>&nbsp; */<br>&nbsp;Message createMessage(int type);<br>}</p>
<p>4. MessageChannel类<br>主要功能都在这个类中，用于发送和接收消息，并封装了所有对于Buffer的操作。<br>class MessageChannel {<br>/**<br>&nbsp;* 构造方法，要求指明发送缓冲区和接收缓冲区的大小<br>&nbsp;*/<br>public MessageChannel(int receiveBufferSize, <br>&nbsp;&nbsp;&nbsp;int sendBufferSize,<br>&nbsp;&nbsp;&nbsp;SocketChannel sc,<br>&nbsp;&nbsp;&nbsp;MessageFactory mf);<br>/**<br>&nbsp;* 接收一条消息，当消息不完整、收到的消息长度太大（超过接收缓冲区大小）或者不能通过MessageFactory创建的消息类型时时抛出MessageFormatException。<br>&nbsp;*/<br>public Message receive() throws IOException, MessageFormatException;</p>
<p>/**<br>&nbsp;* 发送一条消息<br>&nbsp;*/<br>public void send(Message message) throws IOException;</p>
<p>}</p>
<p>5. BufferUtil类<br>这是一个Utility类，主要功能是从ByteBuffer中取得或者放入一个字符串String，不同的协议有不同的字符串处理方法。<br>class BufferUtil {<br>&nbsp;/**<br>&nbsp; * 从buffer取得一个字符串，length为长度<br>&nbsp; */<br>&nbsp;static String getString(ByteBuffer buffer, int length);<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 从buffer取得一个以'\0'结束的字符串，length为最大长度<br>&nbsp; */<br>&nbsp;static String getCString(ByteBuffer buffer, int length);<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 从buffer取得一个变长的字符串，长度用两字节的short类型表示<br>&nbsp; */<br>&nbsp;static String getVarStringShortLength(ByteBuffer buffer);</p>
<p>&nbsp;/**<br>&nbsp; * 从buffer取得一个变长的字符串，长度用四字节的int类型表示<br>&nbsp; */<br>&nbsp;static String getVarStringIntLength(ByteBuffer buffer);</p>
<p>&nbsp;/**<br>&nbsp; * 从buffer取得一个变长的字符串，长度用一字节的byte表示<br>&nbsp; */<br>&nbsp;static String getVarStringByteLength();<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 在buffer中放入一个字符串，length为长度<br>&nbsp; */<br>&nbsp;static void putString(ByteBuffer buffer, String str, int length);</p>
<p>&nbsp;/**<br>&nbsp; * 在buffer中放入一个字符串，length为最大长度。如果str没有达到最大长度，那么用0填充。<br>&nbsp; */<br>&nbsp;static void putCString(ByteBuffer buffer, String str, int length);<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 在buffer中放入一个字符串，长度用一个short表示<br>&nbsp; */<br>&nbsp;static void putVarStringShortLength(ByteBuffer buffer);<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 在buffer中放入一个字符串，长度用一个int表示<br>&nbsp; */<br>&nbsp;static void putVarStringIntLength(ByteBuffer buffer);<br>&nbsp;<br>&nbsp;/**<br>&nbsp; * 在buffer中放入一个字符串，长度用一个byte表示<br>&nbsp; */<br>&nbsp;static void putVarStringByteLength(ByteBuffer buffer);<br>}<br>目前，这个类没有考虑编码方式，可以对这个类进行扩充。</p>
<p>在这个框架中，Message.receive是最复杂的部分，下面对该过程的流程进行说明：<br>(1) 根据MessageFactory.getMessageHeaderLength()返回的消息头长度，接收消息头。<br>(2) 用MessageFactory.createMessageHeader() 构造消息头，并调用MessageHeader.buildFromBuffer()取得消息头数据。<br>(3) 根据消息头中的信息，接收消息体<br>(4) 构造消息，并调用Message.buildBodyFromBuffer()取得消息体数据。<br>(5) 调用Message.setHeader()。<br>(6) 返回构造的消息。<br>这其中有一个步骤出错，将抛出MessageFormatException。</p>
</span>  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94781.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-18 23:53 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/18/94781.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>