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