在傳輸消息時,用Java內置的方法和工具確實很用,如:對象序列化,RMI遠程調用等。但有時候,針對要傳輸的特定類型的數據,實現自己的方法可能更簡單、容易或有效。下面給出一個實現了自定義構建和解析協議消息的Demo(書上例子)。
該例子是一個簡單的投票協議。這里,一個客戶端向服務器發送一個請求消息,消息中包含了一個候選人的ID,范圍在0~1000。程序支持兩種請求:一種是查詢請求,即向服務器詢問候選人當前獲得的投票總數,服務器發回一個響應消息,包含了原來的候選人ID和該候選人當前獲得的選票總數;另一種是投票請求,即向指定候選人投一票,服務器對這種請求也發回響應消息,包含了候選人ID和獲得的選票數(包含了剛剛投的一票)。
在實現一個協議時,一般會定義一個專門的類來存放消息中所包含的的信息。在我們的例子中,客戶端和服務端發送的消息都很簡單,唯一的區別是服務端發送的消息還包含了選票總數和一個表示相應消息的標志。因此,可以用一個類來表示客戶端和服務端的兩種消息。下面的VoteMsg.java類展示了每條消息中的基本信息:
- 布爾值isInquiry,true表示該消息是查詢請求,false表示該消息是投票請求;
- 布爾值isResponse,true表示該消息是服務器發送的相應消息,false表示該消息為客戶端發送的請求消息;
- 整型變量candidateID,指示了候選人的ID;
- 長整型變量voteCount,指示出所查詢的候選人獲得的總選票數。
另外,注意一下幾點:
- candidateID的范圍在0~1000;
- voteCount在請求消息中必須為0;
- voteCount不能為負數
VoteMsg代碼如下:
public class VoteMsg { private boolean isInquiry; // true if inquiry; false if vote private boolean isResponse;// true if response from server private int candidateID; // in [0,1000] private long voteCount; // nonzero only in response public static final int MAX_CANDIDATE_ID = 1000; public VoteMsg(boolean isResponse, boolean isInquiry, int candidateID, long voteCount) throws IllegalArgumentException { // check invariants if (voteCount != 0 && !isResponse) { throw new IllegalArgumentException("Request vote count must be zero"); } if (candidateID < 0 || candidateID > MAX_CANDIDATE_ID) { throw new IllegalArgumentException("Bad Candidate ID: " + candidateID); } if (voteCount < 0) { throw new IllegalArgumentException("Total must be >= zero"); } this.candidateID = candidateID; this.isResponse = isResponse; this.isInquiry = isInquiry; this.voteCount = voteCount; } public void setInquiry(boolean isInquiry) { this.isInquiry = isInquiry; } public void setResponse(boolean isResponse) { this.isResponse = isResponse; } public boolean isInquiry() { return isInquiry; } public boolean isResponse() { return isResponse; } public void setCandidateID(int candidateID) throws IllegalArgumentException { if (candidateID < 0 || candidateID > MAX_CANDIDATE_ID) { throw new IllegalArgumentException("Bad Candidate ID: " + candidateID); } this.candidateID = candidateID; } public int getCandidateID() { return candidateID; } public void setVoteCount(long count) { if ((count != 0 && !isResponse) || count < 0) { throw new IllegalArgumentException("Bad vote count"); } voteCount = count; } public long getVoteCount() { return voteCount; } public String toString() { String res = (isInquiry ? "inquiry" : "vote") + " for candidate " + candidateID; if (isResponse) { res = "response to " + res + " who now has " + voteCount + " vote(s)"; } return res; } }
接下來,我們要根據一定的協議來對其進行編解碼,我們定義一個VoteMsgCoder接口,它提供了對投票消息進行序列化和反序列化的方法。toWrie()方法用於根據一個特定的協議,將投票消息轉換成一個字節序列,fromWire()方法則根據相同的協議,對給定的字節序列進行解析,並根據信息的內容返回一個該消息類的實例。
import java.io.IOException; public interface VoteMsgCoder { byte[] toWire(VoteMsg msg) throws IOException; VoteMsg fromWire(byte[] input) throws IOException; }
下面給出兩個實現了VoteMsgCoder接口的類,一個實現的是基於文本的編碼方式 ,一個實現的是基於二進制的編碼方式。
首先是用文本方式對消息進行編碼的程序。該協議指定使用ASCII字符集對文本進行編碼。消息的開頭是一個所謂的”魔術字符串“,即一個字符序列,用於快速將投票協議的消息和網絡中隨機到來的垃圾消息區分開,投票/查詢布爾值被編碼為字符形似,‘v’代表投票消息,‘i’代表查詢消息。是否為服務器發送的響應消息,由字符‘R’指示,狀態標記后面是候選人ID,其后跟的是選票總數,它們都編碼成十進制字符串。
import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.Scanner; public class VoteMsgTextCoder implements VoteMsgCoder { /* * Wire Format "VOTEPROTO" <"v" | "i"> [<RESPFLAG>] <CANDIDATE> [<VOTECNT>] * Charset is fixed by the wire format. */ // Manifest constants for encoding public static final String MAGIC = "Voting"; public static final String VOTESTR = "v"; public static final String INQSTR = "i"; public static final String RESPONSESTR = "R"; public static final String CHARSETNAME = "US-ASCII"; public static final String DELIMSTR = " "; public static final int MAX_WIRE_LENGTH = 2000; public byte[] toWire(VoteMsg msg) throws IOException { String msgString = MAGIC + DELIMSTR + (msg.isInquiry() ? INQSTR : VOTESTR) + DELIMSTR + (msg.isResponse() ? RESPONSESTR + DELIMSTR : "") + Integer.toString(msg.getCandidateID()) + DELIMSTR + Long.toString(msg.getVoteCount()); byte data[] = msgString.getBytes(CHARSETNAME); return data; } public VoteMsg fromWire(byte[] message) throws IOException { ByteArrayInputStream msgStream = new ByteArrayInputStream(message); Scanner s = new Scanner(new InputStreamReader(msgStream, CHARSETNAME)); boolean isInquiry; boolean isResponse; int candidateID; long voteCount; String token; try { token = s.next(); if (!token.equals(MAGIC)) { throw new IOException("Bad magic string: " + token); } token = s.next(); if (token.equals(VOTESTR)) { isInquiry = false; } else if (!token.equals(INQSTR)) { throw new IOException("Bad vote/inq indicator: " + token); } else { isInquiry = true; } token = s.next(); if (token.equals(RESPONSESTR)) { isResponse = true; token = s.next(); } else { isResponse = false; } // Current token is candidateID // Note: isResponse now valid candidateID = Integer.parseInt(token); if (isResponse) { token = s.next(); voteCount = Long.parseLong(token); } else { voteCount = 0; } } catch (IOException ioe) { throw new IOException("Parse error..."); } return new VoteMsg(isResponse, isInquiry, candidateID, voteCount); } }
toWire()方法簡單地創建一個字符串,該字符串中包含了消息的所有字段,並由空白符隔開。fromWire()方法首先檢查”魔術字符串“,如果在消息最前面沒有魔術字符串,則拋出一個異常。在理說明了在實現協議時非常重要的一點:永遠不要對從網絡中來的任何輸入進行任何假設。你的程序必須時刻為任何可能的輸入做好准備,並能很好的對其進行處理。
下面將展示基於二進制格式對消息進行編碼的程序。與基於文本的格式相反,二進制格式使用固定大小的消息,每條消息由一個特殊字節開始,該字節的最高六位為一個”魔術值“010101,該字節的最低兩位對兩個布爾值進行了編碼,消息的第二個字節總是0,第三、四個字節包含了candidateID值,只有響應消息的最后8個字節才包含了選票總數信息。字節序列格式如下圖所示:
代碼如下:
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; public class VoteMsgBinCoder implements VoteMsgCoder { // manifest constants for encoding public static final int MIN_WIRE_LENGTH = 4; public static final int MAX_WIRE_LENGTH = 16; public static final int MAGIC = 0x5400; public static final int MAGIC_MASK = 0xfc00; public static final int MAGIC_SHIFT = 8; public static final int RESPONSE_FLAG = 0x0200; public static final int INQUIRE_FLAG = 0x0100; public byte[] toWire(VoteMsg msg) throws IOException { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(byteStream); // converts ints short magicAndFlags = MAGIC; if (msg.isInquiry()) { magicAndFlags |= INQUIRE_FLAG; } if (msg.isResponse()) { magicAndFlags |= RESPONSE_FLAG; } out.writeShort(magicAndFlags); // We know the candidate ID will fit in a short: it's > 0 && < 1000 out.writeShort((short) msg.getCandidateID()); if (msg.isResponse()) { out.writeLong(msg.getVoteCount()); } out.flush(); byte[] data = byteStream.toByteArray(); return data; } public VoteMsg fromWire(byte[] input) throws IOException { // sanity checks if (input.length < MIN_WIRE_LENGTH) { throw new IOException("Runt message"); } ByteArrayInputStream bs = new ByteArrayInputStream(input); DataInputStream in = new DataInputStream(bs); int magic = in.readShort(); if ((magic & MAGIC_MASK) != MAGIC) { throw new IOException("Bad Magic #: " + ((magic & MAGIC_MASK) >> MAGIC_SHIFT)); } boolean resp = ((magic & RESPONSE_FLAG) != 0); boolean inq = ((magic & INQUIRE_FLAG) != 0); int candidateID = in.readShort(); if (candidateID < 0 || candidateID > 1000) { throw new IOException("Bad candidate ID: " + candidateID); } long count = 0; if (resp) { count = in.readLong(); if (count < 0) { throw new IOException("Bad vote count: " + count); } } // Ignore any extra bytes return new VoteMsg(resp, inq, candidateID, count); } }
轉自:http://blog.csdn.net/ns_code/article/details/14229253