Java socket通信,数据包的格式 : 数据长度+类型+数据的方式
下面几种错误情况该怎么处理呢?1)收到第一个数据包不是一个完整的(前面数据丢失),数据长度取得值是错误的,之后收信数据包长度也都是错误的,一直错了。
怎么能判断出数据长度是错误的,这个包是个不完整的包呢? 怎么把第一个不完整数据包丢掉,第二包正确收信呢?2)假设送信的数据包是100byte,但是送信时丢了中间2byte(假设),这时我期待的长度还是100,把下包的数据接收到了2byte。导致之后数据又都乱了,发生上面1)的问题。3)client阻塞在readUTF,如果这时服务器端关机,或者网线拔掉,或者socket被close,client还阻塞在readUTF吗?怎么能立即知道socket已经断了这件事呢?
4)writeUTF前,server端socket已经close了,怎么在发送前知道通信不可呢?
下面几种错误情况该怎么处理呢?1)收到第一个数据包不是一个完整的(前面数据丢失),数据长度取得值是错误的,之后收信数据包长度也都是错误的,一直错了。
怎么能判断出数据长度是错误的,这个包是个不完整的包呢? 怎么把第一个不完整数据包丢掉,第二包正确收信呢?2)假设送信的数据包是100byte,但是送信时丢了中间2byte(假设),这时我期待的长度还是100,把下包的数据接收到了2byte。导致之后数据又都乱了,发生上面1)的问题。3)client阻塞在readUTF,如果这时服务器端关机,或者网线拔掉,或者socket被close,client还阻塞在readUTF吗?怎么能立即知道socket已经断了这件事呢?
4)writeUTF前,server端socket已经close了,怎么在发送前知道通信不可呢?
byte[] lengthData = new byte[4];
int readCount = 0;
// 读取消息头
while (readCount < lengthData.length) {
readCount += inputStream.read(lengthData, readCount, lengthData.length - readCount);
if (readCount == -1) {
log.info("服务端断开1!");
return;
}
}2、你的问题3和4是想知道是否连接正常。通常我会发送心跳包数据,可以自定义一个心跳包类型的数据,每隔多长时间发送一次,比如10s,如果说20s或者30s都没有收到此类数据,则判定断线,将socket关闭,如果是客户端此时可考虑断线重连。(其他的通过异常能处理到的那些是可以判定的)
2 同1 不会发生
3 client read的时候,是去读网络层。服务器断线,网络层不能立即知道,直到超时才知道;服务器主动断开,客户端掉线,网络层可以立即知道,向client报错
4 tcp 4次握手才关闭,正常的关闭,网络层收到server端信息,会向应用层通知连接不可用。网络,服务器侧异常,网络层需要到达超时时间才知道
对于断包,好像是有个什么字节大小的限制,发送太大的包可能会被分片发送,具体是什么不记得了,抱歉
总之对于tcp,如果要清晰消息边界,最可靠的是自定义协议格式
此番回复,如有不当,还望指正
你这个格式不好搞, 如果要处理粘包问题, 必须要加规约开始标志:
比如: 国家的 104规约, 开始处就是68开头的标志.
因此规约格式为: [规约标志 + 数据长度+类型+数据的方式] 才好处理, 否则你的长度与数据会分不清, 不知道是不是开始.
看我这个报文格式与解释例子:------------- 冻结数据请求报文分析-------------
索引:00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
报文:68 66 00 66 00 68 47 05 01 2f 00 68 00 06 2f 00 00 00 00 00 0c 06 13 00 00 00 10 0c 06 13 02 75 16
0-1, 共1字节, 起始字符(1 字节,68H) 68
1-3, 共2字节, 长度L(2 字节,只取D2-D15,D1-D0补10后值是0066) 66 00
3-5, 共2字节, 长度L2(重复,2 字节,只取D2-D15) 66 00
5-6, 共2字节, 起始字符(重复,1 字节,68H) 686-7, 共1字节, 控制域C(1 字节,47H) 47
7-11, 共4字节, 地址域A(4 字节) 05 01 2f 00
11-12,共1字节, 应用服务数据单元-类型标识(1 字节,64H) 68
12-13,共1字节, 应用服务数据单元-可变结构限定词(VSQ,1字节,00H) 00
13-14,共1字节, 应用服务数据单元-传送原因(1 字节,06H) 06
14-16,共2字节, 应用服务数据单元-公共地址(2 字节) 2f 0016-23,共7字节, 应用服务数据单元-元公共描述-开始时间( 7 字节) 00 00 00 00 0c 06 13
23-30,共7字节, 应用服务数据单元-元公共描述-结束时间( 7 字节) 00 00 00 10 0c 06 13
30-31,共1字节, 应用服务数据单元-元公共描述-冻结周期( 1 字节) 0231-32,len-2,共1字节, 校验和CS(1 字节,12->0C) 75
32-33,len-1,共1字节, 结束字符(1 字节,16H) 16
1,读前面 4字节数据, 如果标志不是 EB 90 EB 90 就是错误的数据, 直接丢掉,
2,读长度值, 知道长度之后, 就读后面的数据, 必须达到长度, 如果长度不够则放入缓冲中, 等下次读取数据与缓冲合并.
3,根据长度读完之后, 数据还有多, 就判断后面是不是 EB 90 EB 90, 是则就是下一段报文开始(粘了别的包开头了), 放入缓冲中等下次再处理.
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;import com.test.util.StaticConvert;/**
* 数据接收线程
*/
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(1000);
//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) {
//最少要4字节才处理
if(bufCache.size() <4) {
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 = StaticConvert.parseByte2HexStr(begByte, 4);//(代码自己写)
//a,报文以EB90EB90开头
if("EB90EB90".equals(hexStr1)) {
//数据前面的报文内容必须有规定的长度才能处理: 报文头+长度+类型 = 4+2+2=8
byte[] headByte = new byte[8];
if(buflen >=8) {
bufIn2.read(headByte, 0, 8);
//取长度数据(代码自己写)
datalen = StaticConvert.getBJXNYLenBy2ByteL(new byte[] {headByte[1],headByte[2]} );
//1) 刚好一包数据
if(datalen >0 && buflen == datalen){
hisfra =false;
//处理一包数据....
//2) 一包数据有多余
}else if(datalen >0 && buflen > datalen){
byte[] dataBytes = new byte[datalen];
//合并已读出的字节
System.arraycopy(headByte, 0, dataBytes, 0, 6);
//本报文其它字节
bufIn2.read(dataBytes, 6, datalen - headByte.length);
//处理一包数据....
//余下的字节: 如果是EB90EB90开头, 则放在缓存, 否则认为是无效数据.
byte[] bytes2 = new byte[bufIn2.available()];
tmplen = bufIn2.read(bytes2);
begByte = new byte[] {bytes2[0], bytes2[1], bytes2[2], bytes2[3]} ;
hexStr1 = StaticConvert.parseByte2HexStr(begByte, 4);//(代码自己写)
if("EB90EB90".equals(hexStr1)) {
bufCache.write(bytes2, 0, tmplen);
}else {
hisfra =false;
hexStr1 = StaticConvert.parseByte2HexStr(bytes2, tmplen);//(代码自己写)
hexStr2 = StaticConvert.parsePrintHexStr(hexStr1);//(代码自己写)
logger.info(fn+", 剩余无效报文: " + hexStr2);
}
}else {
hisfra =false;
//可变报文长度不够,则放入缓冲
bufCache.write(bufbyte, 0, buflen);
}
}else {
//可变报文长度不够,则放入缓冲
hisfra =false;
bufCache.write(bufbyte, 0, buflen);
}
//c,无效数据
}else {
hisfra =false;
hexStr1 = StaticConvert.parseByte2HexStr(bufbyte, buflen);//(代码自己写)
hexStr2 = StaticConvert.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;
}
}