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里,至于可能遇到的半包粘包问题,请看我半包粘包解决方法的文章