自定义简单的"投票"协议
程序支持两种请求。一种是查询( inquiry),即向服务器询问给定候选人当前获得的投票总数。服务器发回一个响应消息,包含了原来的候选人 ID和该候选人当前(查询请求收到时)获得的选票总数。另一种是投票( voting)请求,即向指定候选人投一票。服务器对这种请求也发回响应消息,包含了候选人 ID 和其获得的选票数(包括了刚投的一票)。
VoteMsg.java是javabean, 包含每条信息中的基本信息
public class VoteMsg {
private boolean isInquiry; // true为查询; false为投票
private boolean isResponse;// true为服务器响应
private int candidateID; // 0<=candidateID<=1000
private long voteCount; // 投票数
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;
}
}
Framer.java和LengthFramer.java是成帧器, 实现数据的发和接
public interface Framer {
void frameMsg(byte[] message, OutputStream out) throws IOException;
byte[] nextMsg() throws IOException;
}
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; // wrapper for data I/O
public LengthFramer(InputStream in) throws IOException {
this.in = new DataInputStream(in);
}
public void frameMsg(byte[] message, OutputStream out) throws IOException {
if (message.length > MAXMESSAGELENGTH) {
throw new IOException("message too long");
}
// write length prefix
out.write((message.length >> BYTESHIFT) & BYTEMASK);
out.write(message.length & BYTEMASK);
// write message
out.write(message);
out.flush();
}
public byte[] nextMsg() throws IOException {
int length;
try {
length = in.readUnsignedShort(); // read 2 bytes
} catch (EOFException e) { // no (or 1 byte) message
return null;
}
// 0 <= length <= 65535
byte[] msg = new byte[length];
in.readFully(msg); // if exception, it's a framing error.
return msg;
}
}
VoteMsgCoder.java和VoteMsgBinCoder是关键代码, 实现解码和编码
public interface VoteMsgCoder {
byte[] toWire(VoteMsg msg) throws IOException;
VoteMsg fromWire (byte[] input) throws IOException;
}
VoteMsgBinCoder.java是使用 二进制格式, 其大小固定。每条消息由一个特殊字节开始,该字节的最高六位为一个 "魔术"值 010101。这一点少量的冗余信息为接收者收到适当的投票消息提供了一定程度的保证。该字节的最低两位对两个布尔值进行了编码。消息的第二个字节总是 0,第三、第四个字节包含了 candidateID 值。只有响应消息的最后 8 个字节才包含了选票总数信息。
/* Wire Format
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | Magic |Flags| ZERO |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | Candidate ID |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | |
* | Vote Count (only in response) |
* | |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
public class VoteMsgBinCoder implements VoteMsgCoder{
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);
}
}
return new VoteMsg(resp, inq, candidateID, count);
}
}
使用TCP进行数据交互
VoteClientTCP.java是客户端
public class VoteClientTCP {
public static final int CANDIDATEID = 888;
public static void main(String args[]) throws Exception {
String destAddr = "127.0.0.1";
int destPort = 9090;
Socket sock = new Socket(destAddr, destPort);
OutputStream out = sock.getOutputStream();
//使用二进制编码和解码, 还可以使用text编码
VoteMsgCoder coder = new VoteMsgBinCoder();
//基于长度的成帧器, 实现数据发和接
Framer framer = new LengthFramer(sock.getInputStream());
//实例化
VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
//二进制编码
byte[] encodedMsg = coder.toWire(msg);
//发送查询
System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): ");
System.out.println(msg);
framer.frameMsg(encodedMsg, out);
//发送投票
msg.setInquiry(false);
encodedMsg = coder.toWire(msg);
System.out.println("Sending Vote (" + encodedMsg.length + " bytes): ");
framer.frameMsg(encodedMsg, out);
//接收查询
encodedMsg = framer.nextMsg();
msg = coder.fromWire(encodedMsg);
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
//接收投票
msg = coder.fromWire(framer.nextMsg());
System.out.println("Received Response (" + encodedMsg.length
+ " bytes): ");
System.out.println(msg);
sock.close();
}
}
VoteServerTCP.java是服务端
public class VoteServerTCP {
public static void main(String args[]) throws Exception {
int port = 9090;
ServerSocket servSock = new ServerSocket(port);
// Change Bin to Text on both client and server for different encoding
VoteMsgCoder coder = new VoteMsgBinCoder();
VoteService service = new VoteService();
while (true) {
Socket clntSock = servSock.accept();
System.out.println("Handling client at " + clntSock.getRemoteSocketAddress());
// Change Length to Delim for a different framing strategy
Framer framer = new LengthFramer(clntSock.getInputStream());
try {
byte[] req;
while ((req = framer.nextMsg()) != null) {
System.out.println("Received message (" + req.length + " bytes)");
VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
framer.frameMsg(coder.toWire(responseMsg), clntSock.getOutputStream());
}
} catch (IOException ioe) {
System.err.println("Error handling client: " + ioe.getMessage());
} finally {
System.out.println("Closing connection");
clntSock.close();
}
}
}
}
运行结果如下: