基于Netty的RPC架构学习笔记(十):自定义数据包协议

文章目录

  • 数据包简介
    • 粘包、分包现象
    • 数据包格式
  • 举个?
    • Common项目
      • Request.java
      • ConstantValue.java
      • RequestEncoder.java
      • RequestDecoder.java
      • Response.java
      • StateCode.java,response的状态码
      • ResponseEncoder.java
      • ResponseDecoder.java
    • FightRequest.java
    • Client项目
      • Client.java
      • HiHandler.java
    • Server项目
      • Server.java
      • HiHandler.java
    • 结果:
    • 弊端:

数据包简介

粘包、分包现象

假如客户端需要给服务端发送数据
give me a coffee give me a tea
可能会出现粘包或者分包的现象

  • 粘包现象
    give me a coffeegive me a tea
  • 分包现象
    give me
    a coffeegive me a tea

粘包和分包出现的原因是:没有一个稳定数据结构,解决方法比如

  • 分割符
    give me a coffee|give me a tea|
    give me a coffee|
    give me a tea|
  • 长度 + 数据
    16give me a coffee13give me a tea
    16give me a coffee
    13give me a tea

数据包格式

+——----——+——-----——+——----——+——----——+——-----——+
| 包头 | 模块号 | 命令号 | 长度 | 数据 |
+——----——+——-----——+——----——+——----——+——-----——+
包头4字节 (一些不常用的东西)
模块号2字节short(Player 1号)
命令号2字节short(Player要做的事情,比如获取玩家数据 1号)
长度4字节(描述数据部分字节长度)
Player 1
1 获取玩家数据
2 注册用户
3 购买金币

举个?

需要的jar:netty-3.10.5.final.jar

Common项目

新建项目Common==》新建包model、 module、codc、constant、serial==>model包下新建Request.java

Request.java

package com.cn.model;
/**
 * 请求对象
 * 
 */
public class Request {
	
	/**
	 * 请求模块
	 */
	private short module;
	
	/**
	 * 命令号
	 */
	private short cmd;
	
	/**
	 * 数据部分
	 */
	private byte[] data;

	public short getModule() {
		return module;
	}

	public void setModule(short module) {
		this.module = module;
	}

	public short getCmd() {
		return cmd;
	}

	public void setCmd(short cmd) {
		this.cmd = cmd;
	}

	public byte[] getData() {
		return data;
	}

	public void setData(byte[] data) {
		this.data = data;
	}
	
	
	public int getDataLength(){
		if(data == null){
			return 0;
		}
		return data.length;
	}
}

constant包下新建

ConstantValue.java

package com.cn.constant;

public interface ConstantValue {
	
	/**
	 * 包头,包头是一个常量
	 */
	public static final int FLAG = -32523523;

}

codc包下新建RequestDecoder.java和RequestEncoder.java

RequestEncoder.java

package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;

import com.cn.constant.ConstantValue;
import com.cn.model.Request;

