É bom você ter método que lêem uma quantia garantida de bytes. Eu implementei um método assim na minha aplicação, deixa eu te mostrar:
/**
* Reads a given number of bytes. The thread will block until the given
* number of bytes are read or an I/O error occur.
*
* @param bytes Number of bytes to read
* @return A flipped bytebuffer containing the bytes read.
* @throws IOException If some I/O error occurs.
*/
private ByteBuffer blockRead(int bytes) throws IOException
{
ByteBuffer msg = ByteBuffer.allocate(bytes);
int readBytes = 0;
while (readBytes < bytes)
{
int readCount = channel.read(dst);
if (readCount == -1) // Server disconnected?
{
Log.getInstance().severe(getClass(), "blockRead(int bytes)",
"Connection closed by server!");
disconnect();
return null;
}
readBytes += readCount;
}
dst.flip();
return dst;
}
Para adaptar para sua, talvez seja necessário mudar o código que está dentro da parte que ele testa se o servidor desconectou. No meu caso, isso não deveria ser um comportamento adequado, por isso o cliente simplesmente loga isso e chama o método disconnect(), que faz com que ele feche o próprio socket. A thread que processa as mensagens também está programada para parar caso o blockRead retorne null.
Aí você pode fazer:
ByteBuffer msg = blockRead(msg, size);
Note que isso já lê a mensagem inteira num buffer, e já deixa esse buffer preparado para ser trabalhado. Você poderia criar uma pequena classe, para enfileirar essa mensagem + seu tipo numa fila. Ou mesmo já chamar um método para decodificar isso num evento mais amigável.
Lembre-se. O número de reads nem sempre é o número de writes. Veja o seu código, você faz:
nBytes = clientChannel.read(buf);
mas considere que nBytes seja 2. Significa que ele leu apenas o short que estaria vindo, mas o int ainda não chegou. E isso pode acontecer, mesmo que vc tenha feito um único “write” no buffer do outro lado, com os dois valores. Portanto, você deveria esperar mais bytes. Quando vc tenta decodificar o int, dá um BufferOverflowException, pois você tentou ler dados que ainda não foram colocados no buffer.
O que o método “blockRead” faz, é justamente esperar um número fixo de bytes, não mais, não menos. Por isso ele leva em consideração esse valor de retorno, e aguarda os bytes até que o valor chegue, certinho.
Para esperar o cabeçalho de 6 bytes (um short + um int) você faz:
ByteBuffer header = blockRead(6);
Esse método garante que 6 bytes foram lidos. E vai bloquear a thread até que os 6 estejam no buffer. Em seguida, vc decodifica esse cabeçalho e faz um outro blockread, mas dessa vez para o conteúdo da mensagem.
Você deve entender que o read lê no máximo o tamanho do buffer, mas não dá garantias quanto ao mínimo. É nessa hora que o protocolo é importante. Só através dele você sabe quantos bytes devem ser lidos. 6 no caso do seu cabeçalho, size no caso dos demais dados.