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

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

项目结构:

mina二进制数据处理粘包断包_第1张图片
首先是构建项目的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

你可能感兴趣的:(java)