在网上很多新手在开发java TCP Socket 程序时, 都不知道怎么处理,
特别是在数据包粘在一起的时候, 不知道怎么取到完整的数据包, 也不知道从什么地方开始取数据
在Socket开发中最基本工作是: 确定双方交互的报文规范
本方的规范是:
报文开头: EB90EB90
报文格式: 报文头(4byte) + 数据长度(2byte) +类型(2byte) +数据(Nbyte);
报文头长 = 4 + 2 + 2 =8byte
报文长度 = 4 + 2 + 2 + N
报文列子: EB 90 EB 90 00 1A 00 01 05 01 2F 00 68 00 07 2F 00 00 00 00 00 0C 06 13 00 00 00 10 0C 06 13 02 75 16
数据长度为 26, 报文长度: 26 + 8 =34
代码如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 数据接收线程
* 报文开头: EB90EB90
* 报文格式: 报文头(4byte) + 数据长度(2byte) +类型(2byte) +数据(Nbyte);
* 报文头长 = 4 + 2 + 2 =8byte
* 报文长度 = 4 + 2 + 2 + N
* 报文列子: EB 90 EB 90 00 1A 00 01 05 01 2F 00 68 00 07 2F 00 00 00 00 00 0C 06 13 00 00 00 10 0C 06 13 02 75 16
* 数据长度为 26, 报文长度: 26 + 8 =34
*/
public class ReceiveThread implements Runnable {
private Log logger = LogFactory.getLog(this.getClass());
private DataInputStream inStm = null;
private Socket socket;
private String fn = "数据接收";
public ReceiveThread(Socket socket, DataInputStream inStm) {
this.socket = socket;
this.inStm = inStm;
}
public void run() {
logger.info(fn+", 启动接收线程.");
boolean b =true;
while (b) {
try {
//处理前先等几毫秒
Thread.sleep(500);
//1 连接是否正常
if(socket.isConnected()) {
ByteArrayInputStream bufIn2 = null;
ByteArrayOutputStream bufCache = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
byte[] bufbyte = null;
int netlen = -1;
int buflen = -1;
int tmplen = -1;
int datalen = -1;
byte[] begByte = null;
String hexStr1 = "";
String hexStr2 = "";
while ((netlen = inStm.read(bytes)) != -1) {
//合并上次一缓存数据 + 本次读取的数据
bufCache.write(bytes, 0, netlen);
//收到数据可能0包,1包,N包数据
boolean hisfra =true;
while (hisfra) {
//最少要8字节才开始处理
if(bufCache.size() <8) {
hisfra =false;
continue;
}
//取出上次缓存,然后清空缓存待用
buflen = bufCache.size();
bufbyte = bufCache.toByteArray();
bufCache.reset();
bufCache = null;
bufCache = new ByteArrayOutputStream();
//放入读取流中处理
bufIn2 = new ByteArrayInputStream(bufbyte, 0, buflen);
//取报文开头4字节内容
begByte = new byte[] {bufbyte[0], bufbyte[1], bufbyte[2], bufbyte[3]} ;
hexStr1 = this.parseByte2HexStr(begByte, 4);
//a,报文以EB90EB90开头
if("EB90EB90".equals(hexStr1)) {
byte[] headByte = new byte[8];
if(buflen >=8) {
bufIn2.read(headByte, 0, 8);
//报文长度 = 数据长度 +8
datalen = this.byte2ToIntB(new byte[] {headByte[4], headByte[5]} ) + 8;
//1) 刚好一包数据
if(datalen >0 && buflen == datalen){
hisfra =false;
//处理一包数据
this.processData(bufbyte);
//2) 一包数据有多余
}else if(datalen >0 && buflen > datalen){
//此包完整数据,为datalen长
byte[] dataBytes = new byte[datalen];
//copy已读出的8字节 到 dataBytes
System.arraycopy(headByte, 0, dataBytes, 0, 8);
//读本报文其它字节
bufIn2.read(dataBytes, 8, datalen - headByte.length);
//处理一包数据
this.processData(dataBytes);
//余下的所有字节bytes2: 如果是EB90EB90开头, 则放在缓存, 否则认为是无效数据.
byte[] bytes2 = new byte[bufIn2.available()];
tmplen = bufIn2.read(bytes2);
begByte = new byte[] {bytes2[0], bytes2[1], bytes2[2], bytes2[3]} ;
hexStr1 = this.parseByte2HexStr(begByte, 4);
if("EB90EB90".equals(hexStr1)) {
bufCache.write(bytes2, 0, tmplen);
}else {
hisfra =false;
hexStr1 = this.parseByte2HexStr(bytes2, tmplen);
hexStr2 = this.parsePrintHexStr(hexStr1);
logger.info(fn+", 剩余无效报文: " + hexStr2);
}
//3) 不够一包
}else {
hisfra =false;
//可变报文长度不够,则放入缓冲
bufCache.write(bufbyte, 0, buflen);
}
}else {
//可变报文长度不够,则放入缓冲
hisfra =false;
bufCache.write(bufbyte, 0, buflen);
}
//c,无效数据
}else {
hisfra =false;
hexStr1 = this.parseByte2HexStr(bufbyte, buflen);
hexStr2 = this.parsePrintHexStr(hexStr1);
logger.info(fn+", 收到无效报文: " + hexStr2);
}
//while
bufIn2.reset();
bufIn2 = null;
bufbyte = null;
}
}
}else {
b = false;
logger.info(fn+", 接收线程结束.");
}
} catch (IOException e) {
//
logger.error(fn+", 接收报文,网络异常.", e);
} catch (Exception e) {
logger.error(fn+", 接收线程异常.", e);
} finally {
}
}
socket = null;
inStm = null;
}
//-----------------------------------tools----------------------------
/**
* 处理完整的数据包,调用自己的业务代码
*/
public void processData(byte[] byteData) {
String hexStr2 = parseByte2HexStr(byteData, byteData.length);
String hexStr3 = parsePrintHexStr(hexStr2);
System.out.println("收到的报文: "+hexStr3);
//下面调用自己的代码处理......
}
/**
* 2个字节长度,(高字节在前)
*/
public int byte2ToIntB(byte b[]) {
int s = 0;
byte b0 = 0;
byte b1 = 0;
s = ((((b0 & 0xff) << 24 | (b1 & 0xff)) << 16) | (b[0] & 0xff)) << 8| (b[1] & 0xff);
return s;
}
/**
* 将byte转换成16进制,16进制格式,如: 0102162A
*/
public String parseByte2HexStr(byte[] byteBuf, int len) {
if (byteBuf == null) return null;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < len; i++) {
String hex = Integer.toHexString(byteBuf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 将16进制转换为byte,16进制格式,如: 0102162A
*/
public byte[] parseHexStr2Byte(String hexStr) {
if (hexStr == null) return null;
hexStr = hexStr.trim();
if (hexStr.length() < 1) return null;
if (hexStr.length() == 1) {
hexStr = '0' + hexStr;
}
if(hexStr.length() % 2 !=0) {
logger.info("输入的16进制数据错误,数据少一个字符: "+hexStr);
return null;
}
int len = hexStr.length();
int len2 = len / 2;
byte[] result = new byte[len2];
try {
for (int i = 0; i < len2; i++) {
String hex = hexStr.substring(i * 2, i * 2 +2);
result[i] = (byte)(Integer.valueOf(hex, 16) & 0xFF);
}
}catch (Exception e) {
result = null;
logger.error(" 将16进制转换为字节异常: "+hexStr, e);
} finally {
}
return result;
}
/**
* 输出打印格式的16进制,用空格分开,格式: 0102162A 输出: 01 02 16 2A
*/
public String parsePrintHexStr(String hexStr) {
if (hexStr == null) return null;
hexStr = hexStr.trim();
if (hexStr.length() < 1) return null;
if (hexStr.length() == 1) {
hexStr = '0' + hexStr;
}
if(hexStr.length() % 2 !=0) {
logger.info("输入的16进制数据错误,数据少一个字符: "+hexStr);
return null;
}
StringBuffer sb = new StringBuffer();
int len = hexStr.length();
for (int i = 0; i < len; i=i+2) {
String hex = hexStr.substring(i, i+2);
hex = hex +" ";
sb.append(hex.toUpperCase());
}
return sb.toString();
}
public static void main(String[] args) {
ReceiveThread tr = new ReceiveThread(null,null);
Integer len =0;
String hexStr = "0068";//00 68 两字节长度
System.out.println("原始16进制:"+hexStr);
byte[] byteBuf = tr.parseHexStr2Byte(hexStr);
String hexStr2 = tr.parseByte2HexStr(byteBuf, byteBuf.length);
System.out.println("二次转换后:"+hexStr2);
String hexStr3 = tr.parsePrintHexStr(hexStr2);
System.out.println("打印格式:"+hexStr3);
len = tr.byte2ToIntB(byteBuf);
System.out.println("长度: "+hexStr+" -> "+len);
}
}