3.1信息和編碼
通過套接字進(jìn)行發(fā)送和接收時(shí),只能處理字節(jié)和字節(jié)數(shù)組。作為一種強(qiáng)類型語言,Java需要把其他數(shù)據(jù)類型(int,String等)顯式轉(zhuǎn)換成字節(jié)數(shù)組。比如String類的getBytes()方法,是將一個(gè)Sring實(shí)例中的字符轉(zhuǎn)換成字節(jié)的標(biāo)準(zhǔn)方式。
注:語言有無類型,弱類型和強(qiáng)類型三種。其中,無類型不檢查,甚至不區(qū)分指令和數(shù)據(jù);弱類型的檢查很弱,僅能嚴(yán)格的區(qū)分指令和數(shù)據(jù);強(qiáng)類型的則嚴(yán)格的在編譯期進(jìn)行檢查。強(qiáng)類型語言在沒有強(qiáng)制類型轉(zhuǎn)化前,不允許兩種不同類型的變量相互操作。 如:double類型變量a,不經(jīng)過強(qiáng)制類型轉(zhuǎn)換那么程序int b = a是無法通過編譯。常用的強(qiáng)類型語言有Java、C# 、Apex和Python等。
3.1.1基本類型
計(jì)算機(jī)組成原理原碼補(bǔ)碼知識(shí)
3.1.2字符串和文本
我們可以將數(shù)字和boolean類型的數(shù)據(jù)表示成String類型,如“123478962”,“6.02e23”,“true”,“false”等。也可以通過調(diào)用getBytes()方法,將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組。
3.2組合輸入輸出流
Java中與流相關(guān)的類可以組合起來從而提供強(qiáng)大的功能。例如,我們可以將一個(gè)Socket實(shí)例的OutputStream包裝在一個(gè)BufferedOutputStream實(shí)例中,這樣可以先將字節(jié)暫時(shí)緩存在一起,然后再一次全部發(fā)送到底層的通信信道中,以提高程序的性能。我們還能再將這個(gè)BufferedOutputStream實(shí)例包裹在一個(gè)DataOutputStream實(shí)例中,以實(shí)現(xiàn)發(fā)送基本數(shù)據(jù)類型的功能。

在這個(gè)例子中,我們先將基本數(shù)據(jù)的值,一個(gè)一個(gè)寫入Data OutputStream中,DataOutputStream再將這些數(shù)據(jù)以二進(jìn)制的形式寫入BufferedOutput-Stream并將三次寫入的數(shù)據(jù)緩存起來,然后再由BufferedOutputStream一次性地將這些數(shù)據(jù)寫入套接字的OutputStream,最后由OutputStream將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)。在另一個(gè)終端,我們創(chuàng)建了相應(yīng)的組合InputStream,以有效地接收基本數(shù)據(jù)類型。

