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

概述:

通过简单投票的例子学习一下,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();
			}
		}
 	}
}
总结:划分功能,单独实现,不要混淆


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值