Серия Netty: пользовательский кодек

Java задняя часть Netty
Серия Netty: пользовательский кодек

Это 14-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления

Введение

В предыдущей серии статей о netty мы говорили о том, как преобразовать объект или строку в ByteBuf. Используя кодировщик и декодер, поставляемые с netty, можно выполнить очень удобное преобразование между объектом и ByteBuf, а затем может свободно писать в канал в объекты и строки.

Хорошо использовать кодировщик, поставляемый с netty, но если у вас есть особые потребности, такие как преобразование данных во время кодирования или выбор полей объектов, вам может потребоваться настроить кодек.

пользовательский кодировщик

Пользовательские кодировщики должны наследовать MessageToByteEncoder.class и реализовать метод encode, в котором написана конкретная логика кодирования.

В этом примере мы хотим вычислить степень N числа 2. Говорят, что, сложив лист бумаги 100 раз, можно достичь высоты Земли до Луны.Обычное число таких больших данных не должно вмещать , мы будем использовать BigInteger, чтобы справиться с сохранением огромных объемов данных.

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

Здесь мы используем структуру данных из трех частей для представления BigInteger. Первая часть это волшебное слово, которое является волшебным словом.Здесь мы используем волшебное слово «N».Когда это волшебное слово читается, это означает, что следующее число BigInteger. Вторая часть — это длина массива байтов, представляющего число bigInteger.После получения этого значения длины вы можете прочитать все значения массива байтов и, наконец, преобразовать их в BigInteger.

Поскольку BigInteger является подклассом Number, чтобы обобщить кодировщик, мы используем Number как общий тип MessageToByteEncoder.Основной код кодирования выглядит следующим образом:

 protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) {
        // 将number编码成为ByteBuf
        BigInteger v;
        if (msg instanceof BigInteger) {
            v = (BigInteger) msg;
        } else {
            v = new BigInteger(String.valueOf(msg));
        }

        // 将BigInteger转换成为byte[]数组
        byte[] data = v.toByteArray();
        int dataLength = data.length;

        // 将Number进行编码
        out.writeByte((byte) 'N'); // 魔法词
        out.writeInt(dataLength);  // 数组长度
        out.writeBytes(data);      // 最终的数据
    }

пользовательский кодек

С закодированным массивом байтов его можно декодировать в декодере.

Как было показано в предыдущем разделе, формат закодированных данных — это волшебное слово N + длина массива + реальные данные.

Длина магического слова — один байт, длина массива — четыре байта, а начальная часть — всего 5 байт. Поэтому при декодировании сначала определите, меньше ли длина читаемых байтов в ByteBuf 5. Если она меньше 5, данные недействительны, и вы можете напрямую вернуться.

Если длина читаемого байта больше 5, это означает, что данные достоверны и их можно декодировать.

Следует отметить в процессе декодирования, что не все данные находятся в том формате, который мы хотим, Если мы читаем формат, который мы не знаем в процессе чтения, это означает, что данные не то, что мы хотим, и мы можем представить это обрабатывается другими обработчиками.

Но для ByteBuf после вызова метода чтения индекс считывателя перемещается, поэтому перед фактическим чтением данных необходимо вызвать метод markReaderIndex класса ByteBuf для записи считывателяIndex. Затем прочитайте волшебное слово, длину массива и оставшиеся данные отдельно и, наконец, преобразуйте данные в BigInteger следующим образом:

 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        // 保证魔法词和数组长度有效
        if (in.readableBytes() < 5) {
            return;
        }
        in.markReaderIndex();
        // 检查魔法词
        int magicNumber = in.readUnsignedByte();
        if (magicNumber != 'N') {
            in.resetReaderIndex();
            throw new CorruptedFrameException("无效的魔法词: " + magicNumber);
        }
        // 读取所有的数据
        int dataLength = in.readInt();
        if (in.readableBytes() < dataLength) {
            in.resetReaderIndex();
            return;
        }
        // 将剩下的数据转换成为BigInteger
        byte[] decoded = new byte[dataLength];
        in.readBytes(decoded);
        out.add(new BigInteger(decoded));
    }

Добавить кодек в конвейер

При двух кодеках его тоже нужно добавить в пайплайн для вызова.

В реализации initChannel в ChannelInitializer ChannelPipeline может быть инициализирован Код инициализации в этом примере выглядит следующим образом:

// 对流进行压缩
        pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
        pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));

        // 添加number编码解码器
        pipeline.addLast(new NumberDecoder());
        pipeline.addLast(new NumberEncoder());

        // 添加业务处理逻辑
        pipeline.addLast(new CustomProtocolServerHandler());

Последняя строка — это реальная логика бизнес-процессинга, а NumberDecoder и NumberEncoder — кодирование и декодирование. Здесь мы также используем ZlibEncoder для сжатия потоковых данных, здесь используется метод сжатия GZIP.

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

Вычислить 2 в N-й степени

Логика вычисления N-й степени 2 следующая: сначала клиент отправляет на сервер 2. Сервер получает сообщение и умножает результат на 1, и записывает результат обратно клиенту.Клиент отправляет сообщение после получения сообщение 2 на сервер, сервер умножает результат последнего вычисления на 2 и отправляет его клиенту, и так далее, пока оно не будет выполнено N раз.

Сначала посмотрите на логику отправки клиента:

// 最大计算2的1000次方
        ChannelFuture future = null;
        for (int i = 0; i < 1000 && next <= CustomProtocolClient.COUNT; i++) {
            future = ctx.write(2);
            next++;
        }

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

Для сервера в методе channelRead0 прочитать сообщение, умножить его на результат и записать результат обратно клиенту.

    public void channelRead0(ChannelHandlerContext ctx, BigInteger msg) throws Exception {
        // 将接收到的msg乘以2,然后返回给客户端
        count++;
        result = result.multiply(msg);
        ctx.writeAndFlush(result);
    }

Клиент подсчитывает количество прочитанных сообщений.Если количество сообщений = COUNT, это означает, что расчет завершен, и результат можно сохранить для последующего использования.Код ядра выглядит следующим образом:

    public void channelRead0(ChannelHandlerContext ctx, final BigInteger msg) {
        receivedMessages ++;
        if (receivedMessages == CustomProtocolClient.COUNT) {
            // 计算完毕,将结果放入answer中
            ctx.channel().close().addListener(future -> {
                boolean offered = answer.offer(msg);
                assert offered;
            });
        }
    }

Суммировать

В этой статье реализован кодек Number, на самом деле вы можете настроить кодек для любого объекта.

Примеры этой статьи могут относиться к:learn-netty4

Эта статья была включена вWoohoo. Floyd Press.com/13-Netty-Rough…

Самая популярная интерпретация, самая глубокая галантерея, самые краткие уроки и множество трюков, о которых вы не знаете, ждут вас!

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: «Программируйте эти вещи», разбирайтесь в технологиях, лучше поймите себя!