RPC的世界,由于涉及到進程間網絡遠程通信,不可避免的需要將信息序列化后在網絡間傳送,序列化有兩大流派: 文本和二進制.
文本序列化
序列化的實現有很多方式,在異構系統中最常用的就是定義成人類可讀的文本形式,其在開發時debug比較方便.
常見的有:
- 如通過http協議傳送并用soap協議(實際形式為xml)封裝的webservice方式.
- http傳送,封裝形式為json.
二進制序列化
二進制序列化會比較復雜,由于字節流只是一組101010,需要一個比較詳細的協議來定義被序列化后的二進制流的每個字節的含義是什么.一般使用其原因是其在網絡傳送效率較高,但犧牲了可讀性.
常見的有:
- Protocol Buffers
- Thrift
- Java序列化
重點介紹下java序列化. JDK1.1起,sun就有Java Object Serialization Specification定義java的序列化方式,根據文檔,可以根據字節流讀出序列化后的含義. 地址在這里.
借用一張java rmi的圖解釋一下其工作方式:
客戶端與服務端都需要有實現了rmi接口的實現類,該接口在實際遠程通信的時候作為一個樁類來處理網絡通信的各種細節.而其傳遞的信息,就是java對象序列化后的二進制流.
讀懂java序列化流
我們給一個簡單的例子,定義一個簡單實現了serializable接口的類:
class Pet implements Serializable{
private static final long serialVersionUID = 2L;
int paws;
}
再寫一段將Pet序列化到文件中的程序:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
Pet dto = new Pet();
File file = new File("/users/kunrong/p3.txt");
try {
ObjectOutputStream oout =
new ObjectOutputStream(new FileOutputStream(file));
oout.writeObject(dto);
} catch (IOException e) {
e.printStackTrace();
}
}
}
什么也沒干,直接將其內容輸出到p3.txt文件中.運行后,由于序列化后寫出的是二進制流,所以用能打開二進制文件的編輯器打開,在mac下我用的是HexMiner,免費.
這些是什么意思呢?先摘取部分協議文檔定義的常量內容:
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
下面對照圖中每個16進制
- 0xACED:根據協議文檔的約定,由于所有二進制流(文件)都需要在流的頭部約定魔數(magic number=STREAM_MAGIC),既java object 序列化后的流的魔數約定為ACED;
- 0x0005:然后是流協議版本是short類型(2字節)并且值為5,則十六進制值為0005;
- 0x7372:java byte型長度為1字節,所以0x73 0x72直接對應到字節流上,0x73(TC_OBJECT)代表這是一個對象,0x72(TC_CLASSDESC)代表這個之后開始是對類的描述.
- 0x0003:類名的長度,這個類名是Pet,是三個字符,所以長度是3,對應16進制中就是0x0003.
0x506574:這三個字節轉為ASCII碼就是類名Pet.
0x00 00 00 00 00 00 00 02: 由于序列化中標識類版本是這樣定義的
private static final long serialVersionUID = 2L;
是long型,long在java中的定義是8字節,所以這里2L對應的二進制值就是這個.0x02: 關于classDescFlags的定義在協議文檔是這樣的:
The flag byte classDescFlags may include values of
final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;
所以0x02代表了可序列化.
- 0x0001: 這一位代表了類中域的個數,在我們的Pet類里,只有一個域就是int paws,所以為1.
0x49: 這個二進制對應的ASCII值是I,這在規范里有定義,我們看下規范定義的是什么:
(
B' for byte,
C' for char,D' for double,
F' for float,I' for int, >
J' for long,L' for non-array object types,
S' for short,Z' for boolean, and
[` for arrays)
所以I的意思就是域的類型int型,跟我們在Pet類中的定義一樣.- ** 0x00 04 :** 既然前面已經指明了域的類型和個數,這里就是域名的長度,就是4個字節,代表這里之后的4個字節代表的是域名.
- ** 0x70617773:** 這里就是域名的ASCII字符,轉換成ASCII就是paws,我們在Pet類聲明的變量名.
** 0x78 70:** 看下協議中的這兩個值的含義:
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_NULL = (byte)0x70;
所以0x78代表該塊定義的序列化流結束了,0x70是NULL,沒有超類了.** 0x00000000**
至此,對于需要傳送的類的整個定義已經完成了,那么最后面四個字節的0x00000000是什么意思呢?很簡單,就是之前定義的我們的int型域變量paws里存儲的實際值了,由于我們聲明了但沒有給值,所以這里就是4個字節的00000000.
總結
看了以上對序列化后的二進制流的逐字翻譯,我們可以看到為什么java的序列化需要服務器和客戶端都需要同一個類的jar包.
因為序列化的流只定義了所傳輸類的一些定義和域的值,其寫入和讀取是分別由客戶端程序和服務端程序組裝完成的,如果雙方沒有一個共同的基礎(同一個類),是無法完成的.
而且由于在網絡上傳輸的兩端的進程可能跑的是不同jdk版本的虛擬機,這個協議還要保證序列化仍然能成功,這個協議文檔中明確寫明了版本設計在序列化中的目標:
The goals are to:
Support bidirectional communication between different versions of a class >operating in different virtual machines by:
Defining a mechanism that allows Java classes to read streams written by >older versions of the same class.
Defining a mechanism that allows Java classes to write streams intended to >be read by older versions of the same class.
Provide default serialization for persistence and for RMI.
Perform well and produce compact streams in simple cases, so that RMI can >use serialization.
Be able to identify and load classes that match the exact class used to >write the stream.
Keep the overhead low for nonversioned classes.
Use a stream format that allows the traversal of the stream without having >to invoke methods specific to the objects saved in the stream.
比較重要的就是:
- java類可以讀取其自身的老版本流信息.
- java類可以輸出一個其老版本的同樣的類能讀出的流.
文章來自微信平臺「麥芽面包」,微信號「darkjune_think」。轉載請注明。
文章列表
![]() |
不含病毒。www.avast.com |