概述:
通过简单投票的例子学习一下,TCP/IP通讯需要注意的那些点,或者这个例子会其他知识,只要是知识就要慢慢捡,捡着捡着就多了。
1.它的类的布局 如下:
总结:
1.在设计TCP/IP传输过程中必须定义好传输的协议(信息如何编排和信息的边界)
2.要用接口来实现解耦,方便后期扩展
2.开始上代码
package com.tcp.ip.chapter3.vote;
public class VoteMsg {
//true查询,false投票
private boolean isInquiry;
//true 从服务器响应的
private boolean isResponse;
//候选人编号[0,1000]
private int candidateID;
//非零
private long voteCount;
public static final int MAX_CANDIDATE_ID =1000;
public VoteMsg(boolean isResponse, boolean isInquiry, int candidateID, long voteCount) {
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 boolean isInquiry() {
return isInquiry;
}
public void setInquiry(boolean isInquiry) {
this.isInquiry = isInquiry;
}
public boolean isResponse() {
return isResponse;
}
public void setResponse(boolean isResponse) {
this.isResponse = isResponse;
}
public int getCandidateID() {
return candidateID;
}
public void setCandidateID(int candidateID) {
if(candidateID < 0 || candidateID > MAX_CANDIDATE_ID) {
throw new IllegalArgumentException("Bad Candidate ID:" + candidateID);
}
this.candidateID = candidateID;
}
public long getVoteCount() {
return voteCount;
}
public void setVoteCount(long voteCount) {
if((voteCount != 0 && !isResponse) || voteCount < 0) {
throw new IllegalArgumentException("Bad vote count");
}
this.voteCount = voteCount;
}
@Override
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;
}
}
总结:
1.对于一些属性隐含一些条件,我们需要提前对数据进行甄别,当前禁止在setter写逻辑代码,很有可能出现灵异事件。
2.简单逻辑判断,不要容忍低级错误,throw new IllegalArgumentException() 又学到一个异常
3.推荐对于每一个dto都重写toString()方法,方便查错
package com.tcp.ip.chapter3.vote;
import java.io.IOException;
public interface VoteMsgCoder {
/**
* 按协议编排数据
*
* @param msg
* @return
* @throws IOException
*/
byte[] toWire(VoteMsg msg) throws IOException;
/**
* 按协议解析数据
*
* @param input
* @return
* @throws IOException
*/
VoteMsg fromWire(byte[] input) throws IOException;
}
总结:没说的
package com.tcp.ip.chapter3.vote;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* 111111
* @author 往前的娘娘
*
*/
public class VoteMsgBinCoder implements VoteMsgCoder {
public static final int MIN_WIRE_LENGTH = 4;
public static final int MAX_WIRE_LENGTH = 16;
//0101 0100 00000000
public static final int MAGIC = 0x5400;
//1111 1100 0000 0000
public static final int MAGIC_MASK = 0xfc00;
public static final int MAGIC_SHIFT = 8;
//0000 0010 0000 0000
public static final int RESPONSE_FLAG = 0x0200;
//0000 0001 0000 0000
public static final int INQUIRE_FLAG = 0x0100;
public byte[] toWire(VoteMsg msg) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteStream);
short magicAndFlags =MAGIC;
if (msg.isInquiry()) {
//0101 0100 00000000
//0000 0001 0000 0000
//0101 0101 0000 0000 0x5500
magicAndFlags |= INQUIRE_FLAG;
}
if (msg.isResponse()) {
//0101 0100 00000000
//0000 0010 0000 0000
//0101 0110 0000 0000
magicAndFlags |=RESPONSE_FLAG;
}
out.writeShort(magicAndFlags);
System.out.println("====================================当前数据的字节树" +byteStream.toByteArray().length);
//我们度知道候选ID将会在0到1000之间
out.writeShort((short) msg.getCandidateID());
out.flush();
byte[] data = byteStream.toByteArray();
System.out.println("====================================当前数据的字节树" +data.length);
return data;
}
public VoteMsg fromWire(byte[] input) throws IOException {
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();
//如果是查询
//0101 0101 0000 0000
//1111 1100 0000 0000
//0101 0100 0000 0000
//如果是响应
//0101 0110 0000 0000
//1111 1100 0000 0000
//0101 0100 0000 0000
if ((magic & MAGIC_MASK) != MAGIC) {
throw new IOException("Bad Magic #: " + ((magic & MAGIC_MASK >> MAGIC_SHIFT)));
}
//响应标志
//0101 0110 0000 0000 magic
//0000 0010 0000 0000 RESPONSE_FLAG
//0000 0010 0000 0000
//resp == true
//查询标志
//0101 0101 0000 0000
//0000 0010 0000 0000 RESPONSE_FLAG
//0000 0000 0000 0000 resp = false
boolean resp = ((magic & RESPONSE_FLAG) != 0);
//响应标志
//0101 0110 0000 0000 magic
//0000 0001 0000 0000 INQUIRE_FLAG
//0000 0000 0000 0000
//inq == false
//查询标志
//0101 0101 0000 0000
//0000 0001 0000 0000 INQUIRE_FLAG
//0000 0001 0000 0000 inq = true
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);
}
}
总结:
1.位运算速度杠杠的,就是阅读不习惯。经过|和&会有神奇的效果。
2.ByteArrayOutputStream byteStream可以被包装到DataOutputStream out中
3.out写入数据, 被包装的byteStream会自动获取out写入的数据, byteStream.toByteArray()比较常用的方法
4.注意写完之后flush一下
5.读取和写入数据可以按照不同数据类型写入。例如readShort(写入short数据)
package com.tcp.ip.chapter3.vote;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class VoteMsgTextCoder implements VoteMsgCoder{
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 DELIMSTER =" ";
public static final int MAX_WIRE_LENGTH = 2000;
public byte [] toWire(VoteMsg msg) throws IOException {
//Voting v R 888 10
String msgString = MAGIC + DELIMSTER + (msg.isInquiry() ? INQSTR : VOTESTR)
+ DELIMSTER + (msg.isResponse() ? RESPONSESTR + DELIMSTER : "")
+Integer.toString(msg.getCandidateID()) + DELIMSTER
+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;
}
//当前的token 为候选者ID
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);
}
}
总结:
1.主要通过空格进行分隔,在流操作做一般采用字节数据传输
2.可以通过ByteArrayInputStream 将字节数组转换为流进行处理
3.InputStreamReader可以指定具体字符编码进行读取字符流,它又包装在Scanner内
4.Scanner 中next()是以空格为分隔符
package com.tcp.ip.chapter3;
import java.io.IOException;
import java.io.OutputStream;
public interface Framer {
/**
* 边界限定
*
* @param message
* @param out
* @throws IOException
*/
void frameMsg(byte[] message, OutputStream out) throws IOException;
/**
* 边界读取
* @return
* @throws IOException
*/
byte[] nextMsg() throws IOException;
}
总结:随处可以字节数组
package com.tcp.ip.chapter3;
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;
//包装DataInputStream in;
private DataInputStream in;
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");
}
//写长度前缀
out.write((message.length >> BYTESHIFT) & BYTEMASK);
out.write(message.length & BYTEMASK);
//写信息
out.write(message);
out.flush();
}
public byte[] nextMsg() throws IOException {
int length ;
try{
//读取两个字节 表示长度的前缀
length = in.readUnsignedShort();
}catch (EOFException e) { //表示只有一个字节或没有
return null;
}
//0 到 65535
byte [] msg = new byte[length];
//读取所有字符
in.readFully(msg);
return msg;
}
}
1.构造方法最好不要进行一些初始化操作,可以单独写init()方法中
2.读取无符号的short readUnsignedShort()
3.DataInputStream you readFully(读取指定大小的数组) 不然会一直阻塞
package com.tcp.ip.chapter3;
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';
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){
if (nextByte == -1) {
if(messageBuffer.size() == 0) {
return null;
} else {
throw new EOFException("Non-Empty message without delimiter");
}
}
messageBuffer.write(nextByte);
}
return messageBuffer.toByteArray();
}
}
package com.tcp.ip.chapter3.vote;
import java.io.OutputStream;
import java.net.Socket;
import com.tcp.ip.chapter3.Framer;
import com.tcp.ip.chapter3.LengthFramer;
public class VoteClientTCP {
public static final int CANDIDATEID = 888;
public static void main(String args[]) throws Exception{
if (args.length != 2) {
throw new IllegalArgumentException("Parameter(s) : <Server> <Port>");
}
String destAddr = args[0];
int destPort = Integer.parseInt(args[1]);
Socket sock = new Socket(destAddr, destPort);
OutputStream out = sock.getOutputStream();
VoteMsgCoder coder =new VoteMsgBinCoder();
//为不同编码策略改变不同的长度
Framer framer = new LengthFramer(sock.getInputStream());
//创建一个查询请求 (第二参数 arg =true)
VoteMsg msg = new VoteMsg(false, true, CANDIDATEID, 0);
//判断是查询,还是投票进行封装
byte[] encodeMsg = coder.toWire(msg);
//发送请求
System.out.println("发送请求 ( " + encodeMsg.length + " bytes : ");
System.out.println(msg);
//写出长度分割符,该字符串的长度
framer.frameMsg(encodeMsg, out);
//现在发送投票
msg.setInquiry(false);
//进行查询或响应处理+候选者ID
encodeMsg = coder.toWire(msg);
System.out.println("发送投票( " + encodeMsg.length + " bytes) : ");
framer.frameMsg(encodeMsg, out);
//接收查询响应的结果
encodeMsg = framer.nextMsg();
System.out.println("=================encodeMsg==" + encodeMsg);
msg = coder.fromWire(encodeMsg);
System.out.println("Received Response ( " + encodeMsg.length + " bytes ): ");
System.out.println(msg);
//接收投票响应的
msg = coder.fromWire(framer.nextMsg());
System.out.println("Received Response (" + encodeMsg.length
+ " bytes): ");
System.out.println(msg);
sock.close();
}
}
package com.tcp.ip.chapter3.vote;
import java.util.HashMap;
import java.util.Map;
public class VoteService {
//有map存储候选者和当前的票数
private Map<Integer, Long> results = new HashMap<Integer, Long>();
public VoteMsg handleRequest(VoteMsg msg) {
//如果是响应直接返回的结果
if (msg.isResponse()) {
return msg;
}
//设置消息
msg.setResponse(true);
//获取候选者ID
int candidate = msg.getCandidateID();
Long count = results.get(candidate);
if(count == null) {
count = 0L;
}
if (!msg.isInquiry()) {
//如果是投票
results.put(candidate, ++count);
}
msg.setVoteCount(count);
return msg;
}
}
package com.tcp.ip.chapter3.vote;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import com.tcp.ip.chapter3.Framer;
import com.tcp.ip.chapter3.LengthFramer;
public class VoteServerTCP {
public static void main(String args[] ) throws Exception {
//判断参数是否为1
if (args.length != 1) {
throw new IllegalArgumentException ("Parameter(s) : <Port>");
}
//获取监听端口
int port = Integer.parseInt(args[0]);
ServerSocket servSock = new ServerSocket(port);
//改变Bin到Text 在both 客户端和服务端 对于不同编码
VoteMsgCoder coder = new VoteMsgBinCoder();
VoteService service = new VoteService();
while (true ) {
Socket clntSock = servSock.accept();
System.out.println("处理客户端 请求" + clntSock.getRemoteSocketAddress());
//改变长度策略从一个不同的框架策略
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));
System.out.println("服务器端读取的数据为:" + responseMsg);
//写出数据
framer.frameMsg(coder.toWire(responseMsg), clntSock.getOutputStream());
}
}catch (IOException ioe) {
ioe.printStackTrace();
System.out.println("错误处理客户端: " + ioe.getMessage());
} finally {
System.out.println("关闭连接");
clntSock.close();
}
}
}
}
UDP实现
package com.tcp.ip.chapter3.vote.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Arrays;
import com.tcp.ip.chapter3.vote.VoteMsg;
import com.tcp.ip.chapter3.vote.VoteMsgCoder;
import com.tcp.ip.chapter3.vote.VoteMsgTextCoder;
public class VoteClientUDP {
public static void main(String args[]) throws IOException{
if(args.length != 3) {
throw new IllegalArgumentException("Parameter(s): <Destination> <Port> <Candidate>");
}
InetAddress destAddr = InetAddress.getByName(args[0]);
int destPort = Integer.parseInt(args[1]);
int candidate = Integer.parseInt(args[2]);
DatagramSocket sock = new DatagramSocket();
sock.connect(destAddr, destPort);
//创建一个发送的信息报(第二个参数为 false 是投票)
VoteMsg vote = new VoteMsg(false, false, candidate, 0);
VoteMsgCoder coder = new VoteMsgTextCoder();
//发送的消息
byte[] encodeVote = coder.toWire(vote);
System.out.println("发送文本编译的请求( " + encodeVote.length + " bytes) :");
System.out.println(vote);
DatagramPacket message = new DatagramPacket(encodeVote, encodeVote.length);
sock.send(message);
//接受请求
message = new DatagramPacket(new byte[VoteMsgTextCoder.MAX_WIRE_LENGTH], VoteMsgTextCoder.MAX_WIRE_LENGTH);
sock.receive(message);
encodeVote = Arrays.copyOfRange(message.getData(), 0,message.getLength());
System.out.println("接受的数据响应 (" + encodeVote.length + " bytes) :");
vote = coder.fromWire(encodeVote);
System.out.println(vote);
}
}
package com.tcp.ip.chapter3.vote.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
import com.tcp.ip.chapter3.vote.VoteMsg;
import com.tcp.ip.chapter3.vote.VoteMsgCoder;
import com.tcp.ip.chapter3.vote.VoteMsgTextCoder;
import com.tcp.ip.chapter3.vote.VoteService;
public class VoteServerUDP {
public static void main(String[] args) throws IOException{
if(args.length != 1) {
throw new IllegalArgumentException("Parameter(s) : <Port>");
}
int port = Integer.parseInt(args[0]);
DatagramSocket sock = new DatagramSocket(port);
byte[] inBuffer = new byte[VoteMsgTextCoder.MAX_WIRE_LENGTH];
VoteMsgCoder coder = new VoteMsgTextCoder();
VoteService service = new VoteService();
while (true) {
DatagramPacket packet = new DatagramPacket(inBuffer, inBuffer.length);
sock.receive(packet);
byte[] encodeMsg = Arrays.copyOfRange(packet.getData(), 0, packet.getLength());
System.out.println("处理请求" + packet.getSocketAddress() + " ("
+ encodeMsg.length + " bytes)");
try{
VoteMsg msg = coder.fromWire(encodeMsg);
msg = service.handleRequest(msg);
packet.setData(coder.toWire(msg));
System.out.println("发送响应的消息( " + packet.getLength() + "bytes):");
System.out.println(msg);
sock.send(packet);
} catch (IOException i) {
System.err.println("解析出错");
i.getStackTrace();
}
}
}
}
总结:划分功能,单独实现,不要混淆