java TCP/IP Socket编程-----发送和接受数据---投票例子--笔记7

来源:互联网 发布:淘宝满减活动怎么设置 编辑:程序博客网 时间:2024/06/12 01:35

概述:

通过简单投票的例子学习一下,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;}@Overridepublic 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 00000000public static final int MAGIC = 0x5400;//1111 1100 0000 0000public static final int MAGIC_MASK = 0xfc00;public static final int MAGIC_SHIFT = 8;//0000 0010  0000 0000public static final int RESPONSE_FLAG = 0x0200;//0000 0001 0000 0000public 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     0x5500magicAndFlags |= INQUIRE_FLAG;}if (msg.isResponse()) {//0101 0100 00000000//0000 0010  0000 0000//0101 0110 0000 0000magicAndFlags |=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 0000if ((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 = falseboolean 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 = trueboolean  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 10String 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 为候选者IDcandidateID = 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 到 65535byte [] 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 sourceprivate 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);//进行查询或响应处理+候选者IDencodeMsg = 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);//获取候选者IDint 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 {//判断参数是否为1if (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();}} }}
总结:划分功能,单独实现,不要混淆


原创粉丝点击