java nio 值对象发送协议

 java tcp通信时发送对象是最好的办法就是把对象按字段打包成字节流,用json和字符串都太占流量了。在游戏开发中,自己搭了一个解析和压缩数据的方法,能将数据类型(整型/长整型/浮点型/双精度浮点型/布尔类型/字符串/流对象/数组)打包成ByteBuffer。

1.定义通信数据类型

package com.socket.protocol;

/**
 * 通信类型(整型/长整型/浮点型/双精度浮点型/布尔类型/字符串/流对象/数组)
 */
public class StreamDataType {
 /**整型*/
 public final static byte TYPE_INT = 1;
 /**长整型*/
 public final static byte TYPE_LONG = 2;
 /**浮点型*/
 public final static byte TYPE_FLOAT = 3;
 /**双精度浮点型*/
 public final static byte TYPE_DOUBLE = 4;
 /**布尔类型*/
 public final static byte TYPE_BOOLEAN = 5;
 /**字符串*/
 public final static byte TYPE_STRING = 6;
 /**流对象*/
 public final static byte TYPE_STREAM_OBJECT = 7;
 /**数组*/
 public final static byte TYPE_LIST = 8;
}

2.定义通信协议异常类

package com.socket.protocol;

/**
 * 通信协议异常
 */
public class ProtocolException extends Exception{

 private static final long serialVersionUID = -3123459738703130675L;
 
 public ProtocolException(Throwable e){
  super(e);
 }
 
 public ProtocolException(String msg){
  super(msg);
 }
}

3.定义流对象接口

package com.socket.protocol;

import java.nio.ByteBuffer;

/**
 * 通信流对象
 */
public interface StreamObject {
 /**
  * 打包
  * @param bf
  * @return
  * @throws ProtocolException
  */
 public ByteBuffer encode(ByteBuffer bf) throws ProtocolException;
 
 /**
  * 解析
  * @param bf
  * @return
  * @throws ProtocolException
  */
 public StreamObject decode(ByteBuffer bf) throws ProtocolException;
}

 

4.由于java目前提供的ByteBuffer长度是不可变的,一旦给定了长度,在压缩数据的时候很可能出现ByteBuffer长度不够,所以我们需要创建一个ByteBuffer 工厂类ByteBufferFactory,在ByteBufferFactory中,我们需要指定一个ByteBuffer的增长策略,为增强可重用性,我们需要定义一个增长策略接口类ByteBufferIncreaseStrategy,

在创建新的ByteBuffer里,可能遇到特定的异常,我们再定义一个ByteBuffer异常类ByteBufferException。

4.1ByteBufferException异常类

package com.socket.protocol;

/**
 * ByteBuffer异常
 */
public class ByteBufferException extends Exception {
 private static final long serialVersionUID = -3123459738703130675L;
 
 public ByteBufferException(Throwable e){
  super(e);
 }
 
 public ByteBufferException(String msg){
  super(msg);
 }
}

 

4.2ByteBuffer 增长策略

package com.socket.protocol;

import java.nio.ByteBuffer;

/**
 * ByteBuffer 增长策略(和ByteBufferFactory共用)
 */
public interface ByteBufferIncreaseStrategy {
 public ByteBuffer increase(ByteBuffer bf, int need) throws ByteBufferException;
}

4.3在定义ByteBufferFactory时,我们需要考虑的是ByteBufferFactory里的增长策略要怎么植入,考虑到可扩展性,我用了xml配置文件,在ByteBufferFactory同级目录下创建一个ByteBufferFactory.xml,

<?xml version="1.0" encoding="UTF-8"?>
<factory>
 <property name="strategy" class="com.socket.protocol.ByteBufferDoubleStrategy" />
</factory>

class指定了增长策略为com.socket.protocol.ByteBufferDoubleStrategy

package com.socket.protocol;

import java.nio.ByteBuffer;

/**
 * ByteBuffer增长策略(双倍增长)
 */
public class ByteBufferDoubleStrategy implements ByteBufferIncreaseStrategy {

