Java SocketChannel ест мои байты

Я создал SocketChannel на удаленном сервере для отправки и получения сообщений на Tomcat. Для получения сообщений с удаленного компьютера я использовал поток, посвященный задаче (только этот поток будет читать из сокета, ничего больше).

Когда некоторые байты получены в SocketChannel (я продолжаю опрашивать SocketChannel в неблокирующем режиме для получения новых данных), я сначала читаю 4 байта, чтобы получить длину следующего сообщения, затем выделяю и считываю x байтов из SocketChannel, что составляет затем декодируется и реконструируется в сообщение.

Ниже приведен мой код для принимающего потока:

@Override
public void run() {

    while (true) { //Don't exit thread

        //Attempt to read the size of the incoming message
        ByteBuffer buf = ByteBuffer.allocate(4);

        int bytesread = 0;
        try {
            while (buf.remaining() > 0) {
                bytesread = schannel.read(buf);

                if (bytesread == -1) { //Socket was terminated

                } 

                if (quitthread) break;
            }

        } catch (IOException ex) {

        }

        if (buf.remaining() == 0) {
            //Read the header
            byte[] header = buf.array();
            int msgsize = (0xFF & (int)header[0]) + ((0xFF & (int)header[1]) << 8)
                    + ((0xFF & (int)header[2]) << 16) + ((0xFF & (int)header[3]) << 24);

            //Read the message coming from the pipeline
            buf = ByteBuffer.allocate(msgsize);
            try {
                while (buf.remaining() > 0) {
                    bytesread = schannel.read(buf);

                    if (bytesread == -1) { //Socket was terminated

                    }

                    if (quitthread) break;
                }
            } catch (IOException ex) {

            }

            parent.recvMessage(buf.array());
        }

        if (quitthread) {
            break;
        }
    }

}

Первые байты, которые я получил от SocketChannel, в порядке, и я успешно расшифровал сообщение. Однако в следующий раз, когда я читал из SocketChannel, сокет пропустил примерно 100 байтов вперед, что привело к чтению и интерпретации неправильных байтов как длины, что привело к повреждению всего.

Что не так с кодом? Ни один другой поток не читает из SocketChannel.


person futureelite7    schedule 16.11.2009    source источник
comment
Вместо того, чтобы преобразовывать буфер заголовка в массив из 4 байтов, вы можете просто вызвать для него getInt(), чтобы прочитать длину. Если вы хотите рассматривать это как целое число без знака, вы всегда можете привести к длинному и побитовому И с 0xFFFFFFFF. Также,   -  person Adamski    schedule 16.11.2009
comment
Я, вероятно, могу, хотя я не был уверен, что java будет читать arr[4] byte[] как прямой порядок байтов. В любом случае, я сомневаюсь, что это является причиной проблемы. Длина первого сообщения правильная, и я прочитал это количество байтов. Проверка прочитанного сообщения также не выявила ошибок. Есть только проблема, когда я пытаюсь прочитать следующие 4 байта для заголовка.   -  person futureelite7    schedule 16.11.2009


Ответы (2)


Ваши скобки выключены, код:

(0xFF & ((int)header[1] << 8))

который всегда равен 0 (то же самое с ‹‹ 16 и ‹‹ 24), я думаю, вы имели в виду:

((0xFF & ((int)header[1])) << 8)

Это приведет к чтению недостаточного количества байтов сообщения, а также к несоответствию синхронизации (в отличие от считывания слишком большого количества байтов).

Редактировать: теперь вы исправили вышеуказанное, я не вижу ничего плохого. Не могли бы вы рассказать нам о соотношении между длиной первого сообщения и точным количеством съеденных байтов?

Основываясь на показанном коде, я могу предположить, что вы отредактировали часть поведения из показанного примера, что может повлиять на schannel. Упоминается ли schannel где-либо еще?

Если строка:

ByteBuffer buf = ByteBuffer.allocate(4);

будет за пределами while, что приведет к описанному вами поведению, но в вашем примере кода это не так.

person rsp    schedule 16.11.2009
comment
Похоже, проблема с расшифровкой заголовка. Теперь он работает нормально. - person futureelite7; 17.11.2009

Я полагаю, когда вы говорите, что опрашиваете сокет в неблокирующем режиме, вы имеете в виду, что используете «стандартный» подход Selector.select()?

Когда select возвращается и указывает, что данные доступны для чтения из сокета, вы должны прочитать только те байты, которые доступны перед повторным вводом вызова select(). Если read() возвращает -1, это указывает на то, что в буфере больше нет байтов, доступных для немедленного чтения. Это не означает, что сокет был закрыт. Следовательно, я подозреваю, что ваша попытка полностью заполнить буфер перед возвратом неверна. Даже если это сработает, ваш поток ввода-вывода будет постоянно вращаться, пока поступают данные. В частности, похоже, что вы просто игнорируете возвращаемое значение -1.

Рассмотрите возможность изменения архитектуры кода для использования подхода конечного автомата. Например, я реализовал это в прошлом, используя модель с тремя состояниями: IDLE, READ_MESSAGE_LENGTH и READ_MESSAGE.

person Adamski    schedule 16.11.2009
comment
Поскольку у меня есть только один сокет на поток, я не знаю, нужно ли использовать Selector.Select(). В любом случае -1 означает, что достигнут конец потока = следует уведомить родителя. Я просто оставил обработку на данный момент. Я попробую использовать простой Socket() и посмотрю, как это пойдет. - person futureelite7; 17.11.2009