我有这个非常奇怪的问题:我有一个小程序从套接字读取字节;每当我调试时,程序都运行良好;但每次我运行它(像直接运行它),我得到ArrayIndexOutOfBounds异常。到底发生了什么事?我读得太快了吗?我错过什么了吗?
这里是main():
public static void main(String[] args){
TParser p = new TParser();
p.init();
p.readPacket();
p.sendResponse();
p.readPacket();
p.sendResponse();
p.shutdown();
}
init方法是我创建读取和写入socket的地方;下一个方法(readPacket)是问题开始出现的地方;我把整个缓冲区读到一个私有字节数组,这样我就可以自由地操作数据;例如,根据数据的一些字节,我设置了一些属性:
public void readPacket(){
System.out.println("readPacket");
readInternalPacket();
setPacketInfo();
}
private void readInternalPacket(){
System.out.println("readInternalPacket");
try {
int available=dataIN.available();
packet= new byte[available];
dataIN.read(packet,0,available);
dataPacketSize=available;
}
catch (Exception e) {
e.printStackTrace();
}
}
private void setPacketInfo() {
System.out.println("setPacketInfo");
System.out.println("packetLen: " +dataPacketSize);
byte[] pkt= new byte[2];
pkt[0]= packet[0];
pkt[1]= packet[1];
String type= toHex(pkt);
System.out.println("packet type: "+type);
if(type.equalsIgnoreCase("000F")){
recordCount=0;
packetIterator=0;
packetType=Constants.PacketType.ACKPacket;
readIMEI();
validateDevice();
}
}
换行的地方是
pkt[1] =包[1];(setPacketInfo)
意味着它当时只有1个字节…但是,如果我调试它时,它运行得很好,这怎么可能呢?我必须在套接字上做一些完整性检查吗?(DataInputStream)
我应该把方法放在单独的线程上吗?我一遍又一遍地讨论这个问题,甚至更换了我的内存模块(当我开始对此有奇怪的想法时)…
我不知道周围的代码,特别是dataIN
类,但我认为你的代码是这样的:
int available=dataIN.available();
根本不等待数据,只是返回有0字节可用
数组的大小为0然后输入:
pkt[0]= packet[0]; pkt[1]= packet[1];
越界
我建议你至少循环,直到available()
返回2
你期望的,但我不能确定这是正确的(*)或正确的(**)的方式去做,因为我不知道dataIN
的类实现。
注意:(*)如果available()
可能单独返回2个字节,这是不正确的。(**)如果dataIN
本身提供了等待的方法,这不是正确的方法。
是否可以从套接字读取数据是一个异步过程,并且在数据包[]完全填充之前调用setPacketInfo() ?如果是这种情况,它可能在调试时运行得很好,但当它在不同的机器上真正使用套接字时就很糟糕了。
您可以在setPacketInfo()方法中添加一些代码来检查数据包[]变量的长度。
byte[] pkt= new byte[packet.length];
for(int x = 0; x < packet.length; x++)
{
pkt[x]= packet[x];
}
不太确定为什么要将包[]变量复制到pkt[]中?
您在面向流层上使用面向数据包的协议,而不传输实际数据包长度。由于碎片的存在,接收到的数据可能比发送的数据包要小。
因此,我强烈建议在发送实际数据包之前发送数据包大小。在接收端,您可以使用DataInputStream并使用阻塞读取来检测传入的数据包:
private void readInternalPacket() {
System.out.println("readInternalPacket");
try {
int packetSize = dataIN.readInt();
packet = new byte[packetSize];
dataIN.read(packet, 0, packetSize);
dataPacketSize = packetSize;
} catch (Exception e) {
e.printStackTrace();
}
}
当然你也必须修改发送端,在发送数据包之前先发送数据包大小
添加到@eznme的答案。您需要从底层流中读取数据,直到没有挂起的数据为止。这可能需要一次或多次读取,但是当available方法返回0时,将表示流结束。我建议使用Apache IOUtils将输入流"复制"到ByteArrayOutputStream,然后从中获取byte[]数组。
在你的setPacketInfo方法中,你应该在得到你的协议头字节之前检查你的数据缓冲区长度:
byte[] pkt= new byte[2];
if((packet != null) && (packet.length >= 2)) {
pkt[0]= packet[0];
pkt[1]= packet[1];
// ...
}
这将摆脱超出边界的异常,当你从你的协议读取零长度的数据缓冲区。
您永远不应该依赖dataIN.available()
, dataIN.read(packet,0,available);
返回一个整数,表示您收到了多少字节。这个值并不总是和available表示的值相同,它也可以小于缓冲区的大小。
你应该这样阅读:
byte[] packet = new byte[1024]; //
dataPacketSize = dataIN.read(packet,0,packet.length);
您还应该将DataInputStream
包装在BufferedInputStream
中,并注意您获得少于2字节的情况,以便您不要尝试处理未收到的字节。