在網上很多新手在開發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);
}
}