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