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);
	 }
}

 

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