 @Override
 public ByteBuffer increase(ByteBuffer bf, int need) throws ByteBufferException{
  if(need <= bf.remaining()){
   return bf;
  }else{
   int len = 0;
   for(int i = 1; i <= need; i++){
    len = (int)(bf.capacity() * Math.pow(2, i));
    if(len >= bf.capacity() + need)
     break;
   }
   if(len < need){
    throw new ByteBufferException("ByteBuffer增长出错");
   }
   ByteBuffer dest = ByteBuffer.allocate(len);
   
   return dest;
  }
 }

}

 

/**
 * ByteBuffer 工厂类
 */
public class ByteBufferFactory {
 
 public static ByteBufferIncreaseStrategy strategy;
 
 static{
  String file = ByteBufferFactory.class.getResource("ByteBufferFactory.xml").getFile();
  try {
   DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
   Document formationDoc = parser.parse(file);
   NodeList list = formationDoc.getElementsByTagName("property");
   Element el = null;
   String field = null;
   String className = null;
   for(int index = 0; index < list.getLength(); index++){
    el = (Element)list.item(index);
    field = el.getAttribute("name");
    className = el.getAttribute("class");
    ByteBufferFactory.class.getField(field).set(ByteBufferFactory.class, Class.forName(className).newInstance());
   }
  } catch (Exception e) {
   e.printStackTrace();
   throw new RuntimeException(e);
  }
 }
 
 public static ByteBuffer getByteBuffer(int capacity){
  return ByteBuffer.allocate(capacity);
 }
 
 /**
  *
  * @param bf 旧的ByteBuffer
  * @param need 所需长度
  * @return
  * @throws ByteBufferException
  */
 public static ByteBuffer getByteBuffer(ByteBuffer bf, int need) throws ByteBufferException{
  ByteBuffer dest = strategy.increase(bf, need);
  if(dest != bf){
   bf.flip();
   while(bf.hasRemaining()){
    dest.put(bf.get());
   }
   bf.limit(bf.capacity());
  }
  return dest;
 }

}

5.到这里,我们需要定义压缩类Encoder和解析类Decoder

5.1Encoder压缩类

package com.socket.protocol;

import java.nio.ByteBuffer;
import java.util.List;

 

/**
 * 通信打包类
 */
public class Encoder {
 public static ByteBuffer encode(int value, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   dest = ByteBufferFactory.getByteBuffer(bf, 4);
  } catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  return dest.putInt(value);
 }
 
 public static ByteBuffer encode(long value, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   dest = ByteBufferFactory.getByteBuffer(bf, 8);
  } catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  return dest.putLong(value);
 }
 
 public static ByteBuffer encode(float value, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   dest = ByteBufferFactory.getByteBuffer(bf, 4);
  } catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  return dest.putFloat(value);
 }
 
 public static ByteBuffer encode(double value, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   dest = ByteBufferFactory.getByteBuffer(bf, 8);
  } catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  return dest.putDouble(value);
 }
 
 public static ByteBuffer encode(String str, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   if(str == null || str.length() == 0){
    dest= ByteBufferFactory.getByteBuffer(bf, 4);
    dest.putInt(0);
   }else{
    byte[] bytes;
    bytes = str.getBytes("UTF-8");
    dest = ByteBufferFactory.getByteBuffer(bf, bytes.length + 4);
    dest.putInt(bytes.length);
    dest.put(bytes);
    
   }
  } catch (Exception e) {
    throw new ProtocolException(e);
  }
  return dest;
 }
 
 public static ByteBuffer encode(boolean bool, ByteBuffer bf) throws ProtocolException{
  ByteBuffer dest;
  try {
   dest = ByteBufferFactory.getByteBuffer(bf, 1);
  } catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  byte b = 0;
  if(bool)
   b = 1;
  return dest.put(b);
 }
 
 public static ByteBuffer encode(StreamObject sb, ByteBuffer bf) throws ProtocolException{
  if(sb == null){
   ByteBuffer dest;
   try {
    dest = ByteBufferFactory.getByteBuffer(bf, 4);
    dest.putInt(0);
   } catch (ByteBufferException e) {
    throw new ProtocolException(e);
   }
   return dest;
  }
  return sb.encode(bf);
 }
 
 @SuppressWarnings("unchecked")
 public static ByteBuffer encode(List list, ByteBuffer bf, byte type) throws ProtocolException{
  ByteBuffer dest;
  try{
   if(list == null || list.size() == 0){
    dest = ByteBufferFactory.getByteBuffer(bf, 4);
    dest.putInt(0);
   }else{
    dest = ByteBufferFactory.getByteBuffer(bf, 5);
    dest.putInt(list.size());
    dest.put(type);
    switch(type){
     case StreamDataType.TYPE_INT:
      for(int i = 0; i < list.size(); i++){
       dest = encode(((Integer)list.get(i)).intValue(), dest);
      }
      break;
     case StreamDataType.TYPE_LONG:
      for(int i = 0; i < list.size(); i++){
       dest = encode(((Long)list.get(i)).longValue(), dest);
      }
      break;
     case StreamDataType.TYPE_FLOAT:
      for(int i = 0; i < list.size(); i++){
       dest = encode(((Float)list.get(i)).floatValue(), dest);
      }
      break;
     case StreamDataType.TYPE_DOUBLE:
      for(int i = 0; i < list.size(); i++){
       dest = encode(((Double)list.get(i)).doubleValue(), dest);
      }
      break;
     case StreamDataType.TYPE_BOOLEAN:
      for(int i = 0; i < list.size(); i++){
       dest = encode(((Boolean)list.get(i)).booleanValue(), dest);
      }
      break;
     case StreamDataType.TYPE_STREAM_OBJECT:
      for(int i = 0; i < list.size(); i++){
       dest = encode((StreamObject)list.get(i), dest);
      }
      break;
     default:
      throw new ProtocolException("找不到解析类型:[" + type + "]"); 
    }
   }
  }catch (ByteBufferException e) {
   throw new ProtocolException(e);
  }
  return dest;
 }
}

