Чтение сокета Android (Java) зависло при чтении с сервера Netty Nio

Я реализовал сервер Netty с таким конвейером:

 public void initChannel(SocketChannel ch) throws Exception {
     ChannelPipeline p = ch.pipeline();
     p.addLast(new ByteArrayDecoder(), new Byte2MsgHandler()
      , new ByteArrayEncoder(), new Msg2ByteHandler(), new MsgDispatcher());
 }

Бизнес-логика находится в MsgDispatcher(). Смотри ниже:

public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    super.channelRead(ctx, msg);
    Msg message = (Msg) msg;
    switch (message.messageType) {
        case MType.SIGN_UP://sends response to the client
            if (userValidator.validate(message.user)) {
                userReg.signUp(message.user);
            } else {
                Msg error = new Msg.Builder().messageType(MType.ERROR)
                        .errorCode(Constants.USERREG_NULL).build();
                ctx.write(error.toByteArray());// write the response back
                ctx.flush(); //flush the stream
            }
            break;
    }
}

Я могу успешно отправить данные на сервер с помощью следующего кода из Android:

public void writeAndFlush(Msg msg) throws IOException {
    try {
        bos.write(msg.toByteArray());
        bos.flush();
    } catch (IOException e) {
        throw new IOException("Exception writing and flushing Msg to network.", e);
    }
}

bos — это буферизованный поток вывода из сокета.

Когда я закрываю serversocket после записи, я могу успешно прочитать данные.

Msg error = new Msg.Builder().messageType(MType.ERROR)
                        .errorCode(Constants.USERREG_NULL).build();
                ctx.write(error.toByteArray());// write the response back
                ctx.flush(); //flush the stream
                ctx.close(); //this result in sucessfull read on android socket.

Но (без «ctx.close() в приведенном выше коде сервера») он зависает в цикле while (код ниже):

public Msg read() throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int read = 0;
    try {
        while ((read = bis.read(buffer, 0, buffer.length)) != -1) {
            Log.e("com.traderreg", "" + read);
            baos.write(buffer, 0, read);
        }
        baos.flush();
    } catch (IOException e) {
        throw new IOException("error reading bytes from socket", e);
    }
    try {
        return wire.parseFrom(baos.toByteArray(), Msg.class);
    }catch (IOException e){
        throw new IOException("error parsing Msg from socket", e);
    }
}

В чем проблема?


person sfabriece    schedule 18.06.2014    source источник
comment
Итак, bis.read() не возвращается. Вы просите прочитать 1024 байта. Но есть меньше. Лучше сначала отправить целое число, которое указывает длину следующего сообщения. Сначала прочитайте это int, чтобы знать, сколько байтов вы можете запросить для чтения read().   -  person greenapps    schedule 19.06.2014
comment
@greenapps Кажется, вы предполагаете, что чтение не вернется, пока не будут доступны все запрошенные данные. Если это так, вы ошибаетесь. Он блокируется только тогда, когда нет данных.   -  person user207421    schedule 19.06.2014
comment
Это тот или иной способ рассказать историю. Во всяком случае, я предложил решение. Клиенту будет проще, если сервер сначала скажет, сколько байтов нужно прочитать.   -  person greenapps    schedule 19.06.2014
comment
Нет ли способа прочитать и вернуться, когда больше нет байтов для чтения?   -  person sfabriece    schedule 19.06.2014
comment
@greenapps Я понятия не имею, что означает «тот или иной способ рассказать историю», но вы неправильно определили проблему, не говоря уже о том, чтобы предложить решение. read() будет блокироваться только в том случае, если данных нет. Следовательно, данных нет. Длина сообщения не имеет к этому никакого отношения.   -  person user207421    schedule 20.06.2014


Ответы (1)


bis.read() будет блокироваться до тех пор, пока не будут доступны байты или поток не будет закрыт. Вот почему закрытие сокета приводит к возврату read(). Если вы хотите читать неблокирующим образом, вам нужно взглянуть на классы в пакете java.nio.channels. Однако на самом деле вы не хотите постоянно запрашивать данные. Классы в java.nio.channels действительно предназначены для обработки нескольких сокетов в одном потоке, а не для поощрения опроса сокета (хотя я знаю, что игры иногда опрашивают сокет как часть своего игрового цикла).

Независимо от того, работаете ли вы с блокирующим или неблокирующим вводом-выводом, вам нужен какой-то метод разграничения сообщений. Предложение добавлять перед каждым сообщением поле длины — простое, но эффективное решение. Затем ваш поток непрерывно читает сообщения и отправляет их коду, который может их обработать.

person johnstlr    schedule 20.06.2014
comment
Не будет ли фрагментации, если я отправлю сначала длину, а затем сообщение? Вот как мне отделить байты, принадлежащие длине, от байтов, принадлежащих сообщению? - person sfabriece; 21.06.2014
comment
Да, велика вероятность, что будет фрагментация. Самый простой вариант — сделать поле длины фиксированным размером, скажем, 2 или 4 байта (в зависимости от максимального размера сообщения). Таким образом, вы знаете, сколько байтов вам нужно прочитать, чтобы прочитать длину сообщения. Более сложной альтернативой является расширяемое целое число, где первые 7 битов байта представляют собой значение от 0 до 127, а верхний бит указывает, расширяется ли целое число в следующий байт. TBH Я бы просто сначала заставил его работать с полем длины фиксированного размера и беспокоился о других сценариях, если размер поля длины действительно является проблемой. - person johnstlr; 24.06.2014