一.自定义协议介绍
1、自定义的编解码工厂
要实现编解码工厂就要实现ProtocolCodecFactory这个接口。
2、实现自定义编解码器
(1)实现自定义解码器:实现ProtocolDecoder接口。
(2)实现自定义的编码器:实现ProtocolEncoder接口。
(3)最后就可以根据我们的自定义编解码工厂获得我们的编解码对象。
3、为什么要使用自定义的编码器?
因为实际工作中往往不是通过一个字符串就可以传输所有的信息,我们传输的是自定义的协议包。并且能在应用程序和网络通信中存在对象和二进制之间转化关系。所以我们需要结合业务编写自定义的编解码器。
4、常用的自定义协议的方法
(1)定长的方式:比如两个字节aa,bb,ok,no等这样的通信方式;
(2)定界符: helloworld | hahaha|......|......通过特殊的符号来区别消息。这样的方式会出现黏包、半包等现象。
比如hello world|watchemen放进了缓冲区,带来了不正确的消息,这样就应该丢弃数据。
(3)自定义的协议包:包头 + 包体
- 包头:数据包的版本号/信息,以及整个数据包(包头+包体)的长度
- 包体:实际数据
总结:自定义协议数据包分析
我们完成通过客户端不断发送指定数目的自定义数据包,然后在服务端解析,这个过程中我们要解决半包问题。
二.Mina小项目Demo
第一步:自定义协议包
package minaStady.com.mina.protocal;
/**
* @author
* @date 创建时间:2018年10月12日 上午10:48:40
* @Description 自定义协议包
*/
public class ProtocalPack {
private int length;
private byte flag;
private String content;
public ProtocalPack(byte flag,String content){
this.flag = flag;
this.content = content;
int len1 = content == null?0:content.getBytes().length;
this.length = 5+len1;//包头(length+版本信息) + 包体
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public byte getFlag() {
return flag;
}
public void setFlag(byte flag) {
this.flag = flag;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("length:").append(length);
sb.append("flag:").append(flag);
sb.append("content:").append(content);
return sb.toString();
}
}
第二步:编码器
package minaStady.com.mina.protocal;
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.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
/**
* @author
* @date 创建时间:2018年10月12日 下午1:38:04
* @Description 编码器(将对象转成字节流)
*/
public class ProtocalEncoder extends ProtocolEncoderAdapter{
private final Charset charset;//定义编码型
public ProtocalEncoder(Charset charset){
this.charset = charset;
}
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
ProtocalPack value = (ProtocalPack) message;//报文信息
IoBuffer buf = IoBuffer.allocate(value.getLength());//设置缓冲区
buf.setAutoExpand(true);//自动增长
buf.putInt(value.getLength());//设置包头
buf.put(value.getFlag());
if(value.getContent() != null){//设置内容
buf.put(value.getContent().getBytes());
}
buf.flip();
out.write(buf);//发送出去
}
}
第三步:解码器
package minaStady.com.mina.protocal;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
/**
* @author
* @date 创建时间:2018年10月12日 下午1:56:45
* @Description 解码器(将字节流转成对象)
*/
public class ProtocalDecoder implements ProtocolDecoder{
private final AttributeKey CONTEXT = new AttributeKey(this.getClass(),"context");//设置上下文存储
private final Charset charset;
private int maxPackLength = 100;//设置最大长度,过滤
public int getMaxPackLength() {
return maxPackLength;
}
public void setMaxPackLength(int maxPackLength) {
if(maxPackLength<0){
throw new IllegalArgumentException("maxPackLength参数:"+maxPackLength);
}
this.maxPackLength = maxPackLength;
}
//默认
public ProtocalDecoder(){
this(Charset.defaultCharset());
}
public ProtocalDecoder(Charset charset){
this.charset = charset;
}
public Context getContext(IoSession session){
Context ctx = (Context)session.getAttribute(CONTEXT);
if(ctx == null){
ctx = new Context();
session.setAttribute(CONTEXT, ctx);
}
return ctx;
}
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
final int packHeadlength = 5;//代表包头的长度
Context ctx = this.getContext(session);
ctx.append(in);
IoBuffer buf = ctx.getBuf();
buf.flip();//缓冲区的指针从0开始
while(buf.remaining() >= packHeadlength){
buf.mark();
int length = buf.getInt();
byte flag = buf.get();
if(length < 0 || length > maxPackLength ){
buf.reset();
break;
}else if(length>=packHeadlength && length-packHeadlength<=buf.remaining()){
int oldLimit = buf.limit();
buf.limit(buf.position()+ length - packHeadlength);
String content = buf.getString(ctx.getDecoder());
buf.limit(oldLimit);
ProtocalPack pakeage = new ProtocalPack(flag, content);
out.write(pakeage);//发送数据包
}else { //半包
buf.clear();
break;
}
}
//读完是否还有数据
if(buf.hasRemaining()){
IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);
temp.put(buf);
temp.flip();
buf.reset();
buf.put(temp);
}else{
buf.reset();//清空
}
}
public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
}
public void dispose(IoSession session) throws Exception {
Context ctx = (Context)session.getAttribute(CONTEXT);
if(ctx != null){
session.removeAttribute(CONTEXT);
}
}
private class Context { //上下文对象
private final CharsetDecoder decoder;
private IoBuffer buf;
private Context(){
decoder = charset.newDecoder();
buf = IoBuffer.allocate(80).setAutoExpand(true);
}
public void append(IoBuffer in){
this.getBuf().put(in);
}
public void rest(){
decoder.reset();
}
public IoBuffer getBuf() {
return buf;
}
public void setBuf(IoBuffer buf) {
this.buf = buf;
}
public CharsetDecoder getDecoder() {
return decoder;
}
}
}
第四步:编解码工厂
package minaStady.com.mina.protocal;
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 minaStady.com.mina.protocal.ProtocalDecoder;
import minaStady.com.mina.protocal.ProtocalEncoder;
/**
* @author
* @date 创建时间:2018年10月12日 下午2:41:35
* @Description 编解码工厂
*/
public class ProtocalFactory implements ProtocolCodecFactory{
private final ProtocalDecoder decoder;
private final ProtocalEncoder encoder;
public ProtocalFactory(Charset charset){
encoder = new ProtocalEncoder(charset);
decoder = new ProtocalDecoder(charset);
}
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
}
第五步:服务端实例
package minaStady.com.mina.protocal;
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.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
/**
* @author
* @date 创建时间:2018年10月12日 下午2:49:24
* @Description 服务端实例
*/
public class ProtocalServer {
private static final int port = 7080;
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("coderc", new ProtocolCodecFilter(
new ProtocalFactory(Charset.forName("UTF-8"))));//设置编解码器
acceptor.getSessionConfig().setReadBufferSize(1024);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
acceptor.setHandler(new Myhandler());
acceptor.bind(new InetSocketAddress(port));
System.out.println("server start......");
}
}
class Myhandler extends IoHandlerAdapter{
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("server->sessionIdle");
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
System.out.println("server->exceptionCaught");
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
ProtocalPack pack = (ProtocalPack)message;
System.out.println("服务端接收: " + pack);
}
}
第六步:客户端实例
package minaStady.com.mina.protocal;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
/**
* @author
* @date 创建时间:2018年10月12日 下午3:06:14
* @Description 客户端实例
*/
public class ProtocalClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 7080;
static long counter = 0;
final static int fil = 100;
static long start = 0;
public static void main(String[] args) {
start = System.currentTimeMillis();//获取当前时间
IoConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("coderc", new ProtocolCodecFilter(
new ProtocalFactory(Charset.forName("UTF-8"))));//设置编解码器
connector.getSessionConfig().setReadBufferSize(1024);
connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
connector.setHandler(new MyHandlers());
ConnectFuture connectfuture = connector.connect(new InetSocketAddress(HOST,PORT));
connectfuture.addListener(new IoFutureListener<ConnectFuture>() {
public void operationComplete(ConnectFuture future) {
if(future.isConnected()){
IoSession session = future.getSession();
sendata(session);
}
}
});
}
public static void sendata(IoSession session){
for (int i = 0; i < fil; i++) {
String content = "watchmen:"+i;
ProtocalPack pack = new ProtocalPack((byte)i, content);
session.write(pack);
System.out.println("客户端发送数据:"+pack);
}
}
}
class MyHandlers extends IoHandlerAdapter{
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
if(status == IdleStatus.READER_IDLE){
session.close(true);
}
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
ProtocalPack pack = (ProtocalPack)message;
System.out.println("client->"+pack);
}
}
第七步:运行结果