5.2Decoder通信解析类

package com.socket.protocol;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * 通信解析类
 */
public class Decoder {
 public static int getInt(ByteBuffer bf) throws ProtocolException{
  return bf.getInt();
 }
 public static long getLong(ByteBuffer bf) throws ProtocolException{
  return bf.getLong();
 }
 public static float getFloat(ByteBuffer bf) throws ProtocolException{
  return bf.getFloat();
 }
 public static double getDouble(ByteBuffer bf) throws ProtocolException{
  return bf.getDouble();
 }
 public static String getString(ByteBuffer bf) throws ProtocolException{
  int len = bf.getInt();
  if(len > 0){
   byte[] bytes = new byte[len];
   bf.get(bytes);
   try {
    return new String(bytes, "UTF-8");
   } catch (UnsupportedEncodingException e) {
    throw new ProtocolException(e);
   }
  }
  return null;
 }
 public static boolean getBoolean(ByteBuffer bf) throws ProtocolException{
  byte b = bf.get();
  if(b == 1)
   return true;
  else
   return false;
 }
 public static StreamObject getStreamObject(StreamObject sb, ByteBuffer bf) throws ProtocolException{
  return sb.decode(bf);
 }
 
 @SuppressWarnings("unchecked")
 public static <T>List<T> getList(ByteBuffer bf, Class<? extends StreamObject> c) throws ProtocolException{
  int len = bf.getInt();
  List list = new ArrayList();
  if(len > 0){
   byte type = bf.get();
   switch(type){
    case StreamDataType.TYPE_INT: //整型
     for(int i = 0; i < len; i++){
      list.add(getInt(bf));
     }
     break;
    case StreamDataType.TYPE_LONG: //长整型
     for(int i = 0; i < len; i++){
      list.add(getLong(bf));
     }
     break;
    case StreamDataType.TYPE_FLOAT: //浮点型
     for(int i = 0; i < len; i++){
      list.add(getFloat(bf));
     }
     break;
    case StreamDataType.TYPE_DOUBLE: //双精度浮点型
     for(int i = 0; i < len; i++){
      list.add(getDouble(bf));
     }
     break;
    case StreamDataType.TYPE_BOOLEAN: //布尔型
     for(int i = 0; i < len; i++){
      list.add(getBoolean(bf));
     }
     break;
    case StreamDataType.TYPE_STRING: //字符串
     for(int i = 0; i < len; i++){
      list.add(getString(bf));
     }
     break;
    case StreamDataType.TYPE_STREAM_OBJECT: //流对象
     try {
      for(int i = 0; i < len; i++){
       list.add(getStreamObject(c.newInstance(), bf));
      }
     }catch(Exception e){
      throw new ProtocolException(e);
     }
     break;
    default:
     throw new ProtocolException("找不到解析类型:[" + type + "]");
   }
  }
  return list;
 }
}