/**
 * 请求编码器
 * 
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+
 * | 包头          | 模块号        | 命令号      |  长度        |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+
 * 
* 包头4字节 * 模块号2字节short * 命令号2字节short * 长度4字节(描述数据部分字节长度) * * * */
public class RequestEncoder extends OneToOneEncoder{ @Override protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception { Request request = (Request)(rs); ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(); //包头 buffer.writeInt(ConstantValue.FLAG); //module buffer.writeShort(request.getModule()); //cmd buffer.writeShort(request.getCmd()); //长度 buffer.writeInt(request.getDataLength()); //data if(request.getData() != null){ buffer.writeBytes(request.getData()); } return buffer; } }

RequestDecoder.java

package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

import com.cn.constant.ConstantValue;
import com.cn.model.Request;

/**
 * 请求解码器
 * 
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+
 * | 包头          | 模块号        | 命令号      |  长度        |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+
 * 
* 包头4字节 * 模块号2字节short * 命令号2字节short * 长度4字节(描述数据部分字节长度) * * * */
//FrameDecoder可以协助我们解决粘包分包的问题 public class RequestDecoder extends FrameDecoder{ /** * 数据包基本长度 */ public static int BASE_LENTH = 4 + 2 + 2 + 4; @Override protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception { //可读长度必须大于基本长度 if(buffer.readableBytes() >= BASE_LENTH){ //防止socket字节流攻击 if(buffer.readableBytes() > 2048){ buffer.skipBytes(buffer.readableBytes()); } //记录包头开始的index int beginReader; while(true){ beginReader = buffer.readerIndex(); buffer.markReaderIndex(); if(buffer.readInt() == ConstantValue.FLAG){ break; } //未读到包头,略过一个字节 buffer.resetReaderIndex(); buffer.readByte(); //长度又变得不满足 if(buffer.readableBytes() < BASE_LENTH){ return null; } } //模块号 short module = buffer.readShort(); //命令号 short cmd = buffer.readShort(); //长度 int length = buffer.readInt(); //判断请求数据包数据是否到齐 if(buffer.readableBytes() < length){ //还原读指针到最开始的地方 buffer.readerIndex(beginReader); return null; } //读取data数据 byte[] data = new byte[length]; buffer.readBytes(data); Request request = new Request(); request.setModule(module); request.setCmd(cmd); request.setData(data); //继续往下传递 return request; } //数据包不完整,需要等待后面的包来,(buffer.readableBytes() < BASE_LENTH return null; } }

ChannelBuffer writeindex和readindex
wirteindex初始值是0,当写一个int,wirteindex就是4
readindex同上
但是注意readindex是不能超过writeindex的

modle包下新建

Response.java

package com.cn.model;
/**

- 返回对象

- 
  *
   */
  public class Response {
  /**

  - 请求模块
    */
    private short module;

  /**

  - 命令号
    */
    private short cmd;

  /**

  - 状态码
    */
    private int stateCode;

  /**

  - 数据部分
    */
    private byte[] data;

  public short getModule() {
  	return module;
  }

  public void setModule(short module) {
  	this.module = module;
  }

  public short getCmd() {
  	return cmd;
  }

  public void setCmd(short cmd) {
  	this.cmd = cmd;
  }

  public int getStateCode() {
  	return stateCode;
  }

  public void setStateCode(int stateCode) {
  	this.stateCode = stateCode;
  }

  public byte[] getData() {
  	return data;
  }

  public void setData(byte[] data) {
  	this.data = data;
  }

  public int getDataLength(){
  	if(data == null){
  		return 0;
  	}
  	return data.length;
  }
  }

model下新建

StateCode.java,response的状态码

package com.cn.model;

public interface StateCode {
	
	/**
	 * 成功
	 */
	public static int SUCCESS  = 0;
	
	/**
	 * 失败
	 */
	public static int FAIL  =  1;

}

codc包下新建ResponseEncoder.java和ResponseDecoder.java

ResponseEncoder.java

package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import com.cn.constant.ConstantValue;
import com.cn.model.Response;

/**
 * 请求编码器
 * 
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * | 包头          | 模块号        | 命令号       |  状态码    |  长度          |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * 
* 包头4字节 * 模块号2字节short * 命令号2字节short * 长度4字节(描述数据部分字节长度) * * */
public class ResponseEncoder extends OneToOneEncoder{ @Override protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception { Response response = (Response)(rs); ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(); //包头 buffer.writeInt(ConstantValue.FLAG); //module buffer.writeShort(response.getModule()); //cmd buffer.writeShort(response.getCmd()); //状态码 buffer.writeInt(response.getStateCode()); //长度 buffer.writeInt(response.getDataLength()); //data if(response.getData() != null){ buffer.writeBytes(response.getData()); } return buffer; } }

ResponseDecoder.java

package com.cn.codc;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import com.cn.constant.ConstantValue;
import com.cn.model.Response;

/**
 * response解码器
 * 
 * 数据包格式
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * | 包头          | 模块号        | 命令号       |  状态码    |  长度          |   数据       |
 * +——----——+——-----——+——----——+——----——+——-----——+——-----——+
 * 
* 包头4字节 * 模块号2字节short * 命令号2字节short * 长度4字节(描述数据部分字节长度) * - * */
public class ResponseDecoder extends FrameDecoder{ /** * 数据包基本长度 */ public static int BASE_LENTH = 4 + 2 + 2 + 4; @Override protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception { //可读长度必须大于基本长度 if(buffer.readableBytes() >= BASE_LENTH){ //记录包头开始的index int beginReader = buffer.readerIndex(); while(true){ if(buffer.readInt() == ConstantValue.FLAG){ break; } } //模块号 short module = buffer.readShort(); //命令号 short cmd = buffer.readShort(); //状态码 int stateCode = buffer.readInt(); //长度 int length = buffer.readInt(); if(buffer.readableBytes() < length){ //还原读指针 buffer.readerIndex(beginReader); return null; } byte[] data = new byte[length]; buffer.readBytes(data); Response response = new Response(); response.setModule(module); response.setCmd(cmd); response.setStateCode(stateCode); response.setData(data); //继续往下传递 return response; } //数据包不完整,需要等待后面的包来 return null; } }

注意: 上面的代码在遇到socket字节流攻击的时候会有异常,包括为什么需要包头,会在下面一节中的最后进行讲解

新建serial包==》将上节最后的两个序列化的工具类粘贴过来(BufferFactory.java和Serializer.java)
model包下新建fuben包==》新建和request包和response包==》request包下新建FightRequest.java==》response包下新建FightResponse.java
这里的副本简单理解为打游戏刷的副本这个对象

FightRequest.java

package com.cn.module.fuben.request;

import com.cn.serial.Serializer;

public class FightRequest extends Serializer{
	
