Socket 接收數據線程, 處理粘包問題, 解釋自定義報文數據

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章