Skip to content

Commit

Permalink
开始 Netty 入门示例
Browse files Browse the repository at this point in the history
  • Loading branch information
YunaiV committed Jun 18, 2020
1 parent 558c495 commit 0ba1edb
Show file tree
Hide file tree
Showing 20 changed files with 608 additions and 0 deletions.
56 changes: 56 additions & 0 deletions lab-67/lab-67-netty-demo/lab-67-netty-demo-client/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-67-netty-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-67-netty-demo-client</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Netty 依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>

<!-- 引入 netty-demo-common 封装 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>lab-67-netty-demo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cn.iocoder.springboot.lab67.nettyclientdemo;

public class NettyClientApplication {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cn.iocoder.springboot.lab67.nettyclientdemo.config;

import cn.iocoder.springboot.lab67.nettycommondemo.dispacher.MessageDispatcher;
import cn.iocoder.springboot.lab67.nettycommondemo.dispacher.MessageHandlerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class NettyClientConfig {

@Bean
public MessageDispatcher messageDispatcher() {
return new MessageDispatcher();
}

@Bean
public MessageHandlerContainer messageHandlerContainer() {
return new MessageHandlerContainer();
}

}
55 changes: 55 additions & 0 deletions lab-67/lab-67-netty-demo/lab-67-netty-demo-common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-67-netty-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-67-netty-demo-common</artifactId>

<properties>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencies>
<!-- Netty 依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>

<!-- FastJSON 依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.71</version>
</dependency>

<!-- 引入 Spring 相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

<!-- 引入 SLF4J 依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cn.iocoder.springboot.lab67.nettycommondemo.codec;

public class Invocation {

/**
* 类型 - 心跳请求
*/
public static final String TYPE_HEARTBEAT_REQUEST = "HEARTBEAT_REQUEST";
/**
* 类型 - 心跳响应
*/
public static final String TYPE_HEARTBEAT_RESPONSE = "HEARTBEAT_RESPONSE";

/**
* 类型
*/
private String type;
/**
* 消息
*/
private String message;

public String getType() {
return type;
}

public Invocation setType(String type) {
this.type = type;
return this;
}

public String getMessage() {
return message;
}

public Invocation setMessage(String message) {
this.message = message;
return this;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cn.iocoder.springboot.lab67.nettycommondemo.codec;

import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;

import java.util.List;

public class InvocationDecoder extends ByteToMessageDecoder {

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 标记当前读取位置
in.markReaderIndex();
// 判断是否能够读取 length 长度
if (in.readableBytes() <= 4) {
return;
}
// 读取长度
int length = in.readInt();
if (length < 0) {
throw new CorruptedFrameException("negative length: " + length);
}
// 如果 message 不够可读,则退回到原读取位置
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
// 读取内容
ByteBuf byteBuf = in.readRetainedSlice(length);
// 解析成 Invocation
byte[] content = byteBuf.array();
Invocation invocation = JSON.parseObject(content, Invocation.class);
out.add(invocation);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cn.iocoder.springboot.lab67.nettycommondemo.codec;

import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class InvocationEncoder extends MessageToByteEncoder<Invocation> {

@Override
protected void encode(ChannelHandlerContext ctx, Invocation invocation, ByteBuf out) {
// 将 Invocation 转换成 byte[] 数组
byte[] content = JSON.toJSONBytes(invocation);
// 写入 length
out.writeInt(content.length);
// 写入内容
out.writeBytes(content);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cn.iocoder.springboot.lab67.nettycommondemo.dispacher;

public interface Message {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cn.iocoder.springboot.lab67.nettycommondemo.dispacher;

import cn.iocoder.springboot.lab67.nettycommondemo.codec.Invocation;
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.springframework.beans.factory.annotation.Autowired;

@ChannelHandler.Sharable
public class MessageDispatcher extends SimpleChannelInboundHandler<Invocation> {

@Autowired
private MessageHandlerContainer messageHandlerContainer;

@Override
protected void channelRead0(ChannelHandlerContext ctx, Invocation invocation) {
// 获得 type 对应的 MessageHandler 处理器
MessageHandler messageHandler = messageHandlerContainer.getMessageHandler(invocation.getType());
// 解析消息
Class<? extends Message> messageClass = MessageHandlerContainer.getMessageClass(messageHandler);
Message message = JSON.parseObject(invocation.getMessage(), messageClass);
// 执行逻辑
// noinspection unchecked
messageHandler.execute(ctx.channel(), message);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cn.iocoder.springboot.lab67.nettycommondemo.dispacher;

import io.netty.channel.Channel;

public interface MessageHandler<T extends Message> {

/**
* 执行处理消息
*
* @param channel 通道
* @param message 消息
*/
void execute(Channel channel, T message);

/**
* @return 消息类型,即每个 Message 实现类上的 TYPE 静态字段
*/
String getType();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package cn.iocoder.springboot.lab67.nettycommondemo.dispacher;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class MessageHandlerContainer implements InitializingBean {

private Logger logger = LoggerFactory.getLogger(getClass());

/**
* 消息类型与 MessageHandler 的映射
*/
private final Map<String, MessageHandler> handlers = new HashMap<>();

@Autowired
private ApplicationContext applicationContext;

@Override
public void afterPropertiesSet() throws Exception {
// 通过 ApplicationContext 获得所有 MessageHandler Bean
applicationContext.getBeansOfType(MessageHandler.class).values() // 获得所有 MessageHandler Bean
.forEach(messageHandler -> handlers.put(messageHandler.getType(), messageHandler)); // 添加到 handlers 中
logger.info("[afterPropertiesSet][消息处理器数量:{}]", handlers.size());
}

protected MessageHandler getMessageHandler(String type) {
MessageHandler handler = handlers.get(type);
if (handler == null) {
throw new IllegalArgumentException(String.format("类型(%s) 找不到匹配的 MessageHandler 处理器", type));
}
return handler;
}

public static Class<? extends Message> getMessageClass(MessageHandler handler) {
// 获得 Bean 对应的 Class 类名。因为有可能被 AOP 代理过。
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(handler);
// 获得接口的 Type 数组
Type[] interfaces = targetClass.getGenericInterfaces();
Class<?> superclass = targetClass.getSuperclass();
while ((Objects.isNull(interfaces) || 0 == interfaces.length) && Objects.nonNull(superclass)) { // 此处,是以父类的接口为准
interfaces = superclass.getGenericInterfaces();
superclass = targetClass.getSuperclass();
}
if (Objects.nonNull(interfaces)) {
// 遍历 interfaces 数组
for (Type type : interfaces) {
// 要求 type 是泛型参数
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// 要求是 MessageHandler 接口
if (Objects.equals(parameterizedType.getRawType(), MessageHandler.class)) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 取首个元素
if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) {
return (Class<Message>) actualTypeArguments[0];
} else {
throw new IllegalStateException(String.format("类型(%s) 获得不到消息类型", handler));
}
}
}
}
}
throw new IllegalStateException(String.format("类型(%s) 获得不到消息类型", handler));
}

}
Loading

0 comments on commit 0ba1edb

Please sign in to comment.