	/**
	 * 副本id
	 */
	private int fubenId;
	
	/**
	 * 次数
	 */
	private int count;

	public int getFubenId() {
		return fubenId;
	}

	public void setFubenId(int fubenId) {
		this.fubenId = fubenId;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	@Override
	protected void read() {
		this.fubenId = readInt();
		this.count = readInt();
	}

	@Override
	protected void write() {
		writeInt(fubenId);
		writeInt(count);
	}
	
	

}

Client项目

新建Client项目,将之前client代码拷贝过来

Client.java

package com.client;

import java.net.InetSocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import com.cn.codc.RequestEncoder;
import com.cn.codc.ResponseDecoder;
import com.cn.model.Request;
import com.cn.module.fuben.request.FightRequest;
/**
 * netty客户端入门
 *
 *
 */
public class Client {

	public static void main(String[] args) throws InterruptedException {
		
		//服务类
		ClientBootstrap bootstrap = new  ClientBootstrap();
		
		//线程池
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		
		//socket工厂
		bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));
		
		//管道工厂
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				//修改为刚刚写的ResponseEncoder和RequestEndoder
				pipeline.addLast("decoder", new ResponseDecoder());
				pipeline.addLast("encoder", new RequestEncoder());
				pipeline.addLast("hiHandler", new HiHandler());
				return pipeline;
			}
		});
		
		//连接服务端
		ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));
		Channel channel = connect.sync().getChannel();
		
		System.out.println("client start");
		
		Scanner scanner = new Scanner(System.in);
		while(true){
			System.out.println("请输入");
			int fubenId = Integer.parseInt(scanner.nextLine());
			int count = Integer.parseInt(scanner.nextLine());
			
			FightRequest fightRequest = new FightRequest();
			fightRequest.setFubenId(fubenId);
			fightRequest.setCount(count);
			
			Request request = new Request();
			request.setModule((short) 1);
			request.setCmd((short) 1);
			request.setData(fightRequest.getBytes());
			//发送请求
			channel.write(request);
		}
	}

}

主要的变化是

//修改为刚刚写的ResponseEncoder和RequestEndoder
//response解码
				pipeline.addLast("decoder", new ResponseDecoder());
				//requset编码
				pipeline.addLast("encoder", new RequestEncoder());
				pipeline.addLast("hiHandler", new HiHandler());

和最后变成发送requst

HiHandler.java

package com.client;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

import com.cn.model.Response;
import com.cn.model.StateCode;
import com.cn.module.fuben.request.FightRequest;
import com.cn.module.fuben.response.FightResponse;
/**
 * 消息接受处理类
 * 
 *
 */
public class HiHandler extends SimpleChannelHandler {

	/**
	 * 接收消息
	 */
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
	//修改为response对象
			Response message = (Response)e.getMessage();