6.基本工具类写完,测试类值对象TestVO,该类中包含所有StreamDataType的所有数据类型

package com.game.vo;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.List;

import com.socket.protocol.StreamDataType;
import com.socket.protocol.Decoder;
import com.socket.protocol.Encoder;
import com.socket.protocol.ProtocolException;
import com.socket.protocol.StreamObject;

public class DataVO implements StreamObject {

 private int i;
 private long l;
 private float f;
 private double d;
 private boolean b;
 private String s;
 private List<DataVO> datas;
 
 @Override
 public StreamObject decode(ByteBuffer bf) throws ProtocolException {
  int len = bf.getInt();
  if(len > 0){
   i = Decoder.getInt(bf);
   l = (long)Decoder.getLong(bf);
   f = Decoder.getFloat(bf);
   d = Decoder.getDouble(bf);
   b = Decoder.getBoolean(bf);
   s = Decoder.getString(bf);
   datas = Decoder.getList(bf, DataVO.class);
   return this;
  }
  return null;
 }

 @Override
 public ByteBuffer encode(ByteBuffer bf) throws ProtocolException {
  int p1 = bf.position();
  ByteBuffer dest = Encoder.encode(0, bf);
  dest = Encoder.encode(i, dest);
  dest = Encoder.encode(l, dest);
  dest = Encoder.encode(f, dest);
  dest = Encoder.encode(d, dest);
  dest = Encoder.encode(b, dest);
  dest = Encoder.encode(s, dest);
  dest = Encoder.encode(datas, dest, StreamDataType.TYPE_STREAM_OBJECT);
  int p2 = dest.position();
  dest.position(p1);
  dest.putInt(p2 - p1);
  dest.position(p2);
  return dest;
 }

 public int getI() {
  return i;
 }

 public void setI(int i) {
  this.i = i;
 }

 public long getL() {
  return l;
 }

 public void setL(long l) {
  this.l = l;
 }

 public float getF() {
  return f;
 }

 public void setF(float f) {
  this.f = f;
 }

 public double getD() {
  return d;
 }

 public void setD(double d) {
  this.d = d;
 }

 public String getS() {
  return s;
 }

 public void setS(String s) {
  this.s = s;
 }

 public List<DataVO> getDatas() {
  return datas;
 }

 public void setDatas(List<DataVO> datas) {
  this.datas = datas;
 }

 public boolean isB() {
  return b;
 }

 public void setB(boolean b) {
  this.b = b;
 }

 public String toString(){
  StringBuffer sb = new StringBuffer();
  sb.append("{\n\t");
  Field[] fs = DataVO.class.getDeclaredFields();
  for(Field f : fs){
   if(sb.length() > 3)
    sb.append(",\n\t");
   sb.append(f.getName());
   sb.append(":");
   try {
    sb.append(f.get(this));
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   }
   
  }
  sb.append("\n}");
  return sb.toString();
 }
 
}

7.测试类

package com.game.test;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import com.game.vo.DataVO;

public class ProtocolTest {
 public static void main(String args[]) throws Throwable{
  DataVO src = new DataVO();
  src.setI(5);
  src.setL(10000000000000000l);
  src.setF(0.999f);
  src.setD(0.323d);
  src.setB(true);
  src.setS("测试");
  List<DataVO> list = new ArrayList<DataVO>();
  int size = 1;
  for(int i = 0; i < size; i++){
   DataVO d = new DataVO();
   d.setI((int)(Math.random() * 10));
   d.setL((long)(Math.random() * 100000));
   d.setF((float)Math.random());
   d.setD(Math.random());
   d.setB(Math.random() > 0.5);
   d.setS("测试" + Math.random());
   list.add(d);
  }
  src.setDatas(list);
  ByteBuffer bf = ByteBuffer.allocate(1);
  ByteBuffer dest = src.encode(bf);
  dest.flip();
  DataVO vo1 = new DataVO();
  vo1.decode(dest);
  System.out.println(vo1);
 }
}

以上基本可以进行常用的数据通信交互,一般通信的话会有两个通信类Request和Response,将这两个类打包成ByteBuffer,直接写到socket里,至于可能遇到的半包粘包问题,请看我半包粘包解决方法的文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值