存档
Eclipse 3.6 正式版本发布

Eclipse Helios is now available to Friends!
Eclipse 3.6正式版下载地址:Eclipse 3.6 Helios正式版
该版本主要改进内容如下:
Eclipse 3.6能够更好的自动完成提示信息,对于关联文件和路径的设置可以使程序员在编码过程中获得整个项目的清晰视图和方向指引。改进的文件链接功能可以更直接 的控制和管理项目中的文件和文件夹;管理Linux/Unix文件的权限属性。
代码风格喜好(Java Code Style Preferences)也是让开发者拍手叫好的功能,在Eclipse 3.6的JDT中,开发者可以根据自己的习惯导入导出自己的Java代码风格喜好。
Eclipse 3.6 加强了对模块化开发的支持,出现了OSGi控制台,它可以与运行时框架互用,可以使用控制视图的下拉菜单打开OSGi控制台。通过在控制台输入OSGi命 令,可以直接使框架在 IDE中运行。
除了语言级平台的支持,Eclipse对Java EE、JSF、Spring等框架的支持改进以及UML等架构、项目 管理、RCP等方面的功能也在不断向前发展。
Popularity: unranked [?]
与或运算
0与0= 0 0与1= 0 1与0= 0 1与1= 1
0或0= 0 0或1= 1 1或0= 1 1或1= 1
Popularity: unranked [?]
byte short int占用的字节数
int 四字节没错 byte一字节也没错
但一个字节有8位哦!
先看byte,byte占一字节,一字节等于八位
我们来看看一个位能表示多少东西: 最小0 最大1 表示 2的1次方
再看看两个位能表示多少东西: 最小00 最大11 共2的2次方, 能表达四个数
再看看三个位能表示多少东西: 最小000 最大111 共2的3次方,能表达8个数
那八个位能表示多少?
最小:00000000 最大:11111111 这就是8位 也叫一个字节
如果用数没有正负之分那可以表示 0~255 共256个数字. 也可以叫做 2的8次方,你用计算器算算,2的8次方是不是等于256?
如果数有正负之分那可以表示-128~+127 也是256个数,因为8位里面拿走一个位用来表示正负了,所以只能表示最大127
接下来 : 9位 = 2的九次方 =512个数字
接下来: 10位 = 2的十次方 =1024 个 ….. 以此类推
现在知道答案了吧:
1 byte = 1字节 = 8bit 可表达 2的八次方个数字
1 short= 2字节 = 16bit 可表达2的十六次方个数字
1 int = 4字节 = 32bit 可表达2的32次方
Popularity: unranked [?]
Java虚拟机参数配置
在Java、J2EE大型应用中,JVM非标准参数的配置直接关系到整个系统的性能。
JVM非标准参数指的是JVM底层的一些配置参数,这些参数在一般开发中默认即可,不需要任何配置。但是在生产环境中,为了提高性能,往往需要调整这些参数,以求系统达到最佳新能。
另外这些参数的配置也是影响系统稳定性的一个重要因素,相信大多数Java开发人员都见过“OutOfMemory”类型的错误。呵呵,这其中很可能就是JVM参数配置不当或者就没有配置没意识到配置引起的。
为了说明这些参数,还需要说说JDK中的命令行工具一些知识做铺垫。
首先看如何获取这些命令配置信息说明:
假设你是windows平台,你安装了J2SDK,那么现在你从cmd控制台窗口进入J2SDK安装目录下的 bin目录,然后运行java命令,出现如下结果,这些就是包括java.exe工具的和JVM的所有命令都在里面。
———————————————————————–
D:\j2sdk15\bin>java
Usage: java [-options] class [args...]
(to execute a class)
or java [-options] -jar jarfile [args...]
(to execute a jar file)
where options include:
-client to select the “client” VM
-server to select the “server” VM
-hotspot is a synonym for the “client” VM [deprecated]
The default VM is client.
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
A ; separated list of directories, JAR archives,
and ZIP archives to search for class files.
-D<name>=<value>
set a system property
-verbose[:class|gc|jni]
enable verbose output
-version print product version and exit
-version:<value>
require the specified version to run
-showversion print product version and continue
-jre-restrict-search | -jre-no-restrict-search
include/exclude user private JREs in the version search
-? -help print this help message
-X print help on non-standard options
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
enable assertions
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
disable assertions
-esa | -enablesystemassertions
enable system assertions
-dsa | -disablesystemassertions
disable system assertions
-agentlib:<libname>[=<options>]
load native agent library <libname>, e.g. -agentlib:hprof
see also, -agentlib:jdwp=help and -agentlib:hprof=help
-agentpath:<pathname>[=<options>]
load native agent library by full pathname
-javaagent:<jarpath>[=<options>]
load Java programming language agent, see java.lang.instrument
———————————————————————–
在控制台输出信息中,有个-X(注意是大写)的命令,这个正是查看JVM配置参数的命令。
其次,用java -X 命令查看JVM的配置说明:
运行后如下结果,这些就是配置JVM参数的秘密武器,这些信息都是英文的,为了方便阅读,我根据自己的理解翻译成中文了(不准确的地方还请各位博友斧正)
———————————————————————–
D:\j2sdk15\bin>java -X
-Xmixed mixed mode execution (default)
-Xint interpreted mode execution only
-Xbootclasspath:<directories and zip/jar files separated by ;>
set search path for bootstrap classes and resources
-Xbootclasspath/a:<directories and zip/jar files separated by ;>
append to end of bootstrap class path
-Xbootclasspath/p:<directories and zip/jar files separated by ;>
prepend in front of bootstrap class path
-Xnoclassgc disable class garbage collection
-Xincgc enable incremental garbage collection
-Xloggc:<file> log GC status to a file with time stamps
-Xbatch disable background compilation
-Xms<size> set initial Java heap size
-Xmx<size> set maximum Java heap size
-Xss<size> set java thread stack size
-Xprof output cpu profiling data
-Xfuture enable strictest checks, anticipating future default
-Xrs reduce use of OS signals by Java/VM (see documentation)
-Xcheck:jni perform additional checks for JNI functions
-Xshare:off do not attempt to use shared class data
-Xshare:auto use shared class data if possible (default)
-Xshare:on require using shared class data, otherwise fail.
The -X options are non-standard and subject to change without notice.
———————————————————————–
JVM配置参数中文说明:
———————————————————————–
1、-Xmixed mixed mode execution (default)
混合模式执行
2、-Xint interpreted mode execution only
解释模式执行
3、-Xbootclasspath:<directories and zip/jar files separated by ;>
set search path for bootstrap classes and resources
设置zip/jar资源或者类(.class文件)存放目录路径
3、-Xbootclasspath/a:<directories and zip/jar files separated by ;>
append to end of bootstrap class path
追加zip/jar资源或者类(.class文件)存放目录路径
4、-Xbootclasspath/p:<directories and zip/jar files separated by ;>
prepend in front of bootstrap class path
预先加载zip/jar资源或者类(.class文件)存放目录路径
5、-Xnoclassgc disable class garbage collection
关闭类垃圾回收功能
6、-Xincgc enable incremental garbage collection
开启类的垃圾回收功能
7、-Xloggc:<file> log GC status to a file with time stamps
记录垃圾回日志到一个文件。
8、-Xbatch disable background compilation
关闭后台编译
9、-Xms<size> set initial Java heap size
设置JVM初始化堆内存大小
10、-Xmx<size> set maximum Java heap size
设置JVM最大的堆内存大小
11、-Xss<size> set java thread stack size
设置JVM栈内存大小
12、-Xprof output cpu profiling data
输入CPU概要表数据
13、-Xfuture enable strictest checks, anticipating future default
执行严格的代码检查,预测可能出现的情况
14、-Xrs reduce use of OS signals by Java/VM (see documentation)
通过JVM还原操作系统信号
15、-Xcheck:jni perform additional checks for JNI functions
对JNI函数执行检查
16、-Xshare:off do not attempt to use shared class data
尽可能不去使用共享类的数据
17、-Xshare:auto use shared class data if possible (default)
尽可能的使用共享类的数据
18、-Xshare:on require using shared class data, otherwise fail.
尽可能的使用共享类的数据,否则运行失败
The -X options are non-standard and subject to change without notice.
———————————————————————–
怎么用这这些参数呢?其实所有的命令行都是这么一用,下面我就给出一个最简单的HelloWorl的例子来演示这个参数的用法,非常的简单。
HelloWorld.java
———————————————–
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println(”Hello World!”);
}
}
编译并运行:
D:\j2sdk15\bin>javac HelloWorld.java
D:\j2sdk15\bin>java -Xms256M -Xmx512M HelloWorld
Hello World!
呵呵,这下满足了吧!
实践:在大型系统或者应用中配置JVM参数
比如你配置IDE工具的参数,常见的有IDEA、Eclipse,这个是在一个配置文件中指定即可。
如果你要在J2EE环境中配置这些参数,那么你需要在J2EE应用服务器或者Servlet容器相关启动参数设置处指定,其启动文件中来配置,Tomcat是在catalina.bat中配置,weblogic和websphere是在其他地方,具体我就说了,相信玩过的这些大型服务器的人都知道,没玩过的看看这篇文章,玩玩就知道了,呵呵。
另外常常有人问到jdk的一些相关命令用法,其实,当你看到这里的时候,你应该知道如何获取这些命令的用法了。如果你还不会,那么,建议你去学学DOS,我是没辙了。如果你会这些,还是没有看明白,那么你赶紧学学英语吧,这样你就能看懂了。
另外:我在最后给出常用的几个Java命令行说明,以供参考:
(1)、javac
用法:javac <选项> <源文件>
其中,可能的选项包括:
-g 生成所有调试信息
-g:none 不生成任何调试信息
-g:{lines,vars,source} 只生成某些调试信息
-nowarn 不生成任何警告
-verbose 输出有关编译器正在执行的操作的消息
-deprecation 输出使用已过时的 API 的源位置
-classpath <路径> 指定查找用户类文件的位置
-cp <路径> 指定查找用户类文件的位置
-sourcepath <路径> 指定查找输入源文件的位置
-bootclasspath <路径> 覆盖引导类文件的位置
-extdirs <目录> 覆盖安装的扩展目录的位置
-endorseddirs <目录> 覆盖签名的标准路径的位置
-d <目录> 指定存放生成的类文件的位置
-encoding <编码> 指定源文件使用的字符编码
-source <版本> 提供与指定版本的源兼容性
-target <版本> 生成特定 VM 版本的类文件
-version 版本信息
-help 输出标准选项的提要
-X 输出非标准选项的提要
-J<标志> 直接将 <标志> 传递给运行时系统
(2)、jar
用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 …
选项:
-c 创建新的存档
-t 列出存档内容的列表
-x 展开存档中的命名的(或所有的〕文件
-u 更新已存在的存档
-v 生成详细输出到标准输出上
-f 指定存档文件名
-m 包含来自标明文件的标明信息
-0 只存储方式;未用ZIP压缩格式
-M 不产生所有项的清单(manifest〕文件
-i 为指定的jar文件产生索引信息
-C 改变到指定的目录,并且包含下列文件:
如果一个文件名是一个目录,它将被递归处理。
清单(manifest〕文件名和存档文件名都需要被指定,按’m’ 和 ‘f’标志指定的相同顺序。
示例1:将两个class文件存档到一个名为 ‘classes.jar’ 的存档文件中:
jar cvf classes.jar Foo.class Bar.class
示例2:用一个存在的清单(manifest)文件 ‘mymanifest’ 将 foo/ 目录下的所有
文件存档到一个名为 ‘classes.jar’ 的存档文件中:
jar cvfm classes.jar mymanifest -C foo/ .
(3)、javadoc
javadoc: 错误 – 未指定软件包或类。
用法:javadoc [选项] [软件包名称] [源文件] [@file]
-overview <文件> 读取 HTML 文件的概述文档
-public 仅显示公共类和成员
-protected 显示受保护/公共类和成员(默认)
-package 显示软件包/受保护/公共类和成员
-private 显示所有类和成员
-help 显示命令行选项并退出
-doclet <类> 通过替代 doclet 生成输出
-docletpath <路径> 指定查找 doclet 类文件的位置
-sourcepath <路径列表> 指定查找源文件的位置
-classpath <路径列表> 指定查找用户类文件的位置
-exclude <软件包列表> 指定要排除的软件包的列表
-subpackages <子软件包列表> 指定要递归装入的子软件包
-breakiterator 使用 BreakIterator 计算第 1 句
-bootclasspath <路径列表> 覆盖引导类加载器所装入的
类文件的位置
-source <版本> 提供与指定版本的源兼容性
-extdirs <目录列表> 覆盖安装的扩展目录的位置
-verbose 输出有关 Javadoc 正在执行的操作的消息
-locale <名称> 要使用的语言环境,例如 en_US 或 en_US_WIN
-encoding <名称> 源文件编码名称
-quiet 不显示状态消息
-J<标志> 直接将 <标志> 传递给运行时系统
通过标准 doclet 提供:
-d <目录> 输出文件的目标目录
-use 创建类和软件包用法页面
-version 包含 @version 段
-author 包含 @author 段
-docfilessubdirs 递归复制文档文件子目录
-splitindex 将索引分为每个字母对应一个文件
-windowtitle <文本> 文档的浏览器窗口标题
-doctitle <html 代码> 包含概述页面的标题
-header <html 代码> 包含每个页面的页眉文本
-footer <html 代码> 包含每个页面的页脚文本
-bottom <html 代码> 包含每个页面的底部文本
-link <url> 创建指向位于 <url> 的 javadoc 输出的链接
-linkoffline <url> <url2> 利用位于 <url2> 的软件包列表链接至位于 <url>
的文档
-excludedocfilessubdir <名称 1>:..排除带有给定名称的所有文档文件子目录。
-group <名称> <p1>:<p2>.. 在概述页面中,将指定的软件包分组
-nocomment 抑止描述和标记,只生成声明。
-nodeprecated 不包含 @deprecated 信息
-noqualifier <名称 1>:<名称 2>:…从输出中排除限定符的列表。
-nosince 不包含 @since 信息
-notimestamp 不包含隐藏时间戳
-nodeprecatedlist 不生成已过时的列表
-notree 不生成类分层结构
-noindex 不生成索引
-nohelp 不生成帮助链接
-nonavbar 不生成导航栏
-serialwarn 生成有关 @serial 标记的警告
-tag <名称>:<位置>:<标题> 指定单个变量自定义标记
-taglet 要注册的 Taglet 的全限定名称
-tagletpath Taglet 的路径
-charset <字符集> 用于跨平台查看生成的文档的字符集。
-helpfile <文件> 包含帮助链接所链接到的文件
-linksource 以 HTML 格式生成源
-sourcetab <制表符长度> 指定源中每个制表符占据的空格数
-keywords 使软件包、类和成员信息附带 HTML 元标记
-stylesheetfile <路径> 用于更改生成文档的样式的文件
-docencoding <名称> 输出编码名称
(4)、rmid
rmid: 非法选项:-?
用法:rmid <option>
其中,<option> 包括:
-port <option> 指定供 rmid 使用的端口
-log <directory> 指定 rmid 将日志写入的目录
-stop 停止当前的 rmid 调用(对指定端口)
-C<runtime 标记> 向每个子进程传递参数(激活组)
-J<runtime 标记> 向 java 解释程序传递参数
Popularity: unranked [?]
jar命令的使用(MANIFET.MF文件)
|
public class HelloWorld{
public static void main(String[ ] args){
System.out.println(“Hi, Hello World!”);
}
}
|
|
Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
|
|
Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Main-Class: HelloWorld
|
|
jar umf MANIFEST.MF hello.jar
|
|
java -jar hello.jar
|
|
Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Main-Class: HelloWorld
|
|
Manifest-Version: 1.0
Class-Path: ./lib/msbase.jar ./lib/mssqlserver.jar ./lib/msutil.jar
Created-By: 1.6.0 (Sun Microsystems Inc.)
Main-Class: org.qiujy.test.TestDB
|
Popularity: unranked [?]
Netty 中文用户手册
原文地址:http://www.jboss.org/file-access/default/members/netty/freezone/guide/3.1/html_single/index.html

The Netty Project 3.1 User Guide
The Proven Approach to Rapid Network Application Development
3.1.5.GA, r1772
序言 1
1. 问题 1
2. 方案 2
第一章. 开始 2
1.1. 开始之前 3
1.2. 抛弃协议服务 3
1.3. 查看接收到的数据 5
1.4. 响应协议服务 6
1.5. 时间协议服务 7
1.6. 时间协议服务客户端 9
1.7. 流数据的传输处理 11
1.7.1. Socket Buffer的缺陷 11
1.7.2. 第一种方案 11
1.7.3. 第二种方案 13
1.8. 使用POJO代替ChannelBuffer 15
1.9. 关闭你的应用 18
1.10. 总述 21
第二章. 架构总览 22
2.1. 丰富的缓冲实现 22
2.2. 统一的异步 I/O API 22
2.3. 基于拦截链模式的事件模型 23
2.4. 适用快速开发的高级组件 24
2.4.1. Codec框架 24
2.4.2. SSL / TLS 支持 25
2.4.3. HTTP实现 25
2.4.4. Google Protocol Buffer 整合 25
2.5. 总述 26
序言
本指南对Netty 进行了介绍并指出其意义所在。
1. 问题
现在,我们使用适合一般用途的应用或组件来和彼此通信。例如,我们常常使用一个HTTP客户端从远程服务器获取信息或者通过web services进行远程方法的调用。
然而,一个适合普通目的的协议或其实现并不具备其规模上的扩展性。例如,我们无法使用一个普通的HTTP服务器进行大型文件,电邮信息的交互,或者处理金 融信息和多人游戏数据那种要求准实时消息传递的应用场景。因此,这些都要求使用一个适用于特殊目的并经过高度优化的协议实现。例如,你可能想要实现一个对 基于AJAX的聊天应用,媒体流或大文件传输进行过特殊优化的HTTP服务器。你甚至可能想去设计和实现一个全新的,特定于你的需求的通信协议。
另一种无法避免的场景是你可能不得不使用一种专有的协议和原有系统交互。在这种情况下,你需要考虑的是如何能够快速的开发出这个协议的实现并且同时还没有 牺牲最终应用的性能和稳定性。
2. 方案
Netty 是一个异步的,事件驱动的网络编程框架和工具,使用Netty 可以快速开发出可维护的,高性能、高扩展能力的协议服务及其客户端应用。
也就是说,Netty 是一个基于NIO的客户,服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP 和UDP的socket服务开发。
“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMPT,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
一些用户可能找到了某些同样声称具有这些特性的编程框架,因此你们可能想问Netty 又有什么不一样的地方。这个问题的答案是Netty 项目的设计哲学。从创立之初,无论是在API还是在其实现上Netty 都致力于为你提供最为舒适的使用体验。虽然这并不是显而易见的,但你终将会认识到这种设计哲学将令你在阅读本指南和使用Netty 时变得更加得轻松和容易。
第一章. 开始
这一章节将围绕Netty的核心结构展开,同时通过一些简单的例子可以让你更快的了解Netty的使用。当你读完本章,你将有能力使用Netty完成客户 端和服务端的开发。
如果你更喜欢自上而下式的学习方式,你可以首先完成 第二章:架构总览 的学习,然后再回到这里。
1.1. 开始之前
运行本章示例程序的两个最低要求是:最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在项目下载页 下载。下载正确版本的JDK,请到你偏好的JDK站点下载。
这就已经足够了吗?实际上你会发现,这两个条件已经足够你完成任何协议的开发了。如果不是这样,请联系Netty项目社区 ,让我们知道还缺少了什么。
最终但不是至少,当你想了解本章所介绍的类的更多信息时请参考API手册。为方便你的使用,这篇文档中所有的类名均连接至在线API手册。此外,如果本篇 文档中有任何错误信息,无论是语法错误,还是打印排版错误或者你有更好的建议,请不要顾虑,立即联系Netty项目社区 。
1.2. 抛弃协议服务
在这个世界上最简化的协议不是“Hello,world!”而是抛弃协议 。这是一种丢弃接收到的任何数据并不做任何回应的协议。
实现抛弃协议(DISCARD protocol),你仅需要忽略接受到的任何数据即可。让我们直接从处理器(handler)实现开始,这个处理器处理Netty的所有I/O事件。
- package org.jboss.netty.example.discard;
- @ChannelPipelineCoverage(“all”)1
- public class DiscardServerHandler extends SimpleChannelHandler {2
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4
- e.getCause().printStackTrace();
- Channel ch = e.getChannel();
- ch.close();
- }
- }
代码说明
2)DiscardServerHandler继承了SimpleChannelHandler,这也是一个ChannelHandler 的实现。SimpleChannelHandler提供了多种你可以重写的事件处理方法。目前直接继承SimpleChannelHandler已经足够 了,并不需要你完成一个自己的处理器接口。
3)我们这里重写了messageReceived事件处理方法。这个方法由一个接收了客户端传送数据的MessageEvent事件调用。在这个例子 中,我们忽略接收到的任何数据,并以此来实现一个抛弃协议(DISCARD protocol)。
4)exceptionCaught 事件处理方法由一个ExceptionEvent异常事件调用,这个异常事件起因于Netty的I/O异常或一个处理器实现的内部异常。多数情况下,捕捉 到的异常应当被记录下来,并在这个方法中关闭这个channel通道。当然处理这种异常情况的方法实现可能因你的实际需求而有所不同,例如,在关闭这个连 接之前你可能会发送一个包含了错误码的响应消息。
目前进展不错,我们已经完成了抛弃协议服务器的一半开发工作。下面要做的是完成一个可以启动这个包含DiscardServerHandler处理 器服务的主方法。
- package org.jboss.netty.example.discard;
- import java.net.InetSocketAddress;
- import java.util.concurrent.Executors;
- public class DiscardServer {
- public static void main(String[] args) throws Exception {
- ChannelFactory factory =
- new NioServerSocketChannelFactory (
- Executors.newCachedThreadPool(),
- Executors.newCachedThreadPool());
- ServerBootstrap bootstrap = new ServerBootstrap (factory);
- DiscardServerHandler handler = new DiscardServerHandler();
- ChannelPipeline pipeline = bootstrap.getPipeline();
- pipeline.addLast(“handler”, handler);
- bootstrap.setOption(“child.tcpNoDelay”, true);
- bootstrap.setOption(“child.keepAlive”, true);
- bootstrap.bind(new InetSocketAddress(8080));
- }
- }
代码说明
2)ServerBootstrap 是一个设置服务的帮助类。你甚至可以在这个服务中直接设置一个Channel通道。然而请注意,这是一个繁琐的过程,大多数情况下并不需要这样做。
3)这里,我们将DiscardServerHandler处理器添加至默认的ChannelPipeline通道。任何时候当服务器接收到一个新的连 接,一个新的ChannelPipeline管道对象将被创建,并且所有在这里添加的ChannelHandler对象将被添加至这个新的 ChannelPipeline管道对象。这很像是一种浅拷贝操作(a shallow-copy operation);所有的Channel通道以及其对应的ChannelPipeline实例将分享相同的DiscardServerHandler 实例。
4)你也可以设置我们在这里指定的这个通道实现的配置参数。我们正在写的是一个TCP/IP服务,因此我们运行设定一些socket选项,例如 tcpNoDelay和keepAlive。请注意我们在配置选项里添加的”child.”前缀。这意味着这个配置项仅适用于我们接收到的通道实例,而不 是ServerSocketChannel实例。因此,你可以这样给一个ServerSocketChannel设定参数:
bootstrap.setOption(”reuseAddress”, true);
5)我们继续。剩下要做的是绑定这个服务使用的端口并且启动这个服务。这里,我们绑定本机所有网卡(NICs,network interface cards)上的8080端口。当然,你现在也可以对应不同的绑定地址多次调用绑定操作。
大功告成!现在你已经完成你的第一个基于Netty的服务端程序。
1.3. 查看接收到的数据
现在你已经完成了你的第一个服务端程序,我们需要测试它是否可以真正的工作。最简单的方法是使用telnet 命令。例如,你可以在命令行中输入“telnet localhost 8080 ”或其他类型参数。
然而,我们可以认为服务器在正常工作吗?由于这是一个丢球协议服务,所以实际上我们无法真正的知道。你最终将收不到任何回应。为了证明它在真正的工作,让 我们修改代码打印其接收到的数据。
我们已经知道当完成数据的接收后将产生MessageEvent消息事件,并且也会触发messageReceived处理方法。所以让我在 DiscardServerHandler处理器的messageReceived方法内增加一些代码。
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- ChannelBuffer buf = (ChannelBuffer) e.getMessage();
- while(buf.readable()) {
- System.out.println((char) buf.readByte());
- }
- }
代码说明
2) 虽然ChannelBuffer有些类似于NIO的ByteBuffer,但强烈建议你参考Netty的API手册。学会如何正确的使用 ChannelBuffer是无障碍使用Netty的关键一步。
如果你再次运行telnet命令,你将会看到你所接收到的数据。
抛弃协议服务的所有源代码均存放在在分发版的org.jboss.netty.example.discard包下。
1.4. 响应协议服务
目前,我们虽然使用了数据,但最终却未作任何回应。然而一般情况下,一个服务都需要回应一个请求。让我们实现ECHO协议 来学习如何完成一个客户请求的回应消息,ECHO协议规定要返回任何接收到的数据。
与我们上一节实现的抛弃协议服务唯一不同的地方是,这里需要返回所有的接收数据而不是仅仅打印在控制台之上。因此我们再次修改 messageReceived方法就足够了。
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- Channel ch = e.getChannel();
- ch.write(e.getMessage());
- }
现在如果你再次运行telnet命令,你将会看到服务器返回的你所发送的任何数据。
相应服务的所有源代码存放在分发版的org.jboss.netty.example.echo包下。
1.5. 时间协议服务
这一节需要实现的协议是TIME协议 。这是一个与先前所介绍的不同的例子。这个例子里,服务端返回一个32位的整数消息,我们不接受请求中包含的任何数据并且当消息返回完毕后立即关闭连接。 通过这个例子你将学会如何构建和发送消息,以及当完成处理后如何主动关闭连接。
因为我们会忽略接收到的任何数据而只是返回消息,这应当在建立连接后就立即开始。因此这次我们不再使用messageReceived方法,取而代之的是 使用channelConnected方法。下面是具体的实现:
- package org.jboss.netty.example.time;
- @ChannelPipelineCoverage(“all”)
- public class TimeServerHandler extends SimpleChannelHandler {
- @Override
- public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
- Channel ch = e.getChannel();
- ChannelBuffer time = ChannelBuffers.buffer(4);
- time.writeInt(System.currentTimeMillis() / 1000);
- ChannelFuture f = ch.write(time);
- f.addListener(new ChannelFutureListener() {
- public void operationComplete(ChannelFuture future) {
- Channel ch = future.getChannel();
- ch.close();
- }
- });
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
- e.getCause().printStackTrace();
- e.getChannel().close();
- }
- }
2) 为了发送一个消息,我们需要分配一个包含了这个消息的buffer缓冲。因为我们将要写入一个32位的整数,因此我们需要一个4字节的 ChannelBuffer。ChannelBuffers是一个可以创建buffer缓冲的帮助类。除了这个buffer方 法,ChannelBuffers还提供了很多和ChannelBuffer相关的实用方法。更多信息请参考API手册。
另外,一个很不错的方法是使用静态的导入方式:
import static org.jboss.netty.buffer.ChannelBuffers.*;
…
ChannelBuffer dynamicBuf = dynamicBuffer(256);
ChannelBuffer ordinaryBuf = buffer(1024);
3) 像通常一样,我们需要自己构造消息。
但是打住,flip在哪?过去我们在使用NIO发送消息时不是常常需要调用 ByteBuffer.flip()方法吗?实际上ChannelBuffer之所以不需要这个方法是因为 ChannelBuffer有两个指针;一个对应读操作,一个对应写操作。当你向一个 ChannelBuffer写入数据的时候写指针的索引值便会增加,但与此同时读指针的索引值不会有任何变化。读写指针的索引值分别代表了这个消息的开 始、结束位置。
与之相应的是,NIO的buffer缓冲没有为我们提供如此简洁的一种方法,除非你调用它的flip方法。因此,当你忘记调用flip方法而引起发送错误 时,你便会陷入困境。这样的错误不会再Netty中发生,因为我们对应不同的操作类型有不同的指针。你会发现就像你已习惯的这样过程变得更加容易—一种没 有flippling的体验!
另一点需要注意的是这个写方法返回了一个ChannelFuture对象。一个ChannelFuture 对象代表了一个尚未发生的I/O操作。这意味着,任何已请求的操作都可能是没有被立即执行的,因为在Netty内部所有的操作都是异步的。例如,下面的代 码可能会关闭一 个连接,这个操作甚至会发生在消息发送之前:
Channel ch = …;
ch.write(message);
ch.close();
因此,你需要这个write方法返回的ChannelFuture对象,close方法需要等待写操作异步完成之后的ChannelFuture通知/监 听触发。需要注意的是,关闭方法仍旧不是立即关闭一个连接,它同样也是返回了一个ChannelFuture对象。
4) 在写操作完成之后我们又如何得到通知?这个只需要简单的为这个返回的ChannelFuture对象增加一个ChannelFutureListener 即可。在这里我们创建了一个匿名ChannelFutureListener对象,在这个ChannelFutureListener对象内部我们处理了 异步操作完成之后的关闭操作。
另外,你也可以通过使用一个预定义的监听类来简化代码。
f.addListener(ChannelFutureListener.CLOSE);
1.6. 时间协议服务客户端
不同于DISCARD和ECHO协议服务,我们需要一个时间协议服务的客户端,因为人们无法直接将一个32位的二进制数据转换一个日历时间。在这一节我们 将学习如何确保服务器端工作正常,以及如何使用Netty完成客户端的开发。
使用Netty开发服务器端和客户端代码最大的不同是要求使用不同的Bootstrap及ChannelFactory。请参照以下的代码:
- package org.jboss.netty.example.time;
- import java.net.InetSocketAddress;
- import java.util.concurrent.Executors;
- public class TimeClient {
- public static void main(String[] args) throws Exception {
- String host = args[0];
- int port = Integer.parseInt(args[1]);
- ChannelFactory factory =
- new NioClientSocketChannelFactory (
- Executors.newCachedThreadPool(),
- Executors.newCachedThreadPool());
- ClientBootstrap bootstrap = new ClientBootstrap (factory);
- TimeClientHandler handler = new TimeClientHandler();
- bootstrap.getPipeline().addLast(“handler”, handler);
- bootstrap.setOption(“tcpNoDelay” , true);
- bootstrap.setOption(“keepAlive”, true);
- bootstrap.connect (new InetSocketAddress(host, port));
- }
- }
2) 客户端的ClientBootstrap对应ServerBootstrap。
3) 请注意,这里不存在使用“child.”前缀的配置项,客户端的SocketChannel实例不存在父级Channel对象。
4) 我们应当调用connect连接方法,而不是之前的bind绑定方法。
正如你所看到的,这与服务端的启动过程是完全不一样的。ChannelHandler又该如何实现呢?它应当负责接收一个32位的整数,将其转换为 可读的格式后,打印输出时间,并关闭这个连接。
- package org.jboss.netty.example.time;
- import java.util.Date;
- @ChannelPipelineCoverage(“all”)
- public class TimeClientHandler extends SimpleChannelHandler {
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- ChannelBuffer buf = (ChannelBuffer) e.getMessage();
- long currentTimeMillis = buf.readInt() * 1000L;
- System.out.println(new Date(currentTimeMillis));
- e.getChannel().close();
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
- e.getCause().printStackTrace();
- e.getChannel().close();
- }
- }
这看起来很是简单,与服务端的实现也并未有什么不同。然而,这个处理器却时常会因为抛出IndexOutOfBoundsException异常而 拒绝工作。我们将在下一节讨论这个问题产生的原因。
1.7. 流数据的传输处理
1.7.1. Socket Buffer的缺陷
对于例如TCP/IP这种基于流的传输协议实现,接收到的数据会被存储在socket的接受缓冲区内。不幸的是,这种基于流的传输缓冲区并不是一个包队 列,而是一个字节队列。这意味着,即使你以两个数据包的形式发送了两条消息,操作系统却不会把它们看成是两条消息,而仅仅是一个批次的字节序列。因此,在 这种情况下我们就无法保证收到的数据恰好就是远程节点所发送的数据。例如,让我们假设一个操作系统的TCP/IP堆栈收到了三个数据包:
| ABC | DEF | GHI |
+—–+—–+—–+
由于这种流传输协议的普遍性质,在你的应用中有较高的可能会把这些数据读取为另外一种形式:
| AB | CDEFG | H | I |
+—-+——-+—+—+
因此对于数据的接收方,不管是服务端还是客户端,应当重构这些接收到的数据,让其变成一种可让你的应用逻辑易于理解的更有意义的数据结构。在上面所 述的这个例子中,接收到的数据应当重构为下面的形式:
| ABC | DEF | GHI |
+—–+—–+—–+
1.7.2. 第一种方案
现在让我们回到时间协议服务客户端的例子中。我们在这里遇到了同样的问题。一个32位的整数是一个非常小的数据量,因此它常常不会被切分在不同的数据段 内。然而,问题是它确实可以被切分在不同的数据段内,并且这种可能性随着流量的增加而提高。
最简单的方案是在程序内部创建一个可准确接收4字节数据的累积性缓冲。下面的代码是修复了这个问题后的TimeClientHandler实现。
- package org.jboss.netty.example.time;
- import static org.jboss.netty.buffer.ChannelBuffers.*;
- import java.util.Date;
- @ChannelPipelineCoverage(“one”)
- public class TimeClientHandler extends SimpleChannelHandler {
- private final ChannelBuffer buf = dynamicBuffer();
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- ChannelBuffer m = (ChannelBuffer) e.getMessage();
- buf.writeBytes(m);
- if (buf.readableBytes() >= 4) {
- long currentTimeMillis = buf.readInt() * 1000L;
- System.out.println(new Date(currentTimeMillis));
- e.getChannel().close();
- }
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
- e.getCause().printStackTrace();
- e.getChannel().close();
- }
- }
代码说明
2) 动态的buffer缓冲也是ChannelBuffer的一种实现,其拥有动态增加缓冲容量的能力。当你无法预估消息的数据长度时,动态的buffer缓 冲是一种很有用的缓冲结构。
3) 首先,所有的数据将会被累积的缓冲至buf容器。
4) 之后,这个处理器将会检查是否收到了足够的数据然后再进行真实的业务逻辑处理,在这个例子中需要接收4字节数据。否则,Netty将重复调用 messageReceived方法,直至4字节数据接收完成。
这里还有另一个地方需要进行修改。你是否还记得我们把TimeClientHandler实例添加到了这个ClientBootstrap实例的默 认ChannelPipeline管道里?这意味着同一个TimeClientHandler实例将被多个Channel通道共享,因此接受的数据也将受 到破坏。为了给每一个Channel通道创建一个新的TimeClientHandler实例,我们需要实现一个 ChannelPipelineFactory管道工厂:
- package org.jboss.netty.example.time;
- public class TimeClientPipelineFactory implements ChannelPipelineFactory {
- public ChannelPipeline getPipeline() {
- ChannelPipeline pipeline = Channels.pipeline();
- pipeline.addLast(“handler”, new TimeClientHandler());
- return pipeline;
- }
- }
现在,我们需要把TimeClient下面的代码片段:
- TimeClientHandler handler = new TimeClientHandler();
- bootstrap.getPipeline().addLast(“handler”, handler);
替换为:
- bootstrap.setPipelineFactory(new TimeClientPipelineFactory());
虽然这看上去有些复杂,并且由于在TimeClient内部我们只创建了一个连接(connection),因此我们在这里确实没必要引入 TimeClientPipelineFactory实例。
然而,当你的应用变得越来越复杂,你就总会需要实现自己的ChannelPipelineFactory,这个管道工厂将会令你的管道配置变得更加具有灵 活性。
1.7.3. 第二种方案
虽然第二种方案解决了时间协议客户端遇到的问题,但是这个修改后的处理器实现看上去却不再那么简洁。设想一种更为复杂的,由多个可变长度字段组成的 协议。你的ChannelHandler实现将变得越来越难以维护。
正如你已注意到的,你可以为一个ChannelPipeline添加多个ChannelHandler,因此,为了减小应用的复杂性,你可以把这个臃肿的 ChannelHandler切分为多个独立的模块单元。例如,你可以把TimeClientHandler切分为两个独立的处理器:
- TimeDecoder,解决数据分段的问题。
- TimeClientHandler,原始版本的实现。
幸运的是,Netty提供了一个可扩展的类,这个类可以直接拿过来使用帮你完成TimeDecoder的开发:
- package org.jboss.netty.example.time;
- public class TimeDecoder extends FrameDecoder {
- @Override
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
- if (buffer.readableBytes() < 4) {
- return null;
- }
- return buffer.readBytes(4);
- }
- }
2) 当接收到新的数据后,FrameDecoder会调用decode方法,同时传入一个FrameDecoder内部持有的累积型buffer缓冲。
3) 如果decode返回null值,这意味着还没有接收到足够的数据。当有足够数量的数据后FrameDecoder会再次调用decode方法。
4) 如果decode方法返回一个非空值,这意味着decode方法已经成功完成一条信息的解码。FrameDecoder将丢弃这个内部的累计型缓冲。请注 意你不需要对多条消息进行解码,FrameDecoder将保持对decode方法的调用,直到decode方法返回非空对象。
如果你是一个勇于尝试的人,你或许应当使用ReplayingDecoder,ReplayingDecoder更加简化了解码的过程。为此你需要 查看API手册获得更多的帮助信息。
- package org.jboss.netty.example.time;
- public class TimeDecoder extends ReplayingDecoder<VoidEnum> {
- @Override
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel,
- ChannelBuffer buffer, VoidEnum state) {
- return buffer.readBytes(4);
- }
- }
此外,Netty还为你提供了一些可以直接使用的decoder实现,这些decoder实现不仅可以让你非常容易的实现大多数协议,并且还会帮你 避免某些臃肿、难以维护的处理器实现。请参考下面的代码包获得更加详细的实例:
- org.jboss.netty.example.factorial for a binary protocol, and
- org.jboss.netty.example.telnet for a text line-based protocol
1.8. 使用POJO代替ChannelBuffer
目前为止所有的实例程序都是使用ChannelBuffer做为协议消息的原始数据结构。在这一节,我们将改进时间协议服务的客户/服务端实现,使用POJO 而不是ChannelBuffer做为协议消息的原始数据结构。
在你的ChannelHandler实现中使用POJO的优势是很明显的;从你的ChannelHandler实现中分离从ChannelBuffer获 取数据的代码,将有助于提高你的ChannelHandler实现的可维护性和可重用性。在时间协议服务的客户/服务端代码中,直接使用 ChannelBuffer读取一个32位的整数并不是一个主要的问题。然而,你会发现,当你试图实现一个真实的协议的时候,这种代码上的分离是很有必要 的。
首先,让我们定义一个称之为UnixTime的新类型。
- package org.jboss.netty.example.time;
- import java.util.Date;
- public class UnixTime {
- private final int value;
- public UnixTime(int value) {
- this.value = value;
- }
- public int getValue() {
- return value;
- }
- @Override
- public String toString() {
- return new Date(value * 1000L).toString();
- }
- }
现在让我们重新修改TimeDecoder实现,让其返回一个UnixTime,而不是一个ChannelBuffer。
- @Override
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
- if (buffer.readableBytes() < 4) {
- return null;
- }
- return new UnixTime(buffer.readInt());
- }
FrameDecoder和ReplayingDecoder允许你返回一个任何类型的对象。如果它们仅允许返回一个ChannelBuffer类 型的对象,我们将不得不插入另一个可以从ChannelBuffer对象转换 为UnixTime对象的ChannelHandler实现。
有了这个修改后的decoder实现,这个TimeClientHandler便不会再依赖ChannelBuffer。
- @Override
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- UnixTime m = (UnixTime) e.getMessage();
- System.out.println(m);
- e.getChannel().close();
- }
更加简单优雅了,不是吗?同样的技巧也可以应用在服务端,让我们现在更新TimeServerHandler的实现:
- @Override
- public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
- UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);
- ChannelFuture f = e.getChannel().write(time);
- f.addListener(ChannelFutureListener.CLOSE);
- }
现在剩下的唯一需要修改的部分是这个ChannelHandler实现,这个ChannelHandler实现需要把一个UnixTime对象重新 转换为一个ChannelBuffer。但这却已是相当简单了,因为当你对消息进行编码的时候你不再需要处理数据包的拆分及组装。
- package org.jboss.netty.example.time;
- import static org.jboss.netty.buffer.ChannelBuffers.*;
- @ChannelPipelineCoverage(“all”)
- public class TimeEncoder extends SimpleChannelHandler {
- public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
- UnixTime time = (UnixTime) e.getMessage();
- ChannelBuffer buf = buffer(4);
- buf.writeInt(time.getValue());
- Channels.write(ctx, e.getFuture(), buf);
- }
- }
代码说明
2) 一个encoder通过重写writeRequested方法来实现对写操作请求的拦截。不过请注意虽然这个writeRequested方法使用了和 messageReceived方法一样的MessageEvent参数,但是它们却分别对应了不同的解释。一个ChannelEvent事件可以既是一 个上升流事件(upstream event)也可以是一个下降流事件(downstream event),这取决于事件流的方向。例如:一个MessageEvent消息事件可以作为一个上升流事件(upstream event)被messageReceived方法调用,也可以作为一个下降流事件(downstream event)被writeRequested方法调用。请参考API手册获得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。
3) 一旦完成了POJO和ChannelBuffer转换,你应当确保把这个新的buffer缓冲转发至先前的 ChannelDownstreamHandler处理,这个下降通道的处理器由某个ChannelPipeline管理。Channels提供了多个可 以创建和发送ChannelEvent事件的帮助方法。在这个例子中,Channels.write(…)方法创建了一个新的 MessageEvent事件,并把这个事件发送给了先前的处于某个ChannelPipeline内的 ChannelDownstreamHandler处理器。
另外,一个很不错的方法是使用静态的方式导入Channels类:
import static org.jboss.netty.channel.Channels.*;
…
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx);
最后的任务是把这个TimeEncoder插入服务端的ChannelPipeline,这是一个很简单的步骤。
1.9. 关闭你的应用
如果你运行了TimeClient,你肯定可以注意到,这个应用并没有自动退出而只是在那里保持着无意义的运行。跟踪堆栈记录你可以发现,这里有一些运行 状态的I/O线程。为了关闭这些I/O线程并让应用优雅的退出,你需要释放这些由ChannelFactory分配的资源。
一个典型的网络应用的关闭过程由以下三步组成:
- 关闭负责接收所有请求的server socket。
- 关闭所有客户端socket或服务端为响应某个请求而创建的socket。
- 释放ChannelFactory使用的所有资源。
为了让TimeClient执行这三步,你需要在TimeClient.main()方法内关闭唯一的客户连接以及ChannelFactory使 用的所有资源,这样做便可以优雅的关闭这个应用。
- package org.jboss.netty.example.time;
- public class TimeClient {
- public static void main(String[] args) throws Exception {
- …
- ChannelFactory factory = …;
- ClientBootstrap bootstrap = …;
- …
- ChannelFuture future = bootstrap.connect(…);
- future.awaitUninterruptible();
- if (!future.isSuccess()) {
- future.getCause().printStackTrace();
- }
- future.getChannel().getCloseFuture().awaitUninterruptibly();
- factory.releaseExternalResources();
- }
- }
2) 阻塞式的等待,直到ChannelFuture对象返回这个连接操作的成功或失败状态。
3) 如果连接失败,我们将打印连接失败的原因。如果连接操作没有成功或者被取消,ChannelFuture对象的getCause()方法将返回连接失败的 原因。
4) 现在,连接操作结束,我们需要等待并且一直到这个Channel通道返回的closeFuture关闭这个连接。每一个Channel都可获得自己的 closeFuture对象,因此我们可以收到通知并在这个关闭时间点执行某种操作。
并且即使这个连接操作失败,这个closeFuture仍旧会收到通知,因为这个代表连接的 Channel对象将会在连接操作失败后自动关闭。
5) 在这个时间点,所有的连接已被关闭。剩下的唯一工作是释放ChannelFactory通道工厂使用的资源。这一步仅需要调用 releaseExternalResources()方法即可。包括NIO Secector和线程池在内的所有资源将被自动的关闭和终止。
关闭一个客户端应用是很简单的,但又该如何关闭一个服务端应用呢?你需要释放其绑定的端口并关闭所有接受和打开的连接。为了做到这一点,你需要使用 一种数据结构记录所有的活动连接,但这却并不是一件容易的事。幸运的是,这里有一种解决方案,ChannelGroup。
ChannelGroup是Java 集合 API的一个特有扩展,ChannelGroup内部持有所有打开状态的Channel通道。如果一个Channel通道对象被加入到 ChannelGroup,如果这个Channel通道被关闭,ChannelGroup将自动移除这个关闭的Channel通道对象。此外,你还可以对 一个ChannelGroup对象内部的所有Channel通道对象执行相同的操作。例如,当你关闭服务端应用时你可以关闭一个ChannelGroup 内部的所有Channel通道对象。
为了记录所有打开的socket,你需要修改你的TimeServerHandler实现,将一个打开的Channel通道加入全局的 ChannelGroup对象,TimeServer.allChannels:
- @Override
- public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
- TimeServer.allChannels.add(e.getChannel());
- }
现在,所有活动的Channel通道将被自动的维护,关闭一个服务端应用有如关闭一个客户端应用一样简单。
- package org.jboss.netty.example.time;
- public class TimeServer {
- static final ChannelGroup allChannels = new DefaultChannelGroup(“time-server” );
- public static void main(String[] args) throws Exception {
- …
- ChannelFactory factory = …;
- ServerBootstrap bootstrap = …;
- …
- Channel channel = bootstrap.bind(…);
- allChannels.add(channel);
- waitForShutdownCommand();
- ChannelGroupFuture future = allChannels.close();
- future.awaitUninterruptibly();
- factory.releaseExternalResources();
- }
- }
2) ServerBootstrap对象的bind方法返回了一个绑定了本地地址的服务端Channel通道对象。调用这个Channel通道的 close()方法将释放这个Channel通道绑定的本地地址。
3) 不管这个Channel对象属于服务端,客户端,还是为响应某一个请求创建,任何一种类型的Channel对象都会被加入ChannelGroup。因 此,你尽可在关闭服务时关闭所有的Channel对象。
4) waitForShutdownCommand()是一个想象中等待关闭信号的方法。你可以在这里等待某个客户端的关闭信号或者JVM的关闭回调命令。
5) 你可以对ChannelGroup管理的所有Channel对象执行相同的操作。在这个例子里,我们将关闭所有的通道,这意味着绑定在服务端特定地址的 Channel通道将解除绑定,所有已建立的连接也将异步关闭。为了获得成功关闭所有连接的通知,close()方法将返回一个 ChannelGroupFuture对象,这是一个类似ChannelFuture的对象。
1.10. 总述
在这一章节,我们快速浏览并示范了如何使用Netty开发网络应用。下一章节将涉及更多的问题。同时请记住,为了帮助你以及能够让Netty基于你的回馈 得到持续的改进和提高,Netty社区 将永远欢迎你的问题及建议。
第二章. 架构总览