			if(message.getModule() == 1){
				
				if(message.getCmd() == 1){
				//从server端获取数据并输出
					FightResponse fightResponse = new FightResponse();
					fightResponse.readFromBytes(message.getData());
					
					System.out.println("gold:" + fightResponse.getGold());
					
				}else if(message.getCmd() == 2){
					
				}
				
			}else if (message.getModule() == 1){
				
				
			}
	}

	/**
	 * 捕获异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		System.out.println("exceptionCaught");
		super.exceptionCaught(ctx, e);
	}

	/**
	 * 新连接
	 */
	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelConnected");
		super.channelConnected(ctx, e);
	}

	/**
	 * 必须是链接已经建立,关闭通道的时候才会触发
	 */
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelDisconnected");
		super.channelDisconnected(ctx, e);
	}

	/**
	 * channel关闭的时候触发
	 */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelClosed");
		super.channelClosed(ctx, e);
	}
}

将原来收到和回写的对象转化成response对象

Server项目

新建Server项目,将之前server代码拷贝过来

Server.java

package com.server;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

import com.cn.codc.RequestDecoder;
import com.cn.codc.ResponseEncoder;
/**
 * netty服务端入门
 *
 */
public class Server {

	public static void main(String[] args) {

		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		
		//boss线程监听端口,worker线程负责数据读写
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		
		//设置niosocket工厂
		bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
		
		//设置管道的工厂
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {
	
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("decoder", new RequestDecoder());
				pipeline.addLast("encoder", new ResponseEncoder());
				pipeline.addLast("helloHandler", new HelloHandler());
				return pipeline;
			}
		});
		
		bootstrap.bind(new InetSocketAddress(10101));
		
		System.out.println("start!!!");
	
	}

}

主要变化

			ChannelPipeline pipeline = Channels.pipeline();
			//对request解码
				pipeline.addLast("decoder", new RequestDecoder());
				//response编码
				pipeline.addLast("encoder", new ResponseEncoder());
				pipeline.addLast("helloHandler", new HelloHandler());
				return pipeline;

HiHandler.java

package com.server;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

import com.cn.model.Request;
import com.cn.model.Response;
import com.cn.model.StateCode;
import com.cn.module.fuben.request.FightRequest;
import com.cn.module.fuben.response.FightResponse;
/**
 * 消息接受处理类
 
 *
 */
public class HelloHandler extends SimpleChannelHandler {

	/**
	 * 接收消息
	 */
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {

		Request message = (Request)e.getMessage();
		
		if(message.getModule() == 1){
			
			if(message.getCmd() == 1){
				
				FightRequest fightRequest = new FightRequest();
				fightRequest.readFromBytes(message.getData());
				//需要打的副本id是xxx打了xxx次
				System.out.println("fubenId:" +fightRequest.getFubenId() + "   " + "count:" + fightRequest.getCount());
				
				//回写数据
				FightResponse fightResponse = new FightResponse();
				fightResponse.setGold(9999);
				
				Response response = new Response();
				response.setModule((short) 1);
				response.setCmd((short) 1);
				response.setStateCode(StateCode.SUCCESS);
				response.setData(fightResponse.getBytes());
				ctx.getChannel().write(response);
			}else if(message.getCmd() == 2){
				
			}
		
		}else if (message.getModule() == 1){
			
			
		}
	}

	/**
	 * 捕获异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		System.out.println("exceptionCaught");
		super.exceptionCaught(ctx, e);
	}

	/**
	 * 新连接
	 */
	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelConnected");
		super.channelConnected(ctx, e);
	}

	/**
	 * 必须是链接已经建立,关闭通道的时候才会触发
	 */
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelDisconnected");
		super.channelDisconnected(ctx, e);
	}

	/**
	 * channel关闭的时候触发
	 */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelClosed");
		super.channelClosed(ctx, e);
	}
}

将收到的数据强转为requset

结果:

运行:
client端

请输入:
101
10
//要打101副本,打十次

server端

fubenID:101 count:10

client端

gold:999

弊端:

每次都要做判断

if(message.getModule() == 1){
			
			if(message.getCmd() == 1){
}

转载于:https://www.cnblogs.com/LeesinDong/p/10835354.html

你可能感兴趣的:(基于Netty的RPC架构学习笔记(十):自定义数据包协议)