从java套接字读取字节:获取ArrayIndexOutOfBounds



我有这个非常奇怪的问题:我有一个小程序从套接字读取字节;每当我调试时,程序都运行良好;但每次我运行它(像直接运行它),我得到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字节的情况,以便您不要尝试处理未收到的字节。

最新更新