关于二进制信息的收发

上一篇中我们了解了“短连接”的工作模式,那在“短连接”模式下就涉及到了一种“二进制”信息的收发。今天就来详细学习一下有关二进制信息收发的内部原理。

在以前的CS网络工作模式下,我们学过一个Communication类,是将底层信息的收发工作分离处理,做到职责单一,功能简单的原则,收发的信息只涉及到了有关字符串信息,包括了commond(计算机的命令),action(有关用户的操作),以及parameter(相关参数)。现在我们所要说的“短连接”模式下,也可以继续将信息的传递工作单立出来,对于CS模式中提到的信息的前两项commond和action依旧采用字符串传递方式,但是将parameter根据二进制形式传输,这就是今天要说到的重点。

parameter本身有两大类:字符串和二进制。
不管是哪一类进行传输,实际上传输时都是要按照二进制的形式来完成。如果信息是按照字符串形式发送,一般情况下,字符串转换成二进制相对于二进制转成字符串是比较简单的,所以我们需要把字符串信息转换成二进制形式,如果信息本身就是二进制形式,则不需要转换。这样做的目的是,统一形式,简化传输内容的类型。

在上面提到的成员基础上,我们需要再增加两个成员——type和len,type是指要传输内容的类型,有两种情况STR(字符串)以及二进制(BIN),len代表的是二进制信息的字节数长度。要明确信息类型是一定的,那么增加len成员的原因是,和字符串信息的收发采用readUTF()和writeUTF()不同,二进制信息的收发是通过read()和write()来完成的,而read()必须要知道字节长度,这是传递二进制信息的必然条件。二进制信息在传输的时候遵循的是先听后发,边听边发,冲突停止,随机延时,继续发送的原则。

public class NetMessage {
	public static final int STR = 0;
	public static final int BIN = 1;
	
	ENetCommend command;
	String action;
	int type;//新增加的消息类型,有两类
	int len;//字节长度
	byte[] binPara;
	String strPara;
	
	public NetMessage() {
	}
	
	ENetCommend getCommand() {
		return command;
	}
	NetMessage setCommand(ENetCommend command) {
		this.command = command;
		return this;
	}
	String getAction() {
		return action;
	}
	NetMessage setAction(String action) {
		this.action = action;
		return this;
	}
	int getType() {
		return type;
	}
	NetMessage setType(int type) {
		this.type = type;
		return this;
	}
	byte[] getBinPara() {
		return binPara;
	}
	NetMessage setBinPara(byte[] binPara) {
	//设置二进制信息之后,如果要发送的形式是STR类型,
	//可以把二进制信息转换成字符串形式
	//并且可以得到二进制信息的字节长度
		this.binPara = binPara;
		if(type == STR) {
			this.strPara = new String(binPara);
		} else {
			this.len = binPara.length;
		}
		return this;
	}
	String getStrPara() {
		return strPara;
	}
	NetMessage setStrPara(String strPara) {
	//设置字符串信息之后,可以通过二进制信息转换成字符串形式
	//并且可以得到二进制信息的字节长度
		this.strPara = strPara;
		this.binPara = strPara.getBytes();
		this.len = binPara.length;
		return this;
	}
	
	int getLen() {//字节长度可以被外部获得
		return len;
	}

	@Override
	public String toString() {
	//toString()方法主要是为了输出信息的头部
		StringBuffer res = new StringBuffer();
		if(type == STR) {
			if(strPara == null) {
				strPara = "";
				binPara = strPara.getBytes();
			}
		}
		if(binPara == null) {
			binPara = new byte[0];
		}
		len = binPara.length;
		
		res.append(command.name()).append(".")
		.append(action == null ? "" : action)
		.append(".").append(type).append(".").append(len);
		
		return res.toString();
	}
	
	//下面给出了netMessage信息的反向解析
	//根据点号的分割将String类型的信息还原成NetMessage类型	
	NetMessage(String message) {
		int index = message.indexOf(".");
		String strcommand = message.substring(0, index);
		command = ENetCommend.valueOf(strcommand);
		
		message = message.substring(index+1);
		index = message.indexOf(".");
		String strAction = message.substring(0, index);
		action = strAction.trim().length() <= 0 ? "" : strAction;
		
		message = message.substring(index+1);
		index = message.indexOf(".");
		String strType = message.substring(0, index);
		type = Integer.valueOf(strType);
		
		String strLen = message.substring(index + 1);
		len = Integer.valueOf(strLen);
	}
}

