開發應用程序與應用程序之間的通訊,程序以前通訊 須要定義協議,好比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(); } }
輸出