郭洪锋 ()该文章对编写客户服务器应用的java程序员有所帮助可以解决程序在对方出现故障的时候继续稳定运行
前言java程序要处理很多的网络数据网络数据发送和接收以及数据流的处理是java程序要特别关注的方面随着java的发展这些方法也越来越得到重视和加强本文从几个方面解释了java正确处理网络数据流的要素这些也是java程序员必须了解的基本的知识
庞大的java流处理
首先之所以说java流的庞大是因为java中的流处理比其他语言的流处理在内容上多的多
java流在处理上分为字符流和字节流字符流处理的单元为个字节的Unicode字符分别操作字符字符数组或字符串而字节流处理单元为个字节操作字节和字节数组
Java内用Unicode编码存储字符字符流处理类负责将外部的其他编码的字符流和java内Unicode字符流之间的转换而类InputStreamReader和OutputStreamWriter处理字符流和字节流的转换字符流(一次可以处理一个缓沖区)一次操作比字节流(一次一个字节)效率高
对应不同的流需要不同的流构建器或流过滤实现java目前依然在逐渐增加其流处理方法虽然java类库的创作人员可以列举出很多理由来说明这要做的优点但我还是觉得java开始变得向其他语言一样复杂起来
网络数据流的收发
java对网络数据的发送和接收处理也借用了一般流处理的方法我们知道在几乎其他所有语言中网络数据的收发在利用类似send(或write)和recv(或read)的方法时并没有明显的流处理但是java和这些语言的收发方法有较大区别要借助流才可以完成
sock = new Socket(addr port);
OutputStream os = sockgetOutputStream();
InputStream is = sockgetInputStream();
oswrite(byte[] b);
isread(byte[] b);
这些方法总给人一种不太舒服的感觉不过从Jdk开始弥补了这一点JDK中新增加了新的I/O流处理在缓沖区管理可伸缩网络和文件IO字符集支持正规表达式匹配方面做了新的处理其中缓沖区管理和通道(Channel)概念则是对网络数据流的收发处理支持的强化缓沖区管理中ByteBuffer类更好的支持了网络数据流处理在网络连接中通道代表了sockets的连接基于这些新的IO处理以上代码可以改写为
ByteBuffer bytebuf = ByteBufferallocate(); // 创建一个指定大小的缓沖区
InetSocketAddress isa = new InetSocketAddress(hostnameport);
sc = SocketChannelopen(); // 建立一个socket通道
nnect( isa);// 建立一个socket连接
…
scwrite(bytebuf); // 发送数据
…
scread(bytebuf); // 接收数据
这样的程序似乎要流畅的多
java对网络数据流的处理
java程序对网络数据流的处理要关注四个基本方面数据流的编码字节顺序数据格式对应和取数这是四个不同的问题但是都影响到网络数据的正确接收
网络数据流的解码和编码
网络数据流的编码和解码主要针对流中出现的字符串网络数据流中的字符串均为原始的字节流形式
要正确接收网络数据流中的字符串首先要知道该字符串的编码方案然后才可以调用解码的方法获得java能够认识的Unicode编码字符串可以用如下代码处理网络数据流中字符串的编码和解码
// 获得编码对象即网络对等方的认识的字符串编码
Charset charset = CharsetforName(???); // ???为对等方的编码名java必须支持
// 生成编码器和解码器对象
CharsetDecoder decoder = charsetnewDecoder();
CharsetEncoder encoder = charsetnewEncoder();
// 对从网络数据流中获得的字节流解码取得java字符串
CharBuffer charbuf = decoderdecode(bytebuff);
// 将java字符串编码成指定编码的字节流以便网络发送
Bytebuffbytebuff = encoderencode(CharBufferwrap(Test String);
网络数据流的字节顺序
目前的字节顺序有两类BIG_ENGIAN和LITTLE_ENDIAN各个平台所支持的字节序不同例如AIXTruUnixWindows等操作系统平台采用LITTLE_ENDIAN字节序Solaris等操作系统平台采用BIG_ENGIANJava自身采用的是BIG_ENGIAN字节序当java和运行在其他平台上的其他语言编写的通信程序通信时则必须考虑到数据的字节序
Jkd新增加的包NIO中的类ByteOrder则带来了一定的方便针对从网络数据流的字节序我们只要增加一行就可以轻松的处理字节序了
bytebuforder(ByteOrderLITTLE_ENDIAN); //按照LITTLE_ENDIAN字节序收发数据
scread(bytebuf); // 接收数据
上面的方法虽然简化了我们的编程但没有真正处理好分布式应用的网络数据字节序问题例如java同时和在TruUnixSolaris平台上的应用通信时上述方法就不能解决问题因为同一数据包可能无法判断其字节序是那一种此时要求网络数据包内携带附加的字节序信息显然是不现实的这种情况下java语言需要提供对XDR(外部数据表达)的支持目前XDR已经为事实上的网络数据流的标准格式分布式应用的网络数据流基本都遵循了这种格式如果java语言提供了对XDR的支持就可以解决通用性的问题对于分布式应用中的网络数据流的处理就无需再根据其平台判断其字节序只要按照XDR格式进行处理就可以了
网络数据流中数据格式的对应
C/C++语言编写的网络程序中一般采用数据结构的缓沖区发送数据在java端接收数据时会出现一些因数据组织引起的问题 如结构 typedef struct {
int id;
charname[];
shortval;
floatfval;
} SendData
在位操作系统中它的大小并不是而是!数据的组织如下图所示
当通过网络发送到客户端时客户端也接收到个字节如果按照顺序依次取相应的值则会发现最后取得的浮点值不正确这是因为把短整型数据后没有意义的两位作为了浮点数中的其中两位如果想正确接收该数据则必须跳过短整型数据后没有意义的两位再取浮点值
而如果以上的结构变为
typedef struct {
int id;
charname[];
floatfval;
shortval;
}
则java端按照顺序依次接收数据就不会发生问题
所以在编写程序时对数据的正确组织也是非常重要的
从网络数据流中取得需要的数据
在C/C++的Socket编程时采用数据结构收发数据很方便特别是接收数据时可以由数据结构的数据类型自动获得网络数据流相应的数据但是在java中目前我们必须对流进行分析逐一的取得自己所需要的数据并且由于网络数据流是原始的数据流还要根据程序所需要的数据类型对网络数据流进行解码处理发送网络数据时同样需要对数据进行封装这个过程也增加了java程序的烦琐性例如上述结构要用如下代码获取相应数据
int id = bytebufgetInt(); // 获得整数型值
int limit = bytebuflimit(); // 获得字节缓沖区的限值
bytebuflimit(); // 设置字节缓沖区的限值为字符串后面的第一个字节位置
CharBuffer charbuf = decoderdecode(bytebuf); // 解码获得字符串
Bytebuflimit(limit); // 恢复字节缓沖区原来的限值
float fval = bytebufgetfloat(); // 获得浮点型值
short val = bytebufgetshort(); // 获得短整型数值
结束语
从上面的介绍可以看出java程序中对网络数据流的处理涉及的问题较多在编写网络程序时必须注意这些问题以使得程序正确的处理通信的内容
参考资料
关于作者
郭洪锋在烟台东方电子信息产业集团公司中心所工作一直从事分布式系统的开发和研究