本文摘自《Mina用户指南》Chapter 9 - Codec Filter,但是用户指南中客户端没有完整版代码,在这里我简单的补上了。此文主要是为了下一篇使用Mina发送数据执行过程分析做准备。
实现内容: client端向server端发送请求(请求图片),server端向client端传输请求的图片。
准备工作:将Mina2.0.7 导入到项目中。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.mina/mina-core -->
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
第一步构架请求/响应消息实体。
// 请求实体
public class ImageRequest {
private int width;
private int height;
private int numberOfCharacters;
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;
}
}
// 响应实体
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;
}
}
第二步,编写 编解码器。
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 session) throws Exception {
return encoder;
}
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
}
public class ImageRequestEncoder extends ProtocolEncoderAdapter {
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 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;
}
}
}
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 avaliable 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 avaliable 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);
}
}
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, "JPG", baos);
return baos.toByteArray();
}
}
关于其中的 校验功能prefixedDataAvailable源码如下:
@Override
public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {
if (remaining() < prefixLength) {
return false;
}
int dataLength;
switch (prefixLength) {
case 1:
dataLength = getUnsigned(position());
break;
case 2:
dataLength = getUnsignedShort(position());
break;
case 4:
dataLength = getInt(position()); // 上文中使用的是4 ,
break;
default:
throw new IllegalArgumentException("prefixLength: " + prefixLength);
}
if (dataLength < 0 || dataLength > maxDataLength) {
throw new BufferDataException("dataLength: " + dataLength);
}
return remaining() - prefixLength >= dataLength;
}
第三步 编写消息处理器
public class ImageClientIoHandler extends IoHandlerAdapter {
private ImageListener imageListener;
public ImageClientIoHandler( ImageListener imageListener) {
this.imageListener = imageListener;
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
ImageResponse response = (ImageResponse) message;
imageListener.onImages(response.getImage1(), response.getImage2());
}
public void sendRequestMessage(IoSession session){
// 发送请求
ImageRequest imageRequest = new ImageRequest(200,200, 10);
session.write(imageRequest);
}
}
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());
@Override
public void sessionOpened(IoSession session) throws Exception {
session.setAttribute(INDEX_KEY, 0);
}
@Override
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 ImageServer {
public static final int PORT = 33789;
public static final String HOST = "127.0.0.1";
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.setDefaultLocalAddress(new InetSocketAddress(HOST, PORT));
acceptor.setHandler(handler);
acceptor.bind();
System.out.println("server IP " + acceptor.getLocalAddress());
System.out.println("server is listenig at port " + PORT);
}
}
public class ImageClient {
public static void main(String[] args) throws InterruptedException {
String host = "127.0.0.1";
int port = 33789;
ImageListener imageListener = new ImageListener();
ImageClientIoHandler clientIoHandler = new ImageClientIoHandler(imageListener);
SocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ImageCodecFactory(true)));
connector.setHandler(clientIoHandler);
connector.setDefaultRemoteAddress(new InetSocketAddress(host, port));
// 连接到传入的目标PC
ConnectFuture connect = connector.connect();
connect.awaitUninterruptibly(); // 等待连接创建完成, Mina 是基于NIO同步非阻塞
IoSession session = connect.getSession();
System.out.println(connect.isConnected());
clientIoHandler.sendRequestMessage(session);
}
}
附:创建图片以及浏览器查看图片实现。
public class ImageListener {
public void onImages(BufferedImage image1, BufferedImage image2) throws IOException {
// 为了方便效果的查看,调用浏览器访问保存目录
// 先保存
File file1 = new File("image1.jpg");
File file2 = new File("image2.jpg");
ImageIO.write(image1, "jpg", file1);
ImageIO.write(image2, "jpg", file2);
// 启动浏览器,打开文件目录
String chromPath = "D:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"; // 本机chrom.exe的位置,其他浏览器也可以
String imagesPath = file1.getAbsolutePath();
String target = "file:///" + imagesPath;
Runtime.getRuntime().exec(new String[]{chromPath, target});
System.out.println("please go to default browser");
}
}