網(wǎng)絡(luò)數(shù)據(jù)流的java處理
前言:java程序要處理很多的網(wǎng)絡(luò)數(shù)據(jù),網(wǎng)絡(luò)數(shù)據(jù)發(fā)送和接收以及數(shù)據(jù)流的處理是java程序要特別關(guān)注的方面,隨著java的發(fā)展,這些方法也越來越得到重視和加強。本文從幾個方面解釋了java正確處理網(wǎng)絡(luò)數(shù)據(jù)流的要素,這些也是java程序員必須了解的基本的知識。
首先,之所以說java流的龐大,是因為java中的流處理比其他語言的流處理在內(nèi)容上多的多。
java流在處理上分為字符流和字節(jié)流。字符流處理的單元為2個字節(jié)的Unicode字符,分別操作字符、字符數(shù)組或字符串,而字節(jié)流處理單元為1個字節(jié),操作字節(jié)和字節(jié)數(shù)組。
Java內(nèi)用Unicode編碼存儲字符,字符流處理類負責(zé)將外部的其他編碼的字符流和java內(nèi)Unicode字符流之間的轉(zhuǎn)換。而類InputStreamReader和OutputStreamWriter處理字符流和字節(jié)流的轉(zhuǎn)換。字符流(一次可以處理一個緩沖區(qū))一次操作比字節(jié)流(一次一個字節(jié))效率高。
對應(yīng)不同的流,需要不同的流構(gòu)建器或流過濾實現(xiàn)。java目前依然在逐漸增加其流處理方法,雖然java類庫的創(chuàng)作人員可以列舉出很多理由來說明這要做的優(yōu)點,但我還是覺得java開始變得向其他語言一樣復(fù)雜起來。
![]() |
![]() |
2:網(wǎng)絡(luò)數(shù)據(jù)流的收發(fā)
java對網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接收處理,也借用了一般流處理的方法。我們知道,在幾乎其他所有語言中,網(wǎng)絡(luò)數(shù)據(jù)的收發(fā)在利用類似send(或write)和recv(或read)的方法時并沒有明顯的流處理。但是java和這些語言的收發(fā)方法有較大區(qū)別,要借助流才可以完成:
.......
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流處理,在緩沖區(qū)管理、可伸縮網(wǎng)絡(luò)和文件IO、字符集支持、正規(guī)表達式匹配方面做了新的處理。其中緩沖區(qū)管理和通道(Channel)概念則是對網(wǎng)絡(luò)數(shù)據(jù)流的收發(fā)處理支持的強化。緩沖區(qū)管理中ByteBuffer類更好的支持了網(wǎng)絡(luò)數(shù)據(jù)流處理。在網(wǎng)絡(luò)連接中,通道代表了sockets的連接。基于這些新的IO處理,以上代碼可以改寫為:
......
ByteBuffer bytebuf = ByteBuffer.allocate(2048); // 創(chuàng)建一個指定大小的緩沖區(qū)
InetSocketAddress isa = new InetSocketAddress(hostname,port);
sc = SocketChannel.open(); // 建立一個socket通道
sc.connect( isa); // 建立一個socket連接
…
sc.write(bytebuf); // 發(fā)送數(shù)據(jù)
…
sc.read(bytebuf); // 接收數(shù)據(jù)
這樣的程序似乎要流暢的多。
|
![]() ![]() |
![]()
|
3:java對網(wǎng)絡(luò)數(shù)據(jù)流的處理
java程序?qū)W(wǎng)絡(luò)數(shù)據(jù)流的處理要關(guān)注四個基本方面:數(shù)據(jù)流的編碼,字節(jié)順序,數(shù)據(jù)格式對應(yīng)和取數(shù)。這是四個不同的問題,但是都影響到網(wǎng)絡(luò)數(shù)據(jù)的正確接收。
3.1 網(wǎng)絡(luò)數(shù)據(jù)流的解碼和編碼
網(wǎng)絡(luò)數(shù)據(jù)流的編碼和解碼主要針對流中出現(xiàn)的字符串。網(wǎng)絡(luò)數(shù)據(jù)流中的字符串均為原始的字節(jié)流形式。
要正確接收網(wǎng)絡(luò)數(shù)據(jù)流中的字符串,首先要知道該字符串的編碼方案。然后才可以調(diào)用解碼的方法獲得java能夠認識的Unicode編碼字符串??梢杂萌缦麓a處理網(wǎng)絡(luò)數(shù)據(jù)流中字符串的編碼和解碼:
// 獲得編碼對象,即網(wǎng)絡(luò)對等方的認識的字符串編碼。
Charset charset = Charset.forName("--?"); // --?為對等方的編碼名,java必須支持。
// 生成編碼器和解碼器對象。
CharsetDecoder decoder = charset.newDecoder();
CharsetEncoder encoder = charset.newEncoder();
.......
// 對從網(wǎng)絡(luò)數(shù)據(jù)流中獲得的字節(jié)流解碼取得java字符串
CharBuffer charbuf = decoder.decode(bytebuff);
.......
// 將java字符串編碼成指定編碼的字節(jié)流,以便網(wǎng)絡(luò)發(fā)送
Bytebuff bytebuff = encoder.encode(CharBuffer.wrap("Test String");
.......
|
3.2 網(wǎng)絡(luò)數(shù)據(jù)流的字節(jié)順序
目前的字節(jié)順序有兩類:BIG_ENGIAN和LITTLE_ENDIAN。各個平臺所支持的字節(jié)序不同,例如AIX、Tru64Unix、Windows等操作系統(tǒng)平臺采用LITTLE_ENDIAN字節(jié)序,Solaris等操作系統(tǒng)平臺采用BIG_ENGIAN。Java自身采用的是BIG_ENGIAN字節(jié)序,當java和運行在其他平臺上的其他語言編寫的通信程序通信時,則必須考慮到數(shù)據(jù)的字節(jié)序。
Jkd1.4新增加的包NIO中的類ByteOrder則帶來了一定的方便。針對從網(wǎng)絡(luò)數(shù)據(jù)流的字節(jié)序,我們只要增加一行就可以輕松的處理字節(jié)序了:
bytebuf.order(ByteOrder.LITTLE_ENDIAN); //按照LITTLE_ENDIAN字節(jié)序收發(fā)數(shù)據(jù)
sc.read(bytebuf); // 接收數(shù)據(jù)
上面的方法雖然簡化了我們的編程,但沒有真正處理好分布式應(yīng)用的網(wǎng)絡(luò)數(shù)據(jù)字節(jié)序問題。例如,java同時和在Tru64Unix、Solaris平臺上的應(yīng)用通信時,上述方法就不能解決問題。因為同一數(shù)據(jù)包,可能無法判斷其字節(jié)序是那一種。此時要求網(wǎng)絡(luò)數(shù)據(jù)包內(nèi)攜帶附加的字節(jié)序信息顯然是不現(xiàn)實的。這種情況下,java語言需要提供對XDR(外部數(shù)據(jù)表達)的支持,目前XDR已經(jīng)為事實上的網(wǎng)絡(luò)數(shù)據(jù)流的標準格式,分布式應(yīng)用的網(wǎng)絡(luò)數(shù)據(jù)流基本都遵循了這種格式,如果java語言提供了對XDR的支持,就可以解決通用性的問題。對于分布式應(yīng)用中的網(wǎng)絡(luò)數(shù)據(jù)流的處理就無需再根據(jù)其平臺判斷其字節(jié)序,只要按照XDR格式進行處理就可以了。
3.3 網(wǎng)絡(luò)數(shù)據(jù)流中數(shù)據(jù)格式的對應(yīng)
C/C++語言編寫的網(wǎng)絡(luò)程序中一般采用數(shù)據(jù)結(jié)構(gòu)的緩沖區(qū)發(fā)送數(shù)據(jù),在java端接收數(shù)據(jù)時,會出現(xiàn)一些因數(shù)據(jù)組織引起的問題:
如結(jié)構(gòu) typedef struct {
int id;
char name[32];
short val;
float fval;
} SendData
|
在32位操作系統(tǒng)中,它的大小并不是42,而是44!數(shù)據(jù)的組織如下圖所示:

當通過網(wǎng)絡(luò)發(fā)送到客戶端時,客戶端也接收到44個字節(jié),如果按照順序依次取相應(yīng)的值,則會發(fā)現(xiàn)最后取得的浮點值不正確。這是因為把短整型數(shù)據(jù)后沒有意義的兩位作為了浮點數(shù)中的其中兩位。如果想正確接收該數(shù)據(jù),則必須跳過短整型數(shù)據(jù)后沒有意義的兩位,再取浮點值。
而如果以上的結(jié)構(gòu)變?yōu)椋?/span>
typedef struct {
int id;
char name[32];
float fval;
short val;
}
|
則java端按照順序依次接收數(shù)據(jù)就不會發(fā)生問題。
所以,在編寫程序時,對數(shù)據(jù)的正確組織也是非常重要的。
3.4從網(wǎng)絡(luò)數(shù)據(jù)流中取得需要的數(shù)據(jù)
在C/C++的Socket編程時,采用數(shù)據(jù)結(jié)構(gòu)收發(fā)數(shù)據(jù)很方便,特別是接收數(shù)據(jù)時,可以由數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)類型自動獲得網(wǎng)絡(luò)數(shù)據(jù)流相應(yīng)的數(shù)據(jù)。但是在java中,目前我們必須對流進行分析,逐一的取得自己所需要的數(shù)據(jù),并且由于網(wǎng)絡(luò)數(shù)據(jù)流是原始的數(shù)據(jù)流,還要根據(jù)程序所需要的數(shù)據(jù)類型對網(wǎng)絡(luò)數(shù)據(jù)流進行解碼處理。發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時同樣需要對數(shù)據(jù)進行封裝。這個過程也增加了java程序的煩瑣性。例如上述結(jié)構(gòu),要用如下代碼獲取相應(yīng)數(shù)據(jù):
- int id = bytebuf.getInt(); // 獲得整數(shù)型值
- int limit = bytebuf.limit(); // 獲得字節(jié)緩沖區(qū)的限值
- bytebuf.limit(36); // 設(shè)置字節(jié)緩沖區(qū)的限值,為字符串后面的第一個字節(jié)位置
- CharBuffer charbuf = decoder.decode(bytebuf); // 解碼獲得字符串
- Bytebuf.limit(limit); // 恢復(fù)字節(jié)緩沖區(qū)原來的限值
- float fval = bytebuf.getfloat(); // 獲得浮點型值
- short val = bytebuf.getshort(); // 獲得短整型數(shù)值
![]() ![]() |
![]() |
從上面的介紹可以看出,java程序中對網(wǎng)絡(luò)數(shù)據(jù)流的處理涉及的問題較多。在編寫網(wǎng)絡(luò)程序時,必須注意這些問題,以使得程序正確的處理通信的內(nèi)容。
posted on 2008-12-20 11:21 々上善若水々 閱讀(751) 評論(0) 編輯 收藏 所屬分類: J2SE