在这个章节,我们将阐述Netty提供的核心功能以及在此基础之上如何构建一个完备的网络应用。
2.1. 丰富的缓冲实现
Netty使用自建的buffer API,而不是使用NIO的ByteBuffer来代表一个连续的字节序列。与ByteBuffer相比这种方式拥有明显的优势。Netty使用新的 buffer类型ChannelBuffer,ChannelBuffer被设计为一个可从底层解决ByteBuffer问题,并可满足日常网络应用开发 需要的缓冲类型。这些很酷的特性包括:
- 如果需要,允许使用自定义的缓冲类型。
- 复合缓冲类型中内置的透明的零拷贝实现。
- 开箱即用的动态缓冲类型,具有像StringBuffer一样的动态缓冲能力。
- 不再需要调用的flip()方法。
- 正常情况下具有比ByteBuffer更快的响应速度。
更多信息请参考:org.jboss.netty.buffer package description
2.2. 统一的异步 I/O API
传统的Java I/O API在应对不同的传输协议时需要使用不同的类型和方法。例如:java.net.Socket 和 java.net.DatagramSocket它们并不具有相同的超类型,因此,这就需要使用不同的调用方式执行socket操作。
这种模式上的不匹配使得在更换一个网络应用的传输协议时变得繁杂和困难。由于(Java I/O API)缺乏协议间的移植性,当你试图在不修改网络传输层的前提下增加多种协议的支持,这时便会产生问题。并且理论上讲,多种应用层协议可运行在多种传输 层协议之上例如TCP/IP,UDP/IP,SCTP和串口通信。
让这种情况变得更糟的是,Java新的I/O(NIO)API与原有的阻塞式的I/O(OIO)API并不兼容,NIO.2(AIO)也是如此。由于所有 的API无论是在其设计上还是性能上的特性都与彼此不同,在进入开发阶段,你常常会被迫的选择一种你需要的API。
例如,在用户数较小的时候你可能会选择使用传统的OIO(Old I/O) API,毕竟与NIO相比使用OIO将更加容易一些。然而,当你的业务呈指数增长并且服务器需要同时处理成千上万的客户连接时你便会遇到问题。这种情况下 你可能会尝试使用NIO,但是复杂的NIO Selector编程接口又会耗费你大量时间并最终会阻碍你的快速开发。
Netty有一个叫做Channel的统一的异步I/O编程接口,这个编程接口抽象了所有点对点的通信操作。也就是说,如果你的应用是基于Netty的某 一种传输实现,那么同样的,你的应用也可以运行在Netty的另一种传输实现上。Netty提供了几种拥有相同编程接口的基本传输实现:
- NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),
- OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),
- OIO-based UDP/IP transport, and
- Local transport (See org.jboss.netty.channel.local).
切换不同的传输实现通常只需对代码进行几行的修改调整,例如选择一个不同的ChannelFactory实现。
此外,你甚至可以利用新的传输实现没有写入的优势,只需替换一些构造器的调用方法即可,例如串口通信。而且由于核心API具有高度的可扩展性,你还可以完 成自己的传输实现。
2.3. 基于拦截链模式的事件模型
一个定义良好并具有扩展能力的事件模型是事件驱动开发的必要条件。Netty具有定义良好的I/O事件模型。由于严格的层次结构区分了不同的事件类型,因 此Netty也允许你在不破坏现有代码的情况下实现自己的事件类型。这是与其他框架相比另一个不同的地方。很多NIO框架没有或者仅有有限的事件模型概 念;在你试图添加一个新的事件类型的时候常常需要修改已有的代码,或者根本就不允许你进行这种扩展。
在一个ChannelPipeline内部一个ChannelEvent被一组ChannelHandler处理。这个管道是拦 截过滤器 模式的一种高级形式的实现,因此对于一个事件如何被处理以及管道内部处理器间的交互过程,你都将拥有绝对的控制力。例如,你可以定义一个从socket读 取到数据后的操作:
- public class MyReadHandler implements SimpleChannelHandler {
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
- Object message = evt.getMessage();
- // Do something with the received message.
- …
- // And forward the event to the next handler.
- ctx.sendUpstream(evt);
- }
- }
同时你也可以定义一种操作响应其他处理器的写操作请求:
- public class MyWriteHandler implements SimpleChannelHandler {
- public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
- Object message = evt.getMessage();
- // Do something with the message to be written.
- …
- // And forward the event to the next handler.
- ctx.sendDownstream(evt);
- }
- }
有关事件模型的更多信息,请参考API文档ChannelEvent和ChannelPipeline部分。
2.4. 适用快速开发的高级组件
上述所提及的核心组件已经足够实现各种类型的网络应用,除此之外,Netty也提供了一系列的高级组件来加速你的开发过程。
2.4.1. Codec框架
就像“1.8. 使用POJO代替ChannelBuffer”一节所展示的那样,从业务逻辑代码中分离协议处理部分总是一个很不错的想法。然而如果一切从零开始便会遭遇 到实现上的复杂性。你不得不处理分段的消息。一些协议是多层的(例如构建在其他低层协议之上的协议)。一些协议过于复杂以致难以在一台主机(single state machine)上实现。
因此,一个好的网络应用框架应该提供一种可扩展,可重用,可单元测试并且是多层的codec框架,为用户提供易维护的codec代码。
Netty提供了一组构建在其核心模块之上的codec实现,这些简单的或者高级的codec实现帮你解决了大部分在你进行协议处理开发过程会遇到的问 题,无论这些协议是简单的还是复杂的,二进制的或是简单文本的。
2.4.2. SSL / TLS 支持
不同于传统阻塞式的I/O实现,在NIO模式下支持SSL功能是一个艰难的工作。你不能只是简单的包装一下流数据并进行加密或解密工作,你不得不借助于 javax.net.ssl.SSLEngine,SSLEngine是一个有状态的实现,其复杂性不亚于SSL自身。你必须管理所有可能的状态,例如密 码套件,密钥协商(或重新协商),证书交换以及认证等。此外,与通常期望情况相反的是SSLEngine甚至不是一个绝对的线程安全实现。
在Netty内部,SslHandler封装了所有艰难的细节以及使用SSLEngine可能带来的陷阱。你所做的仅是配置并将该SslHandler插 入到你的ChannelPipeline中。同样Netty也允许你实现像StartTlS 那样所拥有的高级特性,这很容易。
2.4.3. HTTP实现
HTTP无疑是互联网上最受欢迎的协议,并且已经有了一些例如Servlet容器这样的HTTP实现。因此,为什么Netty还要在其核心模块之上构建一 套HTTP实现?
与现有的HTTP实现相比Netty的HTTP实现是相当与众不同的。在HTTP消息的低层交互过程中你将拥有绝对的控制力。这是因为Netty的 HTTP实现只是一些HTTP codec和HTTP消息类的简单组合,这里不存在任何限制——例如那种被迫选择的线程模型。你可以随心所欲的编写那种可以完全按照你期望的工作方式工作 的客户端或服务器端代码。这包括线程模型,连接生命期,快编码,以及所有HTTP协议允许你做的,所有的一切,你都将拥有绝对的控制力。
由于这种高度可定制化的特性,你可以开发一个非常高效的HTTP服务器,例如:
- 要求持久化链接以及服务器端推送技术的聊天服务(e.g. Comet )
- 需要保持链接直至整个文件下载完成的媒体流服务(e.g. 2小时长的电影)
- 需要上传大文件并且没有内存压力的文件服务(e.g. 上传1GB文件的请求)
- 支持大规模mash-up应用以及数以万计连接的第三方web services异步处理平台
2.4.4. Google Protocol Buffer 整合
Google Protocol Buffers 是快速实现一个高效的二进制协议的理想方案。通过使用ProtobufEncoder和ProtobufDecoder,你可以把Google Protocol Buffers 编译器 (protoc)生成的消息类放入到Netty的codec实现中。请参考“LocalTime ”实例,这个例子也同时显示出开发一个由简 单协议定义 的客户及服务端是多么的容易。
2.5. 总述
在这一章节,我们从功能特性的角度回顾了Netty的整体架构。Netty有一个简单却不失强大的架构。这个架构由三部分组成——缓冲(buffer), 通道(channel),事件模型(event model)——所有的高级特性都构建在这三个核心组件之上。一旦你理解了它们之间的工作原理,你便不难理解在本章简要提及的更多高级特性。
你可能对Netty的整体架构以及每一部分的工作原理仍旧存有疑问。如果是这样,最好的方式是告诉我们 应该如何改进这份指南。
Popularity: unranked [?]
IntelliJ IDEA 9.0.1 注册码
RMI和Socket的比较及区别
本文的测试数据来自国外专家文档,详细介绍如下:
一般来说,基于CS(client-server)软件架构的开发技术有很多种。比较常用的有:基于socket的网络编程、RPC、基于Java技术的RMI(当然C#也有类似技术)、CORBA等。在这里我们只是对基于socket的网络编程与RMI作个对比,有助于我们了解它们各自的应用领域,帮助我们在面对一个具体问题的时候选用适合的技术。另外,本文所做的讨论可以认为是脱离了语言层面的东西,只是对技术的本身做一个讨论,无关乎你是用C++、C#或Java 在开发。
一、RMI技术简介
本文就以Java为例,简单介绍一下RMI技术。
从Java1.1开始,远程方法调用作为Java分布式对象技术成为Java核心的API之一(在java.rmi.* 包)。RMI的引入,使得Java程序之间能够实现灵活的,可扩展的分布式通信。RMI允许Java对象存在于多个不同的地址空间,分布在不同的Java虚拟机上。每一个地址空间可以在同一台主机上或者网络上不同的计算机上。由于远程方法调用跨越不同的虚拟机边界到不同的指定的地址空间,所以没有对象共享的全局变量,这就需要对象序列化(Object Serialization)API,它使得Java对象能够在不同的JVM之间传递。对象序列化是特别为Java的对象设计的,这就意味着Java程序中的对象可以作为对象参数存取(可序列化的对象必须实现Serializable接口)。结合RMI和对象序列化机制,就可以访问越过本地Java虚拟机边界的对象以及数据。通过RMI,可以调用远程对象的远程方法,而通过Java对象序列化机制可以将对象传递给这些方法。
最基本的Java模型并没有提供将远程主机上的Java对象看作本地Java程序地址空间一部分的能力,而RMI祢补了这一不足。另外,由于Java与硬件平台无关的特性,无论是同构的系统还是异构的系统,RMI不需移植就可以顺利运行。
RMI为Java平台的分布式计算提供了一个简单而直接的模型。因为Java的RMI技术是基于Java平台的,所以它将Java平台的安全性和可移植性等优点带到了分布式计算中。RMI大大扩展Java的网络计算能力,它为编写基于分布式对象技术的企业级Internet/Intranet应用提供了强大的系统平台支持。
二、基于socket的网络编程
当你使用socket进行网络应用开发的时候,一般的思路是“消息驱动逻辑”,即这样的软件系统一般具有以下特点:
(1) 客户端与服务器端依靠消息进行通讯。
(2) 客户端或者服务器端都需要一个消息派遣器,将消息投递给具体的massage handler
(3) 客户端或者服务器端利用massage handler进行逻辑事务处理
三、RMI Vs Sochet
RMI技术比较socket的网络编程主要有以下几个方面:
第一、.RMI是面向对象的,而后者不是。
第二、.RMI是与语言相绑定的。比如当你使用Java RMI技术的时候,客户端与服务器端都必须使用Java开发。而socket的网络编程是使用独立于开发语言的,甚至独立于平台。基于socket的网络编程,客户端与服务器端可以使用不同开发语言和不同的平台。
第三、从网络协议栈的观点来看,RMI与socket的网络编程处于不同层次上。基于socket的网络编程位于TCP协议之上,而RMI在TCP协议之上,又定义了自己的应用协议,其传输层采用的是Java远程方法协议(JRMP)。可见,在网络协议栈上,基于RMI的应用位置更高一些,这也决定了,与socket的网络编程相比,RMI会丧失一些灵活性和可控性,但是好处是它带给了应用开发者更多的简洁,方便和易用。比如:如果你用的是RMI,你不需要关心消息是怎么序列化的,你只需要像本地方法调用一样,使用RMI。代价是:应用开发者无法很好地控制消息的序列化机制。
第四、这是最后一点不同,我认为也是比较重要的一点,就是两种方法的性能比较,其往往决定着你将使用那种技术来开发你的应用。以下引用Adrian Reber在Network-programming with RMI文中对TCP和RMI所做的一个比较,其做的实验主要是对两者在网络传输的带宽上作的对比: 在网络上传输2 byte的有效数据,对于TCP而言,总共有478 byte被额外传输,而对于RMI, 1645byte被额外传输。
以下是两者的trace结果:
TCP:
46037 > 12345 [SYN] Seq=801611567 Ack=0 Win=5840 Len=0
12345 > 46037 [SYN, ACK] Seq=266515894 Ack=801611568 Win=10136 Len=0
46037 > 12345 [ACK] Seq=801611568 Ack=266515895 Win=5840 Len=0
12345 > 46037 [PSH, ACK] Seq=266515895 Ack=801611568 Win=10136 Len=1
46037 > 12345 [ACK] Seq=801611568 Ack=266515896 Win=5840 Len=0
12345 > 46037 [FIN, PSH, ACK] Seq=266515896 Ack=801611568 Win=10136 Len=1
46037 > 12345 [RST, ACK] Seq=801611568 Ack=266515898 Win=5840 Len=0
RMI:
42749 > rmiregistry [SYN, ECN, CWR]
Seq=3740552479 Ack=0 Win=32767 Len=0
rmiregistry > 42749 [SYN, ACK, ECN]
Seq=3749262223 Ack=3740552480 Win=32767 Len=0
42749 > rmiregistry [ACK] Seq=3740552480 Ack=3749262224 Win=32767 Len=0
JRMI, Version: 2, StreamProtocol
rmiregistry > 42749 [ACK] Seq=3749262224 Ack=3740552487 Win=32767 Len=0
JRMI, ProtocolAck
42749 > rmiregistry [ACK] Seq=3740552487 Ack=3749262240 Win=32767 Len=0
Continuation
rmiregistry > 42749 [ACK] Seq=3749262240 Ack=3740552506 Win=32767 Len=0
JRMI, Call
rmiregistry > 42749 [ACK] Seq=3749262240 Ack=3740552556 Win=32767 Len=0
JRMI, ReturnData
42749 > rmiregistry [ACK] Seq=3740552556 Ack=3749262442 Win=32767 Len=0
JRMI, Ping
JRMI, PingAck
42749 > rmiregistry [ACK] Seq=3740552557 Ack=3749262443 Win=32767 Len=0
JRMI, DgcAck
42749 > rmiregistry [FIN, ACK]
Seq=3740552572 Ack=3749262443 Win=32767 Len=0
rmiregistry > 42749 [FIN, ACK]
Seq=3749262443 Ack=3740552573 Win=32767 Len=0
42749 > rmiregistry [ACK] Seq=3740552573 Ack=3749262444 Win=32767 Len=0
实验的结果是:RMI与TCP based socket相比,传输相同的有效数据,RMI需要占用更多的网络带宽(protocol overhead)。从这里,我们可以得出一个一般性的结论:RMI主要是用于远程方法的”调用“(RMI是多么的名符其实:)),其技术内涵强调的是“调用”,基于此,我能想到的是:移动计算,和远程控制,当你的应用不需要在client与server之间传输大量的数据时,RMI是较好的选择,它简洁、易于开发。但是,一旦你的应用需要在client与server之间传输大量的数据,极端的,比如FTP应用,则RMI是不适合的,我们应该使用socket。
四、参考资料:
Network-programming with RMI, by Adrian Reber, URL:
http://42.fht-esslingen.de/~adrian/master/rmi.pdf
Popularity: unranked [?]
解决Java打包成Jar找不到文件问题
使用这个就可以了 getClass().getResourceAsStream(”/source/config.xml”)
例:
Element root = dombuilder.parse(getClass().getResourceAsStream(”/config.xml”)).getDocumentElement();
Popularity: unranked [?]
最近评论