Java Socket 3

  投票协议实例:

客户端向服务器发送一个请求信息,包含一个候选人ID 0-1000.

程序支持两种请求,1、查询,2、投票。服务器返回响应信息。

1、消息实体VoteMsg

package vote; 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; } }

2、序列化和反序列化:Java对象 《=》字节数组

接口:

toWire()序列化;fromWire()反序列化

public interface VoteMsgCoder {

  byte[] toWire(VoteMsg msg) throws IOException;

  VoteMsg fromWire(byte[] input) throws IOException;

}

package vote; import java.io.IOException; public interface VoteMsgCoder { byte[] toWire(VoteMsg msg) throws IOException; VoteMsg fromWire(byte[] input) throws IOException; }

两种实现:

(1)    基于文本的编码方式

VoteMsgTextCoder implements VoteMsgCoder

package vote; 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); } }

(2)    二进制的编码方式

VoteMsgBinCoder implements VoteMsgCoder

package vote; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; /* 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 { // 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); } }

3、成帧与解析:

成帧与解析:解决接收端如何定位消息的首尾位置的问题。

样例代码(只是接口):

函数1:把消息成帧,并输出到指定流

函数2:扫描指定的流,从中抽取出下一条消息。

public interface Framer {

      void frameMsg(byte[] message, OutputStream out) throws IOException;

      byte[] nextMsg() throws IOException;

}

package vote; import java.io.IOException; import java.io.OutputStream; public interface Framer { void frameMsg(byte[] message, OutputStream out) throws IOException; byte[] nextMsg() throws IOException; }

1、            基于定界符DelimFramer implements Framer

package vote; 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; // data source private static final byte DELIMITER = '/n'; // message delimiter public DelimFramer(InputStream in) { this.in = in; } public void frameMsg(byte[] message, OutputStream out) throws IOException { // ensure that the message does not contain the delimiter 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; // fetch bytes until find delimiter while ((nextByte = in.read()) != DELIMITER) { if (nextByte == -1) { // end of stream? if (messageBuffer.size() == 0) { // if no byte read return null; } else { // if bytes followed by end of stream: framing error throw new EOFException( "Non-empty message without delimiter"); } } messageBuffer.write(nextByte); // write byte to buffer } return messageBuffer.toByteArray(); } }

 

2、            显示长度;LengthFramer implements Framer

 package vote; 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; // 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; } }

4、发送和接受:

通过流发送消息:创建消息,调用toWire()方法序列化,添加适当的成帧信息,再写入流。

接受消息按照相反顺序执行。

注:对于UDP协议,则不需要显示的成帧,因为UDP协议中保留了消息的边界信息。

 

服务器端所用到的服务:当接收到投票信息时,服务器调用VoteService类

package vote; import java.util.HashMap; import java.util.Map; public class VoteService { // Map of candidates to number of votes private Map<Integer, Long> results = new HashMap<Integer, Long>(); public VoteMsg handleRequest(VoteMsg msg) { if (msg.isResponse()) { // If response, just send it back return msg; } msg.setResponse(true); // Make message a response // Get candidate ID and vote count int candidate = msg.getCandidateID(); Long count = results.get(candidate); if (count == null) { count = 0L; // Candidate does not exist } if (!msg.isInquiry()) { results.put(candidate, ++count); // If vote, increment count } msg.setVoteCount(count); return msg; } }

5、基于TCP的客户端与服务器Socket

客户端:

package vote; import java.io.OutputStream; import java.net.Socket; public class VoteClientTCP { public static final int CANDIDATEID = 888; public static void main(String args[]) throws Exception { if (args.length != 2) { // Test for correct # of args throw new IllegalArgumentException("Parameter(s): <Server> <Port>"); } String destAddr = args[0]; // Destination address int destPort = Integer.parseInt(args[1]); // Destination port Socket sock = new Socket(destAddr, destPort); OutputStream out = sock.getOutputStream(); // Change Bin to Text for a different framing strategy VoteMsgCoder coder = new VoteMsgBinCoder(); // Change Length to Delim for a different encoding strategy Framer framer = new LengthFramer(sock.getInputStream()); // Create an inquiry request (2nd arg = true) VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0); byte[] encodedMsg = coder.toWire(msg); // Send request System.out.println("Sending Inquiry (" + encodedMsg.length + " bytes): "); System.out.println(msg); framer.frameMsg(encodedMsg, out); // Now send a vote msg.setInquiry(false); encodedMsg = coder.toWire(msg); System.out.println("Sending Vote (" + encodedMsg.length + " bytes): "); framer.frameMsg(encodedMsg, out); // Receive inquiry response encodedMsg = framer.nextMsg(); msg = coder.fromWire(encodedMsg); System.out.println("Received Response (" + encodedMsg.length + " bytes): "); System.out.println(msg); // Receive vote response msg = coder.fromWire(framer.nextMsg()); System.out.println("Received Response (" + encodedMsg.length + " bytes): "); System.out.println(msg); sock.close(); } }

服务器:

package vote; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class VoteServerTCP { public static void main(String args[]) throws Exception { if (args.length != 1) { // Test for correct # of args throw new IllegalArgumentException("Parameter(s): <Port>"); } int port = Integer.parseInt(args[0]); // Receiving Port 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(); } } } }

6、基于UDP的客户端与服务器Socket

客户端:

package vote; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Arrays; public class VoteClientUDP { public static void main(String args[]) throws IOException { if (args.length != 3) { // Test for correct # of args throw new IllegalArgumentException("Parameter(s): <Destination>" + " <Port> <Candidate#>"); } InetAddress destAddr = InetAddress.getByName(args[0]); // Destination // addr int destPort = Integer.parseInt(args[1]); // Destination port int candidate = Integer.parseInt(args[2]); // 0 <= candidate <= 1000 // req'd DatagramSocket sock = new DatagramSocket(); // UDP socket for sending sock.connect(destAddr, destPort); // Create a voting message (2nd param false = vote) VoteMsg vote = new VoteMsg(false, false, candidate, 0); // Change Text to Bin here for a different coding strategy VoteMsgCoder coder = new VoteMsgTextCoder(); // Send request byte[] encodedVote = coder.toWire(vote); System.out.println("Sending Text-Encoded Request (" + encodedVote.length + " bytes): "); System.out.println(vote); DatagramPacket message = new DatagramPacket(encodedVote, encodedVote.length); sock.send(message); // Receive response message = new DatagramPacket( new byte[VoteMsgTextCoder.MAX_WIRE_LENGTH], VoteMsgTextCoder.MAX_WIRE_LENGTH); sock.receive(message); encodedVote = Arrays.copyOfRange(message.getData(), 0, message.getLength()); System.out.println("Received Text-Encoded Response (" + encodedVote.length + " bytes): "); vote = coder.fromWire(encodedVote); System.out.println(vote); } }

服务器:

package vote; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.Arrays; public class VoteServerUDP { public static void main(String[] args) throws IOException { if (args.length != 1) { // Test for correct # of args throw new IllegalArgumentException("Parameter(s): <Port>"); } int port = Integer.parseInt(args[0]); // Receiving Port DatagramSocket sock = new DatagramSocket(port); // Receive socket byte[] inBuffer = new byte[VoteMsgTextCoder.MAX_WIRE_LENGTH]; // Change Bin to Text for a different coding approach VoteMsgCoder coder = new VoteMsgTextCoder(); VoteService service = new VoteService(); while (true) { DatagramPacket packet = new DatagramPacket(inBuffer, inBuffer.length); sock.receive(packet); byte[] encodedMsg = Arrays.copyOfRange(packet.getData(), 0, packet.getLength()); System.out.println("Handling request from " + packet.getSocketAddress() + " (" + encodedMsg.length + " bytes)"); try { VoteMsg msg = coder.fromWire(encodedMsg); msg = service.handleRequest(msg); packet.setData(coder.toWire(msg)); System.out.println("Sending response (" + packet.getLength() + " bytes):"); System.out.println(msg); sock.send(packet); } catch (IOException ioe) { System.err.println("Parse error in message: " + ioe.getMessage()); } } } }

你可能感兴趣的:(Java Socket 3)