因业务需求,需要完成一个TCP连接数据的解析与转发的插件。
最终,使用Socket进行TCP简单连接,逐步读取字节数据,解析成想要的数据类型,最后转发。
因为只是做一个Tcp连接的客户端,Socket建立使用最简单的方式:
Socket socket = new Socket(ip, port);
数据流读写方式,设计上要求插件一直等待字节数据,这里使用缓冲流能提高效率:
BufferedInputStream bi = new BufferedInputStream(socket.getInputStream());
public static Packet readPacket(BufferedInputStream in) throws IOException { // 阻塞等待指定长度的字节流, 这里首先等待一个完整包头的字节数据 while (in.available() < LEN_PKT_HEADER) ; // 1. 读取packet header byte[] head = new byte[LEN_PKT_HEADER]; in.read(head); // 2.解析packet header内容, getUShort是将字节数组转为无符号类型的short数据 int pktBodySize = getUShort(head[0], head[1]); // 3. 读取packet body if (pktBodySize <= 0) { // todo: 空数据包 return null; } // 阻塞等待指定长度的字节流 while (in.available() < pktBodySize) ; // 读取 byte[] pktBody = new byte[pktBodySize]; in.read(pktBody); return new Packet(0, 0, pktBody); }
这个插件中用到的协议字节格式是小端,而Java中的字节存储是大端。
需要注意的是,Java的基础数据类型是有符号的,而传入的字节流数据会包含无符号数据,重点在于最高位的bit处理,一般思路是用范围更大的有符号数据来存储无符号数据。
通过位运算来逐个字节取short类型中的bit数据,然后按照大小端顺序来拼成数组。
private static byte[] getBytes(short num, @Min(1) int len) { if (len == 1) { return new byte[]{(byte) (num & 0xFF)}; } else { return new byte[]{(byte) (num & 0xFF), (byte) ((num >> Byte.SIZE) & 0xFF)}; } }
public static int getUShort(byte... bytes) { int num = bytes[0] & 0xFF; if (bytes.length > 1) { num |= (bytes[1] & 0xFF) << Byte.SIZE; } return num; }
<LEN><TEXT>
,首先根据字节的前1位(两位,由协议本身约定)的字节,经过转成short类型得到TEXT部分的字节长度len,进而去转成String类型。String stockCode = new String(bytes, idx, len, StandardCharsets.UTF_8);
int LEN_LONG = Long.SIZE / Byte.SIZE; public static long getLong(byte[] bytes, int idx) { long res = bytes[idx + LEN_LONG - 1]; for (int i = idx + LEN_LONG - 2; i >= idx; i--) { res = (res << Byte.SIZE) | ((long) bytes[i] & 0xFFL); } return res; }
public static BigDecimal getULong(byte[] bytes, int idx) { // 7 * 8 = 2的56次方, 将ULong型数的最高位字节,从第一个字节还原所需的移动的位数 final BigDecimal towOf56 = BigDecimal.valueOf((long) 1 << ((LEN_LONG - 1) * Byte.SIZE)); // 1. 保存最高位 byte high = bytes[idx + LEN_LONG - 1]; // 2. 将最高位置0 bytes[idx + LEN_LONG - 1] = 0; // 3. 将除最高字节以外的byte数组转为有符号long long tmp = getLong(bytes, idx); BigDecimal num = BigDecimal.valueOf(tmp); // 4.将最高位字节转成对应的大小的数 BigDecimal highNum = BigDecimal.valueOf(Byte.toUnsignedLong(high)); // 乘以2的56次方,等同与将这个数表示的最高字节向左移动7个字节 highNum = highNum.multiply(towOf56); return num.add(highNum); }