从代码角度来看,网络传输的三要素是Socket,DataInputStream,DataOuputStream,为了方便起见,可以把这三部分内容写到一个专门的封装起来,以后使用的时候可以直接通过这个类的对象来得到,如下所示:

public class NetConnection {
	Socket socket;
	DataInputStream dis;
	DataOutputStream dos;
	
	NetConnection() {
	}
	NetConnection(Socket socket, DataInputStream dis, DataOutputStream dos) {
		super();
		this.socket = socket;
		this.dis = dis;
		this.dos = dos;
	}

	Socket getSocket() {
		return socket;
	}
	void setSocket(Socket socket) {
		this.socket = socket;
	}
	DataInputStream getDis() {
		return dis;
	}
	void setDis(DataInputStream dis) {
		this.dis = dis;
	}
	DataOutputStream getDos() {
		return dos;
	}
	void setDos(DataOutputStream dos) {
		this.dos = dos;
	}
}

TCP数据报报文的内容是包括报文头部和报文实体两部分,实际上报文传输是一次性传递头部和实体(二进制信息),再由交换机内部进行分割,但是现在我们为了方便理解,假设把报文头部先进性发送,头部包含了四部分内容commond,action,tyoe和len,然后再发送二进制信息。自然而然,接受的时候也是先接收信息头部,再继续接收二进制信息,接收二进制信息同样需要知道自己长度。

从代码角度解释:
信息的收发必然存在基本的两部分(收和发),先说发送消息
消息的发送分为了两部分,头部信息和实体信息(二进制信息),消息的发送有可能会出现异常,一旦发送消息失败,则显示对端异常

void send(NetMessage netMessage) {
		if(connection == null) {
			return;
		}
		DataOutputStream dos = connection.getDos();
		try {
			dos.writeUTF(netMessage.toString());
			sendBinPara(dos, netMessage.getBinPara(), netMessage.getLen());
		} catch (IOException e) {
			// TODO 对端异常掉线
		}
	}

下面是有关二进制信息发送的代码:

public static final int BUFFER_SIZE = 1 << 15;
private void sendBinPara(DataOutputStream dos,byte[] para,int length) 
	throws IOException {
		int restLen = length;
		int len = 0;
		int off = 0;
		
		while(restLen > 0) {
			len = restLen > BUFFER_SIZE ? BUFFER_SIZE : restLen;
			dos.write(para, off, len);
			off += len;
			restLen -= len;
		}
	}

再说接收消息:类似于消息的发送,也是分为两部分,先接收头部信息,再接受二进制信息;

NetMessage receive() {
		if(connection == null) {
			return null;
		}
		DataInputStream dis = connection.getDis();
		NetMessage netMessage = null;
		
		try {
			String message = dis.readUTF();
			netMessage = new NetMessage(message);
			byte[] binPara = receiveBinPara(dis, netMessage.getLen());
			netMessage.setBinPara(binPara);
		} catch (IOException e) {
			// TODO 对端异常掉线
		}
		return netMessage;
	}

下面是有关二进制信息接受的代码,由于和发送的代码类似就不再详细解释了;

public static final int BUFFER_SIZE = 1 << 15;
private byte[] receiveBinPara(DataInputStream dis,int length) throws IOException {
		byte[] para = new byte[length];
		
		int readLen = 0;
		int restLen = length;
		int len = 0;
		int off = 0;
		
		while(restLen > 0 ) {
			len = restLen > BUFFER_SIZE ? BUFFER_SIZE : readLen;
			readLen = dis.read(para, off, len);
			off += readLen;
			restLen -= readLen;
		}
		return para;
	}

再来说这个具体的二进制信息的接收过程,为了方便描述,我们用图示方式来进行;
在这里插入图片描述注:上面代码中提到的“BUFFER_SIZE“”需要简单说明一下;
TCP数据报的报文头部有一个表示长度的量是2B,即16bit,那么一个TCP数据报报文的最大长度是2^16B,即64KB,但是还要除去报文头部的长度,所以报文实际的长度是小于64KB的,对于那些大于64KB的大报文会被交换机自动切割成小于64KB的小报文片段,这是由TCP协议自动完成的。在网络数据传输时,最好以32KB作为每一次传输的数据量(数值上是32*1024,又考虑到计算机对于乘法的运算速度很慢,所以我们最好是写成位运算的形式,也就是1<<15)。

发布了6 篇原创文章 · 获赞 8 · 访问量 175

猜你喜欢

转载自blog.csdn.net/Ctrl_viviya/article/details/105291065