Netty案例介绍

Netty案例介绍教程

前面我们对 Netty 这边的理论知识进行了一些讲解,那么一切的理论都是为了实践来服务的,本章我们就介绍一下我们案例使用到的项目。

网络编程步骤

我们都知道,Netty 是用来网络通信的,那么网络编程的基本步骤如下:

01 网络应用步骤.png

需求分析

我们会以餐厅点餐系统为例子,有客人和餐厅两个角色,那么可人就相当于我们的 Netty 的 Client,餐厅就相当于 Netty 的 Server。具体如下:

02 需求分析.png

其实从上面的图我们就可以看到需求很简单,就是一请求一应答的方式。

数据结构定义

我们本案例就以 Json 格式作为数据结构进行信息交互,它包含请求头和请求体。它的具体设计如下图:

03 数据报文.png

由低向上,里面包含消息体和消息头信息,消息体和消息头组成了 Message,而 length 表示的是 message 的长度。length 和 message 就组成了我们的 frame。这样就组成了一个网络传输信息的数据结构。

请求头

  • version 版本号信息,比如我们比较熟悉的 http 协议,它就有版本号,它可以方便我们升级,方便我们对各种版本协议的兼容。
  • opCode 是 operation code 的缩写,它和 operation 相对应,对应具体的业务操作类型。比如点餐,设置权限等等功能。
  • streamid 就相当于我们的了解到 traceid 或者 messageid,表示对应的数据流 ID。

代码

我们可以创建一个 maven 工程,定义为 nettycase。它需要引入 maven 相关的 jar 包,和我们需要用到的一些工具包,具体引包如下:

<dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.39.Final</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>19.0</version> </dependency> </dependencies>

根据上面的数据结构定义,我们可以先定义 message 相关对象

MessageHeader

package io.netty.common; import lombok.Data; @Data public class MessageHeader { private int version = 1; private int opCode; private long streamId; }

MessageBody

package io.netty.common; public abstract class MessageBody { }

因为 messagebody 它是具体的业务处理数据,有请求数据和返回数据,而且各种业务逻辑传递的数据参数不一样,所以我们定义为抽象类,然后我们定义一个请求结构体和返回结构体:

请求结构体 Operation

package io.netty.common; public abstract class Operation extends MessageBody{ public abstract OperationResult execute(); }

返回结构体 OperationResult

package io.netty.common; import lombok.Data; @Data public abstract class OperationResult extends MessageBody{ }

Message

package io.netty.common; import io.netty.buffer.ByteBuf; import io.netty.util.JsonUtil; import lombok.Data; import java.nio.charset.Charset; /** * @author 嗨客网 * @description */ @Data public abstract class Message<T extends MessageBody> { private MessageHeader messageHeader; private T messageBody; public T getMessageBody() { return messageBody; } /** * 序列化 * * @param byteBuf */ public void encode(ByteBuf byteBuf) { byteBuf.writeInt(messageHeader.getVersion()); byteBuf.writeLong(messageHeader.getStreamId()); byteBuf.writeInt(messageHeader.getOpCode()); byteBuf.writeBytes(JsonUtil.toJson(messageBody).getBytes()); } public abstract Class<T> getMessageBodyDecodeClass(int opcode); /** * 反序列化 * * @param msg */ public void decode(ByteBuf msg) { int version = msg.readInt(); long streamId = msg.readLong(); int opCode = msg.readInt(); MessageHeader messageHeader = new MessageHeader(); messageHeader.setVersion(version); messageHeader.setOpCode(opCode); messageHeader.setStreamId(streamId); this.messageHeader = messageHeader; Class<T> bodyClazz = getMessageBodyDecodeClass(opCode); T body = JsonUtil.fromJson(msg.toString(Charset.forName("UTF-8")), bodyClazz); this.messageBody = body; } }

我们看到反序列化的时候,用到了 getMessageBodyDecodeClass 方法,它根据不同的业务类型获取到具体的业务数据对象。我们分别定义了 RequestMessage 和 ResponseMessage,具体代码如下:

RequestMessage

package io.netty.common; public class RequestMessage extends Message<Operation>{ @Override public Class getMessageBodyDecodeClass(int opcode) { return OperationType.fromOpCode(opcode).getOperationClazz(); } public RequestMessage(){} public RequestMessage(Long streamId, Operation operation){ MessageHeader messageHeader = new MessageHeader(); messageHeader.setStreamId(streamId); messageHeader.setOpCode(OperationType.fromOperation(operation).getOpCode()); this.setMessageHeader(messageHeader); this.setMessageBody(operation); } }

ResponseMessage

package io.netty.common; public class ResponseMessage extends Message <OperationResult>{ @Override public Class getMessageBodyDecodeClass(int opcode) { return OperationType.fromOpCode(opcode).getOperationResultClazz(); } }

OperationType 代码如下

package io.netty.common; import io.netty.common.order.OrderOperation; import io.netty.common.order.OrderOperationResult; import java.util.function.Predicate; public enum OperationType { ORDER(3, OrderOperation.class, OrderOperationResult.class); private int opCode; private Class<? extends Operation> operationClazz; private Class<? extends OperationResult> operationResultClazz; OperationType(int opCode, Class<? extends Operation> operationClazz, Class<? extends OperationResult> responseClass) { this.opCode = opCode; this.operationClazz = operationClazz; this.operationResultClazz = responseClass; } public int getOpCode() { return opCode; } public Class<? extends Operation> getOperationClazz() { return operationClazz; } public Class<? extends OperationResult> getOperationResultClazz() { return operationResultClazz; } public static OperationType fromOpCode(int type) { return getOperationType(requestType -> requestType.opCode == type); } public static OperationType fromOperation(Operation operation) { return getOperationType(requestType -> requestType.operationClazz == operation.getClass()); } private static OperationType getOperationType(Predicate<OperationType> predicate) { OperationType[] values = values(); for (OperationType operationType : values) { if (predicate.test(operationType)) { return operationType; } } throw new AssertionError("no found type"); } }

具体的业务处理对象

OrderOperation

package io.netty.common.order; import io.netty.common.Operation; import lombok.Data; @Data public class OrderOperation extends Operation { private int tableId; private String dish; public OrderOperation(int tableId, String dish) { this.tableId = tableId; this.dish = dish; } @Override public OrderOperationResult execute() { System.out.println("order's executing startup with orderRequest: " + toString()); //execute order logic System.out.println("order's executing complete"); OrderOperationResult orderResponse = new OrderOperationResult(tableId, dish, true); return orderResponse; } }

OrderOperationResult

package io.netty.common.order; import io.netty.common.OperationResult; import lombok.Data; @Data public class OrderOperationResult extends OperationResult { private final int tableId; private final String dish; private final boolean complete; }

代码结构如下图:

04 代码结构图.png

Util相关代码

package io.netty.util; import java.util.concurrent.atomic.AtomicLong; //用来生成 streamid public final class IdUtil { private static final AtomicLong IDX = new AtomicLong(); private IdUtil(){ } public static long nextId(){ return IDX.incrementAndGet(); } }
package io.netty.util; import com.google.gson.Gson; public final class JsonUtil { private static final Gson GSON = new Gson(); private JsonUtil() { } public static <T> T fromJson(String jsonStr, Class<T> clazz){ return GSON.fromJson(jsonStr, clazz); } public static String toJson(Object object){ return GSON.toJson(object); } }

Netty案例介绍总结

本章节,我们对后面需要讲解对项目进行了大体了解,并且对数据结构进行了定义,根据数据结构,我们将代码的架构搭了起来,后面的流程会根据该架构进行完善。