前言:java程序要處理很多的網絡數據,網絡數據發送和接收以及數據流的處理是java程序要特別關注的方面,隨著java的發展,這些方法也越來越得到重視和加強。本文從幾個方面解釋了java正確處理網絡數據流的要素,這些也是java程序員必須了解的基本的知識。
1:龐大的java流處理
首先,之所以說java流的龐大,是因為java中的流處理比其他語言的流處理在內容上多的多。
java流在處理上分為字符流和字節流。字符流處理的單元為2個字節的Unicode字符,分別操作字符、字符數組或字符串,而字節流處理單元為1個字節,操作字節和字節數組。
Java內用Unicode編碼存儲字符,字符流處理類負責將外部的其他編碼的字符流和java內Unicode字符流之間的轉換。而類InputStreamReader和OutputStreamWriter處理字符流和字節流的轉換。字符流(一次可以處理一個緩沖區)一次操作比字節流(一次一個字節)效率高。
對應不同的流,需要不同的流構建器或流過濾實現。java目前依然在逐漸增加其流處理方法,雖然java類庫的創作人員可以列舉出很多理由來說明這要做的優點,但我還是覺得java開始變得向其他語言一樣復雜起來。
2:網絡數據流的收發
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);
|
這些方法總給人一種不太舒服的感覺。不過從Jdk1.4開始彌補了這一點。JDK1.4中新增加了新的I/O流處理,在緩沖區管理、可伸縮網絡和文件IO、字符集支持、正規表達式匹配方面做了新的處理。其中緩沖區管理和通道(Channel)概念則是對網絡數據流的收發處理支持的強化。緩沖區管理中ByteBuffer類更好的支持了網絡數據流處理。在網絡連接中,通道代表了sockets的連接。基于這些新的IO處理,以上代碼可以改寫為:
......
ByteBuffer bytebuf = ByteBuffer.allocate(2048); // 創建一個指定大小的緩沖區
InetSocketAddress isa = new InetSocketAddress(hostname,port);
sc = SocketChannel.open(); // 建立一個socket通道
sc.connect( isa); // 建立一個socket連接
…
sc.write(bytebuf); // 發送數據
…
sc.read(bytebuf); // 接收數據
這樣的程序似乎要流暢的多。
|
3:java對網絡數據流的處理
java程序對網絡數據流的處理要關注四個基本方面:數據流的編碼,字節順序,數據格式對應和取數。這是四個不同的問題,但是都影響到網絡數據的正確接收。
3.1 網絡數據流的解碼和編碼
網絡數據流的編碼和解碼主要針對流中出現的字符串。網絡數據流中的字符串均為原始的字節流形式。
要正確接收網絡數據流中的字符串,首先要知道該字符串的編碼方案。然后才可以調用解碼的方法獲得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");
.......
|
3.2 網絡數據流的字節順序
目前的字節順序有兩類:BIG_ENGIAN和LITTLE_ENDIAN。各個平臺所支持的字節序不同,例如AIX、Tru64Unix、Windows等操作系統平臺采用LITTLE_ENDIAN字節序,Solaris等操作系統平臺采用BIG_ENGIAN。Java自身采用的是BIG_ENGIAN字節序,當java和運行在其他平臺上的其他語言編寫的通信程序通信時,則必須考慮到數據的字節序。
Jkd1.4新增加的包NIO中的類ByteOrder則帶來了一定的方便。針對從網絡數據流的字節序,我們只要增加一行就可以輕松的處理字節序了:
bytebuf.order(ByteOrder.LITTLE_ENDIAN); //按照LITTLE_ENDIAN字節序收發數據
sc.read(bytebuf); // 接收數據
上面的方法雖然簡化了我們的編程,但沒有真正處理好分布式應用的網絡數據字節序問題。例如,java同時和在Tru64Unix、Solaris平臺上的應用通信時,上述方法就不能解決問題。因為同一數據包,可能無法判斷其字節序是那一種。此時要求網絡數據包內攜帶附加的字節序信息顯然是不現實的。這種情況下,java語言需要提供對XDR(外部數據表達)的支持,目前XDR已經為事實上的網絡數據流的標準格式,分布式應用的網絡數據流基本都遵循了這種格式,如果java語言提供了對XDR的支持,就可以解決通用性的問題。對于分布式應用中的網絡數據流的處理就無需再根據其平臺判斷其字節序,只要按照XDR格式進行處理就可以了。
3.3 網絡數據流中數據格式的對應
C/C++語言編寫的網絡程序中一般采用數據結構的緩沖區發送數據,在java端接收數據時,會出現一些因數據組織引起的問題:
如結構 typedef struct {
int id;
char name[32];
short val;
float fval;
} SendData
|
在32位操作系統中,它的大小并不是42,而是44!數據的組織如下圖所示:
當通過網絡發送到客戶端時,客戶端也接收到44個字節,如果按照順序依次取相應的值,則會發現最后取得的浮點值不正確。這是因為把短整型數據后沒有意義的兩位作為了浮點數中的其中兩位。如果想正確接收該數據,則必須跳過短整型數據后沒有意義的兩位,再取浮點值。
而如果以上的結構變為:
typedef struct {
int id;
char name[32];
float fval;
short val;
}
|
則java端按照順序依次接收數據就不會發生問題。
所以,在編寫程序時,對數據的正確組織也是非常重要的。
3.4從網絡數據流中取得需要的數據
在C/C++的Socket編程時,采用數據結構收發數據很方便,特別是接收數據時,可以由數據結構的數據類型自動獲得網絡數據流相應的數據。但是在java中,目前我們必須對流進行分析,逐一的取得自己所需要的數據,并且由于網絡數據流是原始的數據流,還要根據程序所需要的數據類型對網絡數據流進行解碼處理。發送網絡數據時同樣需要對數據進行封裝。這個過程也增加了java程序的煩瑣性。例如上述結構,要用如下代碼獲取相應數據:
- int id = bytebuf.getInt(); // 獲得整數型值
- int limit = bytebuf.limit(); // 獲得字節緩沖區的限值
- bytebuf.limit(36); // 設置字節緩沖區的限值,為字符串后面的第一個字節位置
- CharBuffer charbuf = decoder.decode(bytebuf); // 解碼獲得字符串
- Bytebuf.limit(limit); // 恢復字節緩沖區原來的限值
- float fval = bytebuf.getfloat(); // 獲得浮點型值
- short val = bytebuf.getshort(); // 獲得短整型數值
4:結束語
從上面的介紹可以看出,java程序中對網絡數據流的處理涉及的問題較多。在編寫網絡程序時,必須注意這些問題,以使得程序正確的處理通信的內容。
參考資料