开发应用程序与应用程序之间的通信,程序之前通信 需要定义协议,比如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();
} }

输出

netty使用MessageToByteEncoder 自定义协议(四)的更多相关文章

  1. 物联网架构成长之路(35)-利用Netty解析物联网自定义协议

    一.前言 前面博客大部分介绍了基于EMQ中间件,通信协议使用的是MQTT,而传输的数据为纯文本数据,采用JSON格式.这种方式,大部分一看就知道是熟悉Web开发.软件开发的人喜欢用的方式.由于我也是做 ...

  2. 利用Netty构建自定义协议的通信

    在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如:HTTP,Telnet,FTP,SMTP等等. 在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优 ...

  3. 【转】Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

  4. Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

  5. 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)

    一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行)   LineBasedFrameDecoder是回 ...

  6. netty 自定义协议

    netty 自定义协议 netty 是什么呢? 相信很多人都被人问过这个问题.如果快速准确的回复这个问题呢?网络编程框架,netty可以让你快速和简单的开发出一个高性能的网络应用.netty是一个网络 ...

  7. Netty自定义协议解析原理与应用

    目前,大家都选择Netty做为游戏服务器框架网络通信的框架,而且目前也有很多优秀的产品是基于Netty开发的.它的稳定性,易用性和高效率性已得到广泛的认同.在游戏服务器开发中,选择netty一般就意味 ...

  8. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  9. java-cef系列视频第四集:自定义协议

    上一集我们介绍了如何为java-cef添加flashplayer支持. 本视频介绍java-cef中的自定义协议 本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 中国大陆许可协议进行许可.

随机推荐

  1. BNU 13259.Story of Tomisu Ghost 分解质因子

    Story of Tomisu Ghost It is now 2150 AD and problem-setters are having a horrified time as the ghost ...

  2. 浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】

    本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.html 本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linu ...

  3. SQL2000数据库定期自动备份与修改

    SQL2000数据库定期自动备份与修改 http://www.veryhuo.com 2009-11-19 烈火网 投递稿件 我有话说   在SQL server企业管理器中,可以设置数据库的定期自动 ...

  4. ledisDB底层实现——本质上就是用leveldb这样的底层存储,和ssdb一样,meta里存的是hash、list等的元数据

    Hash hash可以算是一种两级kv,首先通过key找到一个hash对象,然后再通过field找到或者设置相应的值. 在ledisdb里面,我们需要将key跟field关联成一个key,用来存放或者 ...

  5. 【NOI 2014】 动物园

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=3670 [算法] KMP [代码] #include<bits/stdc++.h ...

  6. SQL Server2012 T-SQL基础教程--读书笔记(8 - 10章)

    SQL Server2012 T-SQL基础教程--读书笔记(8 - 10章) 示例数据库:点我 CHAPTER 08 数据修改 8.1 插入数据 8.1.1 INSERT VALUES 语句 8.1 ...

  7. gulp安装成功但是无法使用

    gulp安装正常,但是查看gulp -v和使用gulp的时候报错, 原因:缺少环境变量或环境变量错误. 查找环境变量的方法:在dos下输入npm config get prefix就会显示一个地址,这 ...

  8. ie8 不支持 position:fixed 的简单解决办法

    今天发现使用 position:fixed 的页面在firefox下没有问题,在IE8下却不能正常显示,在网上找了找,有不少相关文章,但是不是不起作用就是太复杂,后来终于发现一个简单的解决办法,就是在 ...

  9. Python 34(进程了解)

    一:僵尸进程与孤儿进程 测试程序: 基本概念: 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中. ...

  10. springboot启动报错:Cannot determine embedded database driver class for database type NONE.

    package cn.zb.test; import org.springframework.boot.SpringApplication; import org.springframework.bo ...