郭洪鋒 ()該文章對編寫客戶服務器應用的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 = sock
getOutputStream();
InputStream is = sock
getInputStream();
os
write(byte[] b);
is
read(byte[] b);
這些方法總給人一種不太舒服的感覺
不過從Jdk
開始彌補了這一點
JDK
中新增加了新的I/O流處理
在緩沖區管理
可伸縮網絡和文件IO
字符集支持
正規表達式匹配方面做了新的處理
其中緩沖區管理和通道(Channel)概念則是對網絡數據流的收發處理支持的強化
緩沖區管理中ByteBuffer類更好的支持了網絡數據流處理
在網絡連接中
通道代表了sockets的連接
基於這些新的IO處理
以上代碼可以改寫為
ByteBuffer bytebuf = ByteBuffer
allocate(
); // 創建一個指定大小的緩沖區
InetSocketAddress isa = new InetSocketAddress(hostname
port);
sc = SocketChannel
open(); // 建立一個socket通道
nnect( isa); // 建立一個socket連接
…
sc
write(bytebuf); // 發送數據
…
sc
read(bytebuf); // 接收數據
這樣的程序似乎要流暢的多
java對網絡數據流的處理
java程序對網絡數據流的處理要關注四個基本方面
數據流的編碼
字節順序
數據格式對應和取數
這是四個不同的問題
但是都影響到網絡數據的正確接收
網絡數據流的解碼和編碼
網絡數據流的編碼和解碼主要針對流中出現的字符串
網絡數據流中的字符串均為原始的字節流形式
要正確接收網絡數據流中的字符串
首先要知道該字符串的編碼方案
然後才可以調用解碼的方法獲得java能夠認識的Unicode編碼字符串
可以用如下代碼處理網絡數據流中字符串的編碼和解碼
// 獲得編碼對象
即網絡對等方的認識的字符串編碼
Charset charset = Charset
forName(
???
); // ???為對等方的編碼名
java必須支持
// 生成編碼器和解碼器對象
CharsetDecoder decoder = charset
newDecoder();
CharsetEncoder encoder = charset
newEncoder();
// 對從網絡數據流中獲得的字節流解碼取得java字符串
CharBuffer charbuf = decoder
decode(bytebuff);
// 將java字符串編碼成指定編碼的字節流
以便網絡發送
Bytebuff bytebuff = encoder
encode(CharBuffer
wrap(
Test String
);
網絡數據流的字節順序
目前的字節順序有兩類
BIG_ENGIAN和LITTLE_ENDIAN
各個平台所支持的字節序不同
例如AIX
Tru
Unix
Windows等操作系統平台采用LITTLE_ENDIAN字節序
Solaris等操作系統平台采用BIG_ENGIAN
Java自身采用的是BIG_ENGIAN字節序
當java和運行在其他平台上的其他語言編寫的通信程序通信時
則必須考慮到數據的字節序
Jkd
新增加的包NIO中的類ByteOrder則帶來了一定的方便
針對從網絡數據流的字節序
我們只要增加一行就可以輕松的處理字節序了
bytebuf
order(ByteOrder
LITTLE_ENDIAN); //按照LITTLE_ENDIAN字節序收發數據
sc
read(bytebuf); // 接收數據
上面的方法雖然簡化了我們的編程
但沒有真正處理好分布式應用的網絡數據字節序問題
例如
java同時和在Tru
Unix
Solaris平台上的應用通信時
上述方法就不能解決問題
因為同一數據包
可能無法判斷其字節序是那一種
此時要求網絡數據包內攜帶附加的字節序信息顯然是不現實的
這種情況下
java語言需要提供對XDR(外部數據表達)的支持
目前XDR已經為事實上的網絡數據流的標准格式
分布式應用的網絡數據流基本都遵循了這種格式
如果java語言提供了對XDR的支持
就可以解決通用性的問題
對於分布式應用中的網絡數據流的處理就無需再根據其平台判斷其字節序
只要按照XDR格式進行處理就可以了
網絡數據流中數據格式的對應
C/C++語言編寫的網絡程序中一般采用數據結構的緩沖區發送數據
在java端接收數據時
會出現一些因數據組織引起的問題
如結構 typedef struct {
int id;
char name[
];
short val;
float fval;
} SendData
在
位操作系統中
它的大小並不是
而是
!數據的組織如下圖所示
當通過網絡發送到客戶端時
客戶端也接收到
個字節
如果按照順序依次取相應的值
則會發現最後取得的浮點值不正確
這是因為把短整型數據後沒有意義的兩位作為了浮點數中的其中兩位
如果想正確接收該數據
則必須跳過短整型數據後沒有意義的兩位
再取浮點值
而如果以上的結構變為
typedef struct {
int id;
char name[
];
float fval;
short val;
}
則java端按照順序依次接收數據就不會發生問題
所以
在編寫程序時
對數據的正確組織也是非常重要的
從網絡數據流中取得需要的數據
在C/C++的Socket編程時
采用數據結構收發數據很方便
特別是接收數據時
可以由數據結構的數據類型自動獲得網絡數據流相應的數據
但是在java中
目前我們必須對流進行分析
逐一的取得自己所需要的數據
並且由於網絡數據流是原始的數據流
還要根據程序所需要的數據類型對網絡數據流進行解碼處理
發送網絡數據時同樣需要對數據進行封裝
這個過程也增加了java程序的煩瑣性
例如上述結構
要用如下代碼獲取相應數據
int id = bytebuf
getInt(); // 獲得整數型值
int limit = bytebuf
limit(); // 獲得字節緩沖區的限值
bytebuf
limit(
); // 設置字節緩沖區的限值
為字符串後面的第一個字節位置
CharBuffer charbuf = decoder
decode(bytebuf); // 解碼獲得字符串
Bytebuf
limit(limit); // 恢復字節緩沖區原來的限值
float fval = bytebuf
getfloat(); // 獲得浮點型值
short val = bytebuf
getshort(); // 獲得短整型數值
結束語
從上面的介紹可以看出
java程序中對網絡數據流的處理涉及的問題較多
在編寫網絡程序時
必須注意這些問題
以使得程序正確的處理通信的內容
參考資料
關於作者
郭洪鋒
在煙台東方電子信息產業集團公司中心所工作
一直從事分布式系統的開發和研究
From:http://tw.wingwit.com/Article/program/Java/hx/201311/27197.html