您的位置:首页 > 理论基础 > 计算机网络

《Java TCP/IP Socket编程》读书笔记(7)

2013-01-28 22:46 176 查看

3.3 成帧与解析

成帧(framing)技术解决了接收端如何定位消息的首尾位置的问题。

主要有两种技术能够查找到消息的结束位置。

·基于界定符(Delimiter-based):消息的结束由唯一的标记指出,即发送者在传输完数据 后显示添加一个特殊的字节序列,这个标记不在传输的数据中出现。

·显示长度:在变长字段或者消息前加一个固定大小的字段,用来指示该字段或者消息 中包含多少个字节。

基于界定符的一个特殊情况是,可以用在TCP连接上传输的最后一个消息上:在发送完这个消息后,发送者就简单的关闭(shutdownOutput()或者close()方法)发送端的TCP连接,接收者在读取完这个消息的最后一个字节后将接收到一个流结束标记(即read方法返回的-1),该标记指示已经到达消息的末尾。

基于界定符的方法通常用在文本方式编码的消息中:定义一个特殊的字符或者字符串来标识消息的结束。接收者只需要简单的扫描输入的信息,来查找定界序列,并将定界符前边的字符串返回。这个方法的缺点是消息的本身不能含有定界符。

基于长度的方法要知道消息长度的上限,发送者要首先确定消息的长度,将长度信息存入一个整数,作为消息的前缀。如果消息的长度不大于255个字节,则需要一个字节,如果消息的长度小于65535个字节,则需要两个字节。

下面是一个Framer的接口。它有两个方法,frameMsg()方法用来添加成帧信息并将制定消息输出到指定的流,nextMsg()则将扫描指定的流,从中抽取一条消息。

package com.suifeng.tcpip.chapter3.framer;

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


下面是使用定界符的对Framer接口的实现

package com.suifeng.tcpip.chapter3.framer;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 使用边界符处理和解析数据
 * @author Suifeng
 *
 */
public class DelimFramer implements Framer
{
	private InputStream in;
	private static final int DELIMTER = '\n';
	
	public DelimFramer(InputStream in)
	{
		this.in = in;
	}
	
	@Override
	public void frameMsg(byte[] message, OutputStream out) throws IOException
	{
		for(byte b : message)
		{
			// 检查消息是否包含界定符,如果包含,则要抛出一个异常
			if(DELIMTER == b)
			{
				throw new IOException("Message contains delimiter");
			}
		}
		
		// 写消息
		out.write(message);
		
		// 将成帧信息输出到流中
		out.write(DELIMTER);
		
		out.flush();
	}

	@Override
	public byte[] nextMsg() throws IOException
	{
		ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream();
		int nextByte;
		
		// 读取流中的字节,知道遇到定界符
		while((nextByte = in.read()) != DELIMTER)
		{
			// 到达流末尾
			if(nextByte == -1)
			{
				if(messageBuffer.size() == 0)
				{
					// 消息已经接收完
					return null;
				}
				else
				{
					// 消息没有定界符,抛出异常
					throw new EOFException("Non-empty message without delimiter");
				}
			}
			
			// 将无定界符的消息写入消息缓冲区
			messageBuffer.write(nextByte);
		}
		
		// 转换成字节数组返回
		return messageBuffer.toByteArray();
	}

}


下面是基于长度的成帧方法,适用于小于65535自己的消息。
发送者先给出消息的长度,并将长度信息以big-endian顺序写入两个字节的整数中,再将这两个字节放到完整的消息的内容前,连同消息一起写入输出流。
下面是具体的代码实现
package com.suifeng.tcpip.chapter3.framer;

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 MAX_MESSAGE_LENGTH = 65535;
	public static final int BYTE_MASK = 0xff;
	public static final int SHORT_MASK = 0xff;
	public static final int BYTE_SHIFT = 8;
	
	private DataInputStream in;
	
	public LengthFramer(InputStream in)
	{
		this.in = new DataInputStream(in);
	}
	@Override
	public void frameMsg(byte[] message, OutputStream out) throws IOException
	{
		if(message.length > MAX_MESSAGE_LENGTH)
		{
			throw new IOException("Message too long");
		}
		
		// 高8位
		out.write((message.length >> BYTE_SHIFT)&BYTE_MASK);
		// 低8位
		out.write(message.length & BYTE_MASK);
		out.write(message);
		
		out.flush();
	}

	@Override
	public byte[] nextMsg() throws IOException
	{
		int length = 0;
		try
		{	
			length = in.readUnsignedShort();	// 两个字节
		}
		catch(EOFException e)
		{
			return null;
		}
		
		byte[] msg = new byte[length];
		
		in.readFully(msg);	// 阻塞等待,直到收到足够的字节
		
		return msg;
	}

}


3.4 Java特定编码

当使用套接字的时候,如果知道(i)通信双方都是用Java实现,(ii)拥有对协议的完全控制权,那么可以使用Java的内置工具如Serializable或者远程方法调用工具(Remote Method Invocation,RMI)。RMI可以调用不同虚拟机的方法,并隐藏所有繁琐的编码解码细节。序列化处理了实际的java对象转换成字节序列的工作,因此可以在不同的虚拟机传递Java对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: