mina二进制数据处理粘包断包

来源:互联网 发布:网店交易 知乎 编辑:程序博客网 时间:2024/06/05 06:45

ApacheMINA是一个网络应用程序框架,用来帮助用户简单地开发高性能和高可扩展性的网络应用程序。它提供了一个通过Java NIO在不同的传输例如TCP/IP和UDP/IP上抽象的事件驱动的异步API。

项目结构:

这里写图片描述
首先是构建项目的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>com.roden.mina</groupId>    <artifactId>mina</artifactId>    <version>0.0.1-SNAPSHOT</version>    <packaging>jar</packaging>    <name>mina</name>    <url>http://maven.apache.org</url>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>3.8.1</version>            <scope>test</scope>        </dependency>        <dependency>            <groupId>org.apache.mina</groupId>            <artifactId>mina-core</artifactId>            <version>2.0.12</version>        </dependency>    </dependencies></project>

消息实体

package com.roden.mina.entity;/* * 登陆消息 */public class LoginMsg {    public short startBit=0x76;  //起始固定位    public byte packageLen;//包长度    public byte protocolNo;//协议号    public String content;//信息内容    public short serialNumber;//信息序列号    public short errorCheck;//错误校验    public short endBit;//停止位    public short getStartBit() {        return startBit;    }    public void setStartBit(short startBit) {        this.startBit = startBit;    }    public byte getPackageLen() {        return packageLen;    }    public void setPackageLen(byte packageLen) {        this.packageLen = packageLen;    }    public byte getProtocolNo() {        return protocolNo;    }    public void setProtocolNo(byte protocolNo) {        this.protocolNo = protocolNo;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }    public short getSerialNumber() {        return serialNumber;    }    public void setSerialNumber(short serialNumber) {        this.serialNumber = serialNumber;    }    public short getErrorCheck() {        return errorCheck;    }    public void setErrorCheck(short errorCheck) {        this.errorCheck = errorCheck;    }    public short getEndBit() {        return endBit;    }    public void setEndBit(short endBit) {        this.endBit = endBit;    }}

编解码

package com.roden.mina.encode;import java.nio.charset.Charset;import org.apache.mina.core.buffer.IoBuffer;import org.apache.mina.core.session.IoSession;import org.apache.mina.filter.codec.ProtocolEncoder;import org.apache.mina.filter.codec.ProtocolEncoderOutput;import com.roden.mina.entity.LoginMsg;public class LoginMsgEncoder implements ProtocolEncoder {    private Charset charset;    public LoginMsgEncoder(Charset charset) {        this.charset = charset;    }    @Override    public void encode(IoSession session, Object message,            ProtocolEncoderOutput out) throws Exception {        LoginMsg lm = (LoginMsg) message;        IoBuffer buffer = IoBuffer.allocate(100, false).setAutoExpand(true);        buffer.putShort(lm.getStartBit());        buffer.put(lm.getPackageLen());        buffer.put(lm.getProtocolNo());        buffer.put(lm.getContent().getBytes(charset));        buffer.putShort(lm.getSerialNumber());        buffer.putShort(lm.getErrorCheck());        buffer.putShort(lm.getEndBit());        buffer.flip();        out.write(buffer);    }    @Override    public void dispose(IoSession session) throws Exception {    }}package com.roden.mina.decode;import java.nio.charset.Charset;import org.apache.mina.core.buffer.IoBuffer;import org.apache.mina.core.session.IoSession;import org.apache.mina.filter.codec.CumulativeProtocolDecoder;import org.apache.mina.filter.codec.ProtocolDecoderOutput;import com.roden.mina.entity.LoginMsg;//消息解码器public class LoginMsgDecoder extends CumulativeProtocolDecoder {        private Charset charset;    public LoginMsgDecoder(Charset charset) {        this.charset = charset;    }    @Override    protected boolean doDecode(IoSession session, IoBuffer buffer,ProtocolDecoderOutput out) throws Exception {        while (buffer.remaining() > 3) {            //记录解码数据起始位置            buffer.mark();            // 读取包格式及大小            short s = buffer.getShort();            byte length = buffer.get();                         // 检查读取的包头是否正常,不正常的话清空buffer            if (length < 0) {                buffer.clear();                         break;            } else if (length > 3 && buffer.remaining() >= length+7) {                LoginMsg lm = new LoginMsg();                lm.setStartBit(s);                lm.setPackageLen(length);                lm.setProtocolNo(buffer.get());                byte[] content = new byte[lm.getPackageLen()];                buffer.get(content);                lm.setContent(new String(content,charset));                lm.setSerialNumber(buffer.getShort());                lm.setErrorCheck(buffer.getShort());                lm.setEndBit(buffer.getShort());                out.write(lm);                return true;            } else {                    // 如果消息包不完整,将指针重新移动消息头的起始位置                 buffer.reset();                return false;            }                   }        return false;    }}package com.roden.mina.common;import java.nio.charset.Charset;import org.apache.mina.core.session.IoSession;import org.apache.mina.filter.codec.ProtocolCodecFactory;import org.apache.mina.filter.codec.ProtocolDecoder;import org.apache.mina.filter.codec.ProtocolEncoder;import com.roden.mina.decode.LoginMsgDecoder;import com.roden.mina.encode.LoginMsgEncoder;//编解码器生成工产public class LoginMsgProtocolCodecFactory implements ProtocolCodecFactory {    private ProtocolEncoder encoder;    private ProtocolDecoder decoder;    public LoginMsgProtocolCodecFactory()    {        this(Charset.forName("UTF-8"));    }    public LoginMsgProtocolCodecFactory(Charset charset)    {        encoder = new LoginMsgEncoder(charset);        decoder = new LoginMsgDecoder(charset);    }    @Override    public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {        return decoder;    }    @Override    public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {        return encoder;    }}

程序的关键就是解码时需要处理断包和粘包,本例通过继承CumulativeProtocolDecoder实现doDecode方法
A. 你的doDecode()方法返回true 时,CumulativeProtocolDecoder 的decode()方法会首先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据,如果没有,则会抛出非法的状态异常,也就是你的doDecode()方法返回true 就表示你已经消费了本次数据(相当于聊天室中一个完整的消息已经读取完毕),进一步说,也就是此时你必须已经消费过内部的IoBuffer 缓冲区的数据(哪怕是消费了一个字节的数据)。如果验证过通过,那么CumulativeProtocolDecoder 会检查缓冲区内是否还有数据未读取,如果有就继续调用doDecode()方法,没有就停止对doDecode()方法的调用,直到有新
的数据被缓冲。
B. 当你的doDecode()方法返回false 时,CumulativeProtocolDecoder 会停止对doDecode()方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的IoBuffer 缓冲区保存到IoSession 中,以便下一次数据到来时可以从IoSession 中提取合并。如果发现本次数据全都读取完毕,则清空IoBuffer 缓冲区。简而言之,当你认为读取到的数据已经够解码了,那么就返回true,否则就返回false。这个CumulativeProtocolDecoder 其实最重要的工作就是帮你完成了数据的累积,因为这个工作是很烦琐的。

也就是说返回true,那么CumulativeProtocolDecoder会再次调用decoder,并把剩余的数据发下来
返回false就不处理剩余的,当有新数据包来的时候把剩余的和新的拼接在一起然后再调用decoder

服务端

package com.roden.mina.server;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.charset.Charset;import org.apache.mina.core.service.IoAcceptor;import org.apache.mina.core.session.IdleStatus;import org.apache.mina.filter.codec.ProtocolCodecFilter;import org.apache.mina.filter.codec.prefixedstring.PrefixedStringCodecFactory;import org.apache.mina.filter.codec.textline.TextLineCodecFactory;import org.apache.mina.filter.logging.LoggingFilter;import org.apache.mina.transport.socket.nio.NioSocketAcceptor;import com.roden.mina.common.LoginMsgProtocolCodecFactory;public class MinaTimeServer {    public static final int PORT= 9800;    public static void main(String[] args) throws IOException {        // 创建服务端监控线程        IoAcceptor acceptor = new NioSocketAcceptor();        // 设置日志记录器        acceptor.getFilterChain().addLast("logger", new LoggingFilter());        // 设置编码过滤器        //acceptor.getFilterChain().addLast("codec",new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));        //acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter( new PrefixedStringCodecFactory(Charset.forName("UTF-8"))));        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter( new LoginMsgProtocolCodecFactory(Charset.forName("UTF-8"))));        // 指定业务逻辑处理器        acceptor.setHandler(new TimeServerHandler());        acceptor.getSessionConfig().setReadBufferSize(2048);        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);        // 设置端口号        acceptor.bind(new InetSocketAddress(PORT));           }}package com.roden.mina.server;import org.apache.mina.core.service.IoHandlerAdapter;import org.apache.mina.core.session.IdleStatus;import org.apache.mina.core.session.IoSession;import com.roden.mina.entity.LoginMsg;//实现接口IoHandler中的回调方法,优先适配器模式public class TimeServerHandler extends IoHandlerAdapter {     @Override    public void sessionCreated(IoSession session){        // 显示客户端的ip和端口        System.out.println(session.getRemoteAddress().toString());    }        @Override    public void messageReceived(IoSession session, Object message) throws Exception {        LoginMsg lm=(LoginMsg)message;        System.out.println("----------message-----------");        System.out.println(lm.getPackageLen());        System.out.println(lm.getContent());        System.out.println("---------------------");    }}

客户端

package com.roden.mina.client;import java.io.UnsupportedEncodingException;import java.net.InetSocketAddress;import java.nio.charset.Charset;import java.util.Scanner;import org.apache.mina.core.future.ConnectFuture;import org.apache.mina.core.service.IoConnector;import org.apache.mina.core.session.IoSession;import org.apache.mina.filter.codec.ProtocolCodecFilter;import org.apache.mina.filter.codec.prefixedstring.PrefixedStringCodecFactory;import org.apache.mina.filter.codec.textline.TextLineCodecFactory;import org.apache.mina.filter.logging.LoggingFilter;import org.apache.mina.transport.socket.nio.NioSocketConnector;import com.roden.mina.common.LoginMsgProtocolCodecFactory;import com.roden.mina.entity.LoginMsg;import com.roden.mina.server.MinaTimeServer;public class MimaTimeClient {    public static void main(String[] args) throws UnsupportedEncodingException {          // 创建客户端连接器.        NioSocketConnector connector = new NioSocketConnector();        connector.getFilterChain().addLast("logger", new LoggingFilter());        //connector.getFilterChain().addLast("codec",  new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));        //connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new PrefixedStringCodecFactory(Charset.forName("UTF-8"))));        connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new LoginMsgProtocolCodecFactory(Charset.forName("UTF-8"))));        // 设置连接超时检查时间        connector.setConnectTimeoutCheckInterval(30);        connector.setHandler(new TimeClientHander());        // 建立连接        ConnectFuture cf = connector.connect(new InetSocketAddress("127.0.0.1", MinaTimeServer.PORT));        // 等待连接创建完成        cf.awaitUninterruptibly();        IoSession session = cf.getSession();        String str="我是消息休";        byte a=2;               LoginMsg lm=new LoginMsg();        short s=78;        lm.setStartBit(s);                lm.setPackageLen((byte)str.getBytes("utf-8").length);        lm.setProtocolNo(a);               lm.setContent(str);        lm.setSerialNumber(s);        lm.setErrorCheck(s);        lm.setEndBit(s);        session.write(lm);           //关闭        if(session!=null){            if(session.isConnected()){                // 等待连接断开                cf.getSession().getCloseFuture().awaitUninterruptibly();            }             // 释放连接            connector.dispose(true);        }    }}package com.roden.mina.client;import org.apache.mina.core.service.IoHandlerAdapter;import org.apache.mina.core.session.IoSession;public class TimeClientHander  extends IoHandlerAdapter  {    @Override    public void messageReceived(IoSession session, Object message) throws Exception {               System.out.println("client接受信息:"+message.toString());    }    @Override    public void messageSent(IoSession session, Object message) throws Exception {               System.out.println("client发送信息:"+message.toString());    }}

Hander部分采用适配器模式,自己选择要实现的方法

参考:
http://mina.apache.org/mina-project/quick-start-guide.html

0 0
原创粉丝点击