zoukankan      html  css  js  c++  java
  • netty使用MessageToByteEncoder 自定义协议(四)

    开发应用程序与应用程序之间的通信,程序之前通信 需要定义协议,比如http协议。

    首先我们定义一个协议类

    package com.liqiang.SimpeEcode;
    
    import java.sql.Date;
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    
    import com.liqiang.nettyTest2.Md5Utils;
    
    /**
     * 自定义协议 数据包格式
     * -----------------------------------
     * | 协议开始标志 | 包长度|令牌 (定长50个字节)|令牌生成时间(定长50个字节)| 包内容 |   
     * -----------------------------------
     * 令牌生成规则
     *  协议开始标志 +包长度+令牌生成时间+包内容+服务器与客户端约定的秘钥
     * @author Administrator
     *
     */
    public class Message {
        public Message(MessageHead head,byte[] content) {
            this.head=head;
            this.content=content;
        }
        // 协议头
        private MessageHead head;
    
        // 内容
        private byte[] content;
    
        public MessageHead getHead() {
            return head;
        }
    
        public void setHead(MessageHead head) {
            this.head = head;
        }
    
        public byte[] getContent() {
            return content;
        }
    
        public void setContent(byte[] content) {
            this.content = content;
        }
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "[head:"+head.toString()+"]"+"content:"+new String(content);
        }
        
        /**
         * 生成token   协议开始标志 +包长度+令牌生成时间+包内容+服务器与客户端约定的秘钥
         * @return
         */
        public String buidToken() {
            //生成token
            SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = format0.format(this.getHead().getCreateDate());// 这个就是把时间戳经过处理得到期望格式的时间
            String allData=String.valueOf(this.getHead().getHeadData());
            allData+=String.valueOf(this.getHead().getLength());
            allData+=time;
            allData+=new String(this.getContent());
            allData+="11111";//秘钥
            return Md5Utils.stringMD5(allData);
    
        }
        /**
         * 验证是否认证通过
         * @param token
         * @return
         */
        public boolean authorization(String token) {
            //表示参数被修改
            if(!token.equals(this.getHead().getToken())) {
                return false;
            }
            //验证是否失效
            Long s = (System.currentTimeMillis() - getHead().getCreateDate().getTime()) / (1000 * 60);
            if(s>60) {
                return false;
            }
            return true;
        }
    
    }

    Head类

    package com.liqiang.SimpeEcode;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class MessageHead {
         private int headData=0X76;//协议开始标志
            private int length;//包的长度
            private String token;
            private Date createDate;
           
            public int getHeadData() {
                return headData;
            }
            public void setHeadData(int headData) {
                this.headData = headData;
            }
            public int getLength() {
                return length;
            }
            public void setLength(int length) {
                this.length = length;
            }
            
            
            public String getToken() {
                return token;
            }
            public void setToken(String token) {
                this.token = token;
            }
            public Date getCreateDate() {
                return createDate;
            }
            public void setCreateDate(Date createDate) {
                this.createDate = createDate;
            }
            @Override
            public String toString() {
                SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                // TODO Auto-generated method stub
                return "headData:"+headData+",length:"+length+",token:"+token+",createDate:"+    simpleDateFormat.format(createDate);
            }
    }

    自定义的编码器

    package com.liqiang.SimpeEcode;
    
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Calendar;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    
    public class MessageEncoder extends MessageToByteEncoder<Message> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
            // TODO Auto-generated method stub
            // 写入开头的标志
            out.writeInt(msg.getHead().getHeadData());
            // 写入包的的长度
            out.writeInt(msg.getContent().length);
            byte[] tokenByte = new byte[50];
            /**
             * token定长50个字节
             *  第一个参数 原数组
             *  第二个参数 原数组位置
             *  第三个参数 目标数组 
             *  第四个参数 目标数组位置 
             *  第五个参数 copy多少个长度
             */
            byte[] indexByte=msg.getHead().getToken().getBytes();
            try {
                System.arraycopy(indexByte, 0, tokenByte, 0,indexByte.length>tokenByte.length?tokenByte.length:indexByte.length);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            
            //写入令牌
            out.writeBytes(tokenByte);
            byte[] createTimeByte = new byte[50];
            SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = format0.format(msg.getHead().getCreateDate());
            indexByte=time.getBytes();
            System.arraycopy(indexByte, 0, createTimeByte, 0,indexByte.length>createTimeByte.length?createTimeByte.length:indexByte.length);
            //写入令牌生成时间
            out.writeBytes(createTimeByte);
        
            // 写入消息主体
            out.writeBytes(msg.getContent());
    
        }
    
    }

    按照message注释的协议顺序 写入。token和token生成时间定长50 不足空补

    解码器

    package com.liqiang.SimpeEcode;
    
    import java.text.SimpleDateFormat;
    import java.util.List;
    import com.liqiang.nettyTest2.nettyMain;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    import io.netty.handler.codec.MessageToByteEncoder;
    import io.netty.handler.codec.MessageToMessageDecoder;
    
    public class MessageDecode extends ByteToMessageDecoder{
    
        private final int BASE_LENGTH=4+4+50+50;//协议头 类型 int+length 4个字节+令牌和 令牌生成时间50个字节
        private int headData=0X76;//协议开始标志
        
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
            // 刻度长度必须大于基本长度
            if(buffer.readableBytes()>=BASE_LENGTH) {
                /**
                 * 粘包 发送频繁 可能多次发送黏在一起 需要考虑  不过一个客户端发送太频繁也可以推断是否是攻击
                 */
                //防止soket流攻击。客户端传过来的数据太大不合理
                if(buffer.readableBytes()>2048) {
                    //buffer.skipBytes(buffer.readableBytes());
                    
                }
            }
            int beginIndex;//记录包开始位置
            while(true) {
                  // 获取包头开始的index  
                beginIndex = buffer.readerIndex();  
                //如果读到开始标记位置 结束读取避免拆包和粘包
                if(buffer.readInt()==headData) {
                    break;
                }
                 
                //初始化读的index为0
                buffer.resetReaderIndex();  
                // 当略过,一个字节之后,  
                //如果当前buffer数据小于基础数据 返回等待下一次读取
                if (buffer.readableBytes() < BASE_LENGTH) {  
                    return;  
                }  
            }
               // 消息的长度  
            int length = buffer.readInt();  
            // 判断请求数据包数据是否到齐  
            if ((buffer.readableBytes()-100) < length) {  
                //没有到期 返回读的指针 等待下一次数据到期再读
                buffer.readerIndex(beginIndex);  
                return;  
            }  
            //读取令牌
            byte[] tokenByte=new byte[50];
            buffer.readBytes(tokenByte);
           
            //读取令牌生成时间
            byte[]createDateByte=new byte[50];
            buffer.readBytes(createDateByte);
            //读取content
            byte[] data = new byte[length];  
            buffer.readBytes(data); 
            MessageHead head=new MessageHead();
            head.setHeadData(headData);
            head.setToken(new String(tokenByte).trim());
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            head.setCreateDate(  simpleDateFormat.parse(new String(createDateByte).trim()));
            head.setLength(length);
            Message message=new Message(head, data);
            //认证不通过
            if(!message.authorization(message.buidToken())) {
                ctx.close();
                
                return;
            }
            out.add(message);
            buffer.discardReadBytes();//回收已读字节
        }
        
    
    }

    解码器 在解码的同时需要做拆包和粘包处理

        1.循环读到包分割符起始位置

        2.判断可读的包长度是否大于基本数据长度 如果不大于表示 拆包了 head部分没有发完。等待下一次处理

        3.如果head部分发过来了  通过length 判断剩余可读部分 是否大于等于content内容长度 如果小于 表示 内容部分没有发完等待下一次处理

        4.如果都满足 则解析head部分 再根据length解析包内容 封装到message

        5.message.authorization   

                 1.首先按照我们token生成规则 生成字符串 +加密秘钥 生成token

                 2.2个token对比是否相等。如果不相等表示参数被窜改 或者加密秘钥有问题。是非法请求

                 3.如果token相等 判断时间是否超过1分种。避免别人抓到我们的包内容根据我们的包内容循环发送请求

       服务端和客户端应用上编码器

    Server

    package com.liqiang.nettyTest2;
    
    import com.liqiang.SimpeEcode.MessageDecode;
    import com.liqiang.SimpeEcode.MessageEncoder;
    
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.handler.codec.LineBasedFrameDecoder;
    import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType;
    import io.netty.handler.codec.DelimiterBasedFrameDecoder;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        private Server server;
        public ServerChannelInitializer(Server server) {
            this.server=server;
        }
        @Override
        protected void initChannel(SocketChannel channel) throws Exception {
            // TODO Auto-generated method stub
            channel.pipeline()
        
            .addLast("decoder",new MessageDecode())
            .addLast("encoder",new MessageEncoder())
            .addLast(new ServerHandle(server));
        }
    
    }

    Client

    package com.liqiang.nettyTest2;
    
    import com.liqiang.SimpeEcode.MessageDecode;
    import com.liqiang.SimpeEcode.MessageEncoder;
    
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        private Client client;
        public  ClientChannelInitializer(Client client) {
            // TODO Auto-generated constructor stub
            this.client=client;
        }
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            // TODO Auto-generated method stub
            socketChannel.pipeline()
            .addLast("encoder",new MessageEncoder())
            .addLast("decode",new MessageDecode())
            .addLast(new ClientHandle(client));//注册处理器
            
        }
    }

    测试运行

    package com.liqiang.nettyTest2;
    
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import javax.management.StringValueExp;
    import javax.swing.text.StringContent;
    
    import com.liqiang.SimpeEcode.Message;
    import com.liqiang.SimpeEcode.MessageHead;
    
    public class nettyMain {
        public static void main(String[] args) {
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    Server server = new Server(8081);
                    server.start();
    
                }
            }).start();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    Client client1 = new Client("127.0.0.1", 8081);
                    client1.connection();
                    String content = "哈哈哈哈!";
                    byte[] bts = content.getBytes();
                    MessageHead head = new MessageHead();
                    // 令牌生成时间
                    head.setCreateDate(new Date());
    
                    head.setLength(bts.length);
                    Message message = new Message(head, bts);
                    message.getHead().setToken(message.buidToken());
    
                    message.getHead().setToken(message.buidToken());
                    client1.sendMsg(message);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
    
                    //token错误 则认为是非法客户端会关闭连接
                    message.getHead().setToken("fff");
    
                    client1.sendMsg(message);
                    //再次发送 服务端则收不到
                    message.getHead().setToken(message.buidToken());
                    client1.sendMsg(message);
    
                }
            }).start();
        }
    
    }

    输出

     

  • 相关阅读:
    firefox显示 您的连接不安全 解决办法
    【TweenMax】to():添加动画
    【TweenMax】实例TimelineMax
    【js】document.all用法
    【js】阻止默认事件
    【封装】【JS】getClassName方法、get class+id封装
    【HTML】html结构,html5结构
    【实例】原生 js 实现全屏滚动效果
    【音乐】播放器
    GO : 斐波纳契数列
  • 原文地址:https://www.cnblogs.com/LQBlog/p/9159134.html
Copyright © 2011-2022 走看看