netty使用MessageToByteEncoder 自定義協議(四)

開發應用程序與應用程序之間的通訊,程序以前通訊 須要定義協議,好比http協議。java

首先咱們定義一個協議類sql

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 不足空補socket

 

解碼器ide

 

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.循環讀到包分割符起始位置ui

    2.判斷可讀的包長度是否大於基本數據長度 若是不大於表示 拆包了 head部分沒有發完。等待下一次處理this

    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();
    }

}

輸出

 

相關文章
相關標籤/搜索