前面我们对 Netty 这边的理论知识进行了一些讲解,那么一切的理论都是为了实践来服务的,本章我们就介绍一下我们案例使用到的项目。
我们都知道,Netty 是用来网络通信的,那么网络编程的基本步骤如下:
我们会以餐厅点餐系统为例子,有客人和餐厅两个角色,那么可人就相当于我们的 Netty 的 Client,餐厅就相当于 Netty 的 Server。具体如下:
其实从上面的图我们就可以看到需求很简单,就是一请求一应答的方式。
我们本案例就以 Json 格式作为数据结构进行信息交互,它包含请求头和请求体。它的具体设计如下图:
由低向上,里面包含消息体和消息头信息,消息体和消息头组成了 Message,而 length 表示的是 message 的长度。length 和 message 就组成了我们的 frame。这样就组成了一个网络传输信息的数据结构。
我们可以创建一个 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 相关对象
package io.netty.common;
import lombok.Data;
@Data
public class MessageHeader {
private int version = 1;
private int opCode;
private long streamId;
}
package io.netty.common;
public abstract class MessageBody {
}
因为 messagebody 它是具体的业务处理数据,有请求数据和返回数据,而且各种业务逻辑传递的数据参数不一样,所以我们定义为抽象类,然后我们定义一个请求结构体和返回结构体:
package io.netty.common;
public abstract class Operation extends MessageBody{
public abstract OperationResult execute();
}
package io.netty.common;
import lombok.Data;
@Data
public abstract class OperationResult extends MessageBody{
}
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,具体代码如下:
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);
}
}
package io.netty.common;
public class ResponseMessage extends Message <OperationResult>{
@Override
public Class getMessageBodyDecodeClass(int opcode) {
return OperationType.fromOpCode(opcode).getOperationResultClazz();
}
}
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");
}
}
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;
}
}
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;
}
代码结构如下图:
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);
}
}
本章节,我们对后面需要讲解对项目进行了大体了解,并且对数据结构进行了定义,根据数据结构,我们将代码的架构搭了起来,后面的流程会根据该架构进行完善。