成幀與解析
這部分主要講的是流傳輸中對(duì)數(shù)據(jù)開始和結(jié)束邊界的處理,這也是為什么我們使用read()方法讀取-1,進(jìn)行判定讀到流結(jié)束的原因。(SOGa!)
成幀(framing)技術(shù)則解決了接收端如何定位消息的首尾位置的問題。無論信息是編碼成了文本、多字節(jié)二進(jìn)制數(shù)、或是兩者的結(jié)合,應(yīng)用程序協(xié)議必須指定消息的接收者如何確定何時(shí)消息已完整接收。
由于UDP套接字保留了消息的邊界信息,因此不需要進(jìn)行成幀處理(實(shí)際上,主要是DatagramPacket負(fù)載的數(shù)據(jù)有一個(gè)確定的長(zhǎng)度,接收者能夠準(zhǔn)確地知道消息的結(jié)束位置),而TCP協(xié)議中沒有消息邊界的概念,因此,在使用TCP套接字時(shí),成幀就是一個(gè)非常重要的考慮因素(在TCP連接中,接收者讀取完最后一條消息的最后一個(gè)字節(jié)后,將受到一個(gè)流結(jié)束標(biāo)記,即read()返回-1,該標(biāo)記指示出已經(jīng)讀取到了消息的末尾,非嚴(yán)格意義上來講,這也算是基于定界符方法的一種特殊情況)。
主要有兩種技術(shù)使接收者能夠準(zhǔn)確地找到消息的結(jié)束位置:
- 1、基于定界符:消息的結(jié)束由一個(gè)唯一的標(biāo)記指出,即發(fā)送者在傳輸完數(shù)據(jù)后顯式添加的一個(gè)特定字節(jié)序列,這個(gè)特殊標(biāo)記不能在傳輸?shù)臄?shù)據(jù)中出現(xiàn)(這也不是絕對(duì)的,應(yīng)用填充技術(shù)能夠?qū)ο⒅谐霈F(xiàn)的定界符進(jìn)行修改,從而使接收者不將其識(shí)別為定界符)。該方法通常用在以文本方式編碼的消息中。
- 2、顯式長(zhǎng)度:在變長(zhǎng)字段或消息前附加一個(gè)固定大小的字段,用來指示該字段或消息中包含了多少字節(jié)。該方法主要用在以二進(jìn)制字節(jié)方式編碼的消息中。
-
基于定界符的方法
通常用在以文本方式編碼的消息中:定義一個(gè)特殊的字符或字符串來標(biāo)識(shí)消息的結(jié)束。接收者只需要簡(jiǎn)單地掃描輸入信息(以字節(jié)的方式)來查找定界序列,并將定界符前面的字符串返回。這種方法的缺點(diǎn)是消息本身不能包含有定界字符,否則接收者將提前認(rèn)為消息已經(jīng)結(jié)束。在基于定界符的成幀方法中,發(fā)送者要保證滿足這個(gè)先決條件。幸運(yùn)的是,填充(stuffing)技術(shù)能夠?qū)ο⒅谐霈F(xiàn)的定界符進(jìn)行修改,從而使接收者不將其識(shí)別為定界符。在接收者掃描定界符時(shí),還能識(shí)別出修改過的數(shù)據(jù),并在輸出消息中對(duì)其進(jìn)行還原,從而使其與原始消息一致。這個(gè)技術(shù)的缺點(diǎn)是發(fā)送者和接收者雙方都必須掃描消息。 -
基于長(zhǎng)度的方法
更簡(jiǎn)單一些,不過要使用這種方法必須知道消息長(zhǎng)度的上限。發(fā)送者先要確定消息的長(zhǎng)度,將長(zhǎng)度信息存入一個(gè)整數(shù),作為消息的前綴。消息的長(zhǎng)度上限定義了用來編碼消息長(zhǎng)度所需要的字節(jié)數(shù):如果消息的長(zhǎng)度小于256字節(jié),則需要1個(gè)字節(jié);如果消息的長(zhǎng)度小于65 536字節(jié),則需要2個(gè)字節(jié)等。
代碼實(shí)現(xiàn):
定義的Framer接口。它有兩個(gè)方法:frameMsg()方法用來添加成幀信息并將指定消息輸出到指定流,nextMsg()方法則掃描指定的流,從中抽取出下一條消息。
Frame.java
import java.io.IOException;
import java.io.OutputStream;
public interface Framer {
void frameMsg(byte[] message, OutputStream out) throws IOException;
byte[] nextMsg() throws IOException;
}
DelimFramer.java類實(shí)現(xiàn)了基于定界符的成幀方法,其定界符為“換行”符(“\n”,字節(jié)值為10)。frameMethod()方法并沒有實(shí)現(xiàn)填充,當(dāng)成幀的字節(jié)序列中包含有定界符時(shí),它只是簡(jiǎn)單地拋出異常。nextMsg()方法掃描流,直到讀取到了定界符,并返回定界符前面的所有字符,如果流為空則返回null。如果累積了一個(gè)消息的不少字符,但直到流結(jié)束也沒有找到定界符,程序?qū)伋鲆粋€(gè)異常來指示成幀錯(cuò)誤。
DelimFramer.java
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class DelimFramer implements Framer {
private InputStream in; // 數(shù)據(jù)來源
private static final byte DELIMITER = '\n'; // 定界符
public DelimFramer(InputStream in) {
this.in = in;
}
public void frameMsg(byte[] message, OutputStream out) throws IOException {
for (byte b : message) {
if (b == DELIMITER) {
//如果在消息中檢查到界定符,則拋出異常
throw new IOException("Message contains delimiter");
}
}
out.write(message);
out.write(DELIMITER);
out.flush();
}
public byte[] nextMsg() throws IOException {
ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream();
int nextByte;
while ((nextByte = in.read()) != DELIMITER) {
//如果流已經(jīng)結(jié)束還沒有讀取到定界符
if (nextByte == -1) {
//如果讀取到的流為空,則返回null
if (messageBuffer.size() == 0) {
return null;
} else {
//如果讀取到的流不為空,則拋出異常
throw new EOFException("Non-empty message without delimiter");
}
}
messageBuffer.write(nextByte);
}
return messageBuffer.toByteArray();
}
}
LengthFramer.java類實(shí)現(xiàn)了基于長(zhǎng)度的成幀方法,適用于長(zhǎng)度小于65 535(216-1)字節(jié)的消息。發(fā)送者首先給出指定消息的長(zhǎng)度,并將長(zhǎng)度信息以big-endian順序存入兩個(gè)字節(jié)的整數(shù)中,再將這兩個(gè)字節(jié)放在完整的消息內(nèi)容前,連同消息一起寫入輸出流。在接收端,我們使用DataInputStream以讀取整型的長(zhǎng)度信息;readFully()方法將阻塞等待,直到給定的數(shù)組完全填滿,這正是我們需要的。值得注意的是,使用這種成幀方法,發(fā)送者不需要檢查要成幀的消息內(nèi)容,而只需要檢查消息的長(zhǎng)度是否超出了限制。
LengthFramer.java
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class LengthFramer implements Framer {
public static final int MAXMESSAGELENGTH = 65535;
public static final int BYTEMASK = 0xff;
public static final int SHORTMASK = 0xffff;
public static final int BYTESHIFT = 8;
private DataInputStream in;
public LengthFramer(InputStream in) throws IOException {
this.in = new DataInputStream(in); //數(shù)據(jù)來源
}
//對(duì)字節(jié)流message添加成幀信息,并輸出到指定流
public void frameMsg(byte[] message, OutputStream out) throws IOException {
//消息的長(zhǎng)度不能超過65535
if (message.length > MAXMESSAGELENGTH) {
throw new IOException("message too long");
}
out.write((message.length >> BYTESHIFT) & BYTEMASK);
out.write(message.length & BYTEMASK);
out.write(message);
out.flush();
}
public byte[] nextMsg() throws IOException {
int length;
try {
//該方法讀取2個(gè)字節(jié),將它們作為big-endian整數(shù)進(jìn)行解釋,并以int型整數(shù)返回它們的值
length = in.readUnsignedShort();
} catch (EOFException e) { // no (or 1 byte) message
return null;
}
// 0 <= length <= 65535
byte[] msg = new byte[length];
//該方法處阻塞等待,直到接收到足夠的字節(jié)來填滿指定的數(shù)組
in.readFully(msg); //
return msg;
}
}