为什么使用它:
TCP协议保证所有的包有正确的顺序,但是不保证发送端的一个写操作只导致接收端的一个读事件发生,用 MINA术语来描述 就是:没有ProtocolCodecFilter,发送端一个IoSession.write(...)能够导致接收端多个 messageReceived(...),多个write(...)也能被引导到一个messageReceived(...),也许在单机测试时我们 不会碰到这样的情况,但是我们的应用应该有能力处理这种问题.
大多数网络应用需要一种方式来找到当前信息的结束点和下一条信息的开始点.
我们能够在IoHandler中实现所有业务逻辑,但是添加ProtocolCodecFilter将使你的代码更加容易,清晰的维护.
它能帮助我们分离业务逻辑和协议逻辑.
怎么使用:
应用基本上仅仅接收字节流而且我们需要将它们转化成高层对象(message).
这里有三种通用技术来分割字节流到message:
1.使用固定长度的信息.
2.使用固定长度的消息头来指定的消息体的长度.
3.使用定界符(如:许多基于文本的协议会在每条消息末尾加上换行符).
例子:
本例中,我们将开发一个无用的图形字符服务来阐明如何实现自己的协议编解码器
Request:
//一个简单的POJO代表一个请求
public class ImageRequest {
private int width;
private int height;
private int numberOfCharacters;
public ImageRequest(int width, int height, int numberOfCharacters) {
this.width = width;
this.height = height;
this.numberOfCharacters = numberOfCharacters;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getNumberOfCharacters() {
return numberOfCharacters;
}
}
//将ImageRequest对象编码成特定协议数据(客户端使用)
public class ImageRequestEncoder implements ProtocolEncoder {
public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
ImageRequest request = (ImageRequest) message;
IoBuffer buffer = IoBuffer.allocate(12, false);
buffer.putInt(request.getWidth());
buffer.putInt(request.getHeight());
buffer.putInt(request.getNumberOfCharacters());
buffer.flip();
out.write(buffer);
}
public void dispose(IoSession session) throws Exception {
// 释放相关资源,如果没有,直接继承自ProtocolEncoderAdapter
}
/**
* MINA会为IoSession写入队列中的所有消息调用encode方法.因为我们的客户端只写入ImageRequest,我们可以安全强转.
* 我们从堆中分配一个新的IoBuffer,我们最好避免使用直接缓冲区,因为通常来讲,堆缓冲性能更好.
* 你不需要手动释放缓冲区,MINA会帮你做.
* 在dispose()方法中释放所有编码期间使用到的资源,如果没有需要处理的,可以直接让他继承ProtocolEncoderAdapter.
*/
}
//将特定协议数据解码成ImageRequest对象(服务端使用)
//CumulativeProtocolDecoder是非常有用的,他会缓冲所有的传入数据直到你的解码器能判定数据已经准备就绪
public class ImageRequestDecoder extends CumulativeProtocolDecoder {
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
if (in.remaining() >= 12) {
int width = in.getInt();
int height = in.getInt();
int numberOfCharachters = in.getInt();
ImageRequest request = new ImageRequest(width, height, numberOfCharachters);
out.write(request);
return true;
} else {
return false;
}
}
/**
* 每当一条完整的消息被解码,你应该把它写入ProtocolDecoderOutput;这些消息会沿着过滤器链传递并最终抵达IoHandler.messageReceived()方法
* 你不需要手动释放缓冲区,MINA会帮你做.
* 当数据还没接收完全时,只需要return false.
*/
}
Response:
//一个见到的POJO代表一个响应
public class ImageResponse {
private BufferedImage image1;
private BufferedImage image2;
public ImageResponse(BufferedImage image1, BufferedImage image2) {
this.image1 = image1;
this.image2 = image2;
}
public BufferedImage getImage1() {
return image1;
}
public BufferedImage getImage2() {
return image2;
}
}
//将ImageResponse对象编码成特定协议数据(服务端使用)
public class ImageResponseEncoder extends ProtocolEncoderAdapter {
public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
ImageResponse imageResponse = (ImageResponse) message;
byte[] bytes1 = getBytes(imageResponse.getImage1());
byte[] bytes2 = getBytes(imageResponse.getImage2());
int capacity = bytes1.length + bytes2.length + 8;
IoBuffer buffer = IoBuffer.allocate(capacity, false);
buffer.setAutoExpand(true);
buffer.putInt(bytes1.length);
buffer.put(bytes1);
buffer.putInt(bytes2.length);
buffer.put(bytes2);
buffer.flip();
out.write(buffer);
}
private byte[] getBytes(BufferedImage image) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", baos);
return baos.toByteArray();
}
/**
* 如果IoBuffer大小不能被事先计算时,可以使用自动扩容setAutoExpand(true).
*/
}
//将特定协议数据解码成ImageResponse对象(客户端使用)
public class ImageResponseDecoder extends CumulativeProtocolDecoder {
private static final String DECODER_STATE_KEY = ImageResponseDecoder.class.getName() + ".STATE";
public static final int MAX_IMAGE_SIZE = 5 * 1024 * 1024;
private static class DecoderState {
BufferedImage image1;
}
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
DecoderState decoderState = (DecoderState) session.getAttribute(DECODER_STATE_KEY);
if (decoderState == null) {
decoderState = new DecoderState();
session.setAttribute(DECODER_STATE_KEY, decoderState);
}
if (decoderState.image1 == null) {
// try to read first image
if (in.prefixedDataAvailable(4, MAX_IMAGE_SIZE)) {
decoderState.image1 = readImage(in);
} else {
// not enough data available to read first image
return false;
}
}
if (decoderState.image1 != null) {
// try to read second image
if (in.prefixedDataAvailable(4, MAX_IMAGE_SIZE)) {
BufferedImage image2 = readImage(in);
ImageResponse imageResponse = new ImageResponse(decoderState.image1, image2);
out.write(imageResponse);
decoderState.image1 = null;
return true;
} else {
// not enough data available to read second image
return false;
}
}
return false;
}
private BufferedImage readImage(IoBuffer in) throws IOException {
int length = in.getInt();
byte[] bytes = new byte[length];
in.get(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return ImageIO.read(bais);
}
/**
* 我们在session属性中保存了状态里记录解码进度,这个状态也可以被保存在Decoder本身属性中,但那样做有几个缺点:
* 每个IoSession都需要自己的Decoder实例.
* MINA确保在同一个IoSession中不会有超过一个线程同时在执行decode(),但是它不保证永远是再一个线程中执行.假设第一个数据片被线程1处理,
* 线程1判定这些数据还不足以被解码,当下一个数据片到达时,可能是被另一个线程处理.那么为了避免可见性问题,我们必须明确的对Decoder中的状态属
* 性进行同步声明(IoSession本身就具有这一特性).
* IoBuffer.prefixedDataAvailable() 是非常方便的当你使用长度前缀,它支持的1/2/4字节的前缀.
* 不要忘了重置解码状态当一个响应被解码完成时(另一种方式是:移除session中的状态属性).
*/
//如果只需要处理一张图片,则不需要保存状态
}
Factory:
public class ImageCodecFactory implements ProtocolCodecFactory {
private ProtocolEncoder encoder;
private ProtocolDecoder decoder;
public ImageCodecFactory(boolean client) {
if (client) {
encoder = new ImageRequestEncoder();
decoder = new ImageResponseDecoder();
} else {
encoder = new ImageResponseEncoder();
decoder = new ImageRequestDecoder();
}
}
public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
return encoder;
}
public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {
return decoder;
}
/**
* 针对每一个新的IoSession,MINA会要求ProtocolCodecFactory创建编解码器.
* 因为我们的编解码器没有存储交互式的状态,所以让所有session共享一个实例是安全的.
*/
}
服务端:
public class ImageServer {
public static final int PORT = 33789;
public static void main(String[] args) throws IOException {
ImageServerIoHandler handler = new ImageServerIoHandler();
NioSocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("protocol", new ProtocolCodecFilter(new ImageCodecFactory(false)));
acceptor.setLocalAddress(new InetSocketAddress(PORT));
acceptor.setHandler(handler);
acceptor.bind();
System.out.println("server is listenig at port " + PORT);
}
}
public class ImageServerIoHandler extends IoHandlerAdapter {
private final static String characters = "mina rocks abcdefghijklmnopqrstuvwxyz0123456789";
public static final String INDEX_KEY = ImageServerIoHandler.class.getName() + ".INDEX";
private Logger logger = LoggerFactory.getLogger(this.getClass());
public void sessionOpened(IoSession session) throws Exception {
session.setAttribute(INDEX_KEY, 0);
}
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
IoSessionLogger sessionLogger = IoSessionLogger.getLogger(session, logger);
sessionLogger.warn(cause.getMessage(), cause);
}
public void messageReceived(IoSession session, Object message) throws Exception {
ImageRequest request = (ImageRequest) message;
String text1 = generateString(session, request.getNumberOfCharacters());
String text2 = generateString(session, request.getNumberOfCharacters());
BufferedImage image1 = createImage(request, text1);
BufferedImage image2 = createImage(request, text2);
ImageResponse response = new ImageResponse(image1, image2);
session.write(response);
}
private BufferedImage createImage(ImageRequest request, String text) {
BufferedImage image = new BufferedImage(request.getWidth(), request.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
Graphics graphics = image.createGraphics();
graphics.setColor(Color.YELLOW);
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
Font serif = new Font("serif", Font.PLAIN, 30);
graphics.setFont(serif);
graphics.setColor(Color.BLUE);
graphics.drawString(text, 10, 50);
return image;
}
private String generateString(IoSession session, int length) {
Integer index = (Integer) session.getAttribute(INDEX_KEY);
StringBuffer buffer = new StringBuffer(length);
while (buffer.length() < length) {
buffer.append(characters.charAt(index));
index++;
if (index >= characters.length()) {
index = 0;
}
}
session.setAttribute(INDEX_KEY, index);
return buffer.toString();
}
}
客户端:
public class ImageClient extends IoHandlerAdapter {
public static final int CONNECT_TIMEOUT = 3000;
private String host;
private int port;
private SocketConnector connector;
private IoSession session;
private ImageListener imageListener;
public ImageClient(String host, int port, ImageListener imageListener) {
this.host = host;
this.port = port;
this.imageListener = imageListener;
connector = new NioSocketConnector();
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ImageCodecFactory(true)));
connector.setHandler(this);
}
public void messageReceived(IoSession session, Object message) throws Exception {
ImageResponse response = (ImageResponse) message;
imageListener.onImages(response.getImage1(), response.getImage2());
}
...
}