diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/pom.xml b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/pom.xml new file mode 100644 index 00000000..5904c2d1 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/pom.xml @@ -0,0 +1,56 @@ + + + + lab-67-netty-demo + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-67-netty-demo-client + + + + 2.2.4.RELEASE + + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-parent + ${spring.boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + io.netty + netty-all + 4.1.50.Final + + + + + cn.iocoder.springboot.labs + lab-67-netty-demo-common + 1.0-SNAPSHOT + + + + diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/NettyClientApplication.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/NettyClientApplication.java new file mode 100644 index 00000000..13d4254c --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/NettyClientApplication.java @@ -0,0 +1,4 @@ +package cn.iocoder.springboot.lab67.nettyclientdemo; + +public class NettyClientApplication { +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/config/NettyClientConfig.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/config/NettyClientConfig.java new file mode 100644 index 00000000..54a83d0e --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-client/src/main/java/cn/iocoder/springboot/lab67/nettyclientdemo/config/NettyClientConfig.java @@ -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(); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/pom.xml b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/pom.xml new file mode 100644 index 00000000..503b3b40 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/pom.xml @@ -0,0 +1,55 @@ + + + + lab-67-netty-demo + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-67-netty-demo-common + + + + 1.8 + 1.8 + + + + + + io.netty + netty-all + 4.1.50.Final + + + + + com.alibaba + fastjson + 1.2.71 + + + + + org.springframework + spring-aop + 5.2.5.RELEASE + + + org.springframework + spring-context + 5.2.5.RELEASE + + + + + org.slf4j + slf4j-api + 1.7.30 + + + + diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/Invocation.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/Invocation.java new file mode 100644 index 00000000..537be159 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/Invocation.java @@ -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; + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationDecoder.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationDecoder.java new file mode 100644 index 00000000..a097f746 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationDecoder.java @@ -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 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); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationEncoder.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationEncoder.java new file mode 100644 index 00000000..b69216f5 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/codec/InvocationEncoder.java @@ -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 { + + @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); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/Message.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/Message.java new file mode 100644 index 00000000..7ee0f6b4 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/Message.java @@ -0,0 +1,4 @@ +package cn.iocoder.springboot.lab67.nettycommondemo.dispacher; + +public interface Message { +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageDispatcher.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageDispatcher.java new file mode 100644 index 00000000..28776af0 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageDispatcher.java @@ -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 { + + @Autowired + private MessageHandlerContainer messageHandlerContainer; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Invocation invocation) { + // 获得 type 对应的 MessageHandler 处理器 + MessageHandler messageHandler = messageHandlerContainer.getMessageHandler(invocation.getType()); + // 解析消息 + Class messageClass = MessageHandlerContainer.getMessageClass(messageHandler); + Message message = JSON.parseObject(invocation.getMessage(), messageClass); + // 执行逻辑 + // noinspection unchecked + messageHandler.execute(ctx.channel(), message); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandler.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandler.java new file mode 100644 index 00000000..c70bb69d --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandler.java @@ -0,0 +1,20 @@ +package cn.iocoder.springboot.lab67.nettycommondemo.dispacher; + +import io.netty.channel.Channel; + +public interface MessageHandler { + + /** + * 执行处理消息 + * + * @param channel 通道 + * @param message 消息 + */ + void execute(Channel channel, T message); + + /** + * @return 消息类型,即每个 Message 实现类上的 TYPE 静态字段 + */ + String getType(); + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandlerContainer.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandlerContainer.java new file mode 100644 index 00000000..42467601 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-common/src/main/java/cn/iocoder/springboot/lab67/nettycommondemo/dispacher/MessageHandlerContainer.java @@ -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 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 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) actualTypeArguments[0]; + } else { + throw new IllegalStateException(String.format("类型(%s) 获得不到消息类型", handler)); + } + } + } + } + } + throw new IllegalStateException(String.format("类型(%s) 获得不到消息类型", handler)); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/pom.xml b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/pom.xml new file mode 100644 index 00000000..f5b6f308 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/pom.xml @@ -0,0 +1,56 @@ + + + + lab-67-netty-demo + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-67-netty-demo-server + + + + 2.2.4.RELEASE + + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-parent + ${spring.boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter + + + + + io.netty + netty-all + 4.1.50.Final + + + + + cn.iocoder.springboot.labs + lab-67-netty-demo-common + 1.0-SNAPSHOT + + + + diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/NettyServerApplication.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/NettyServerApplication.java new file mode 100644 index 00000000..eae612c7 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/NettyServerApplication.java @@ -0,0 +1,13 @@ +package cn.iocoder.springboot.lab67.nettyserverdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class NettyServerApplication { + + public static void main(String[] args) { + SpringApplication.run(NettyServerApplication.class, args); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/config/NettyServerConfig.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/config/NettyServerConfig.java new file mode 100644 index 00000000..7db3f2ca --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/config/NettyServerConfig.java @@ -0,0 +1,21 @@ +package cn.iocoder.springboot.lab67.nettyserverdemo.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 NettyServerConfig { + + @Bean + public MessageDispatcher messageDispatcher() { + return new MessageDispatcher(); + } + + @Bean + public MessageHandlerContainer messageHandlerContainer() { + return new MessageHandlerContainer(); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServer.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServer.java new file mode 100644 index 00000000..b1c2449f --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServer.java @@ -0,0 +1,80 @@ +package cn.iocoder.springboot.lab67.nettyserverdemo.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.net.InetSocketAddress; + +@Component +public class NettyServer { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Value("${netty.port}") + private Integer port; + + @Autowired + private NettyServerHandlerInitializer nettyServerHandlerInitializer; + + /** + * boss 线程组,用于服务端接受客户端的连接 + */ + private EventLoopGroup bossGroup = new NioEventLoopGroup(); + /** + * worker 线程组,用于服务端接受客户端的数据读写 + */ + private EventLoopGroup workerGroup = new NioEventLoopGroup(); + /** + * Netty Server Channel + */ + private Channel channel; + + /** + * 启动 Netty Server + */ + @PostConstruct + public void start() throws InterruptedException { + // 创建 ServerBootstrap 对象,用于 Netty Server 启动 + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) // 设置两个 EventLoopGroup 对象 + .channel(NioServerSocketChannel.class) // 指定 Channel 为服务端 NioServerSocketChannel + .localAddress(new InetSocketAddress(port)) // 设置 Netty Server 的端口 + .option(ChannelOption.SO_BACKLOG, 1024) // 服务端 accept 队列的大小 + .childOption(ChannelOption.SO_KEEPALIVE, true) // TCP Keepalive 机制,实现 TCP 层级的心跳保活功能 + .childOption(ChannelOption.TCP_NODELAY, true) // 允许较小的数据包的发送,降低延迟 + .childHandler(nettyServerHandlerInitializer); + // 绑定端口,并同步等待成功,即启动服务端 + ChannelFuture future = bootstrap.bind().sync(); + if (future.isSuccess()) { + channel = future.channel(); + logger.info("[start][Netty Server 启动在 {} 端口]", port); + } + } + + /** + * 关闭 Netty Server + */ + @PreDestroy + public void shutdown() { + // 关闭 Netty Server + if (channel != null) { + channel.close(); + } + // 优雅关闭两个 EventLoopGroup 对象 + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServerHandlerInitializer.java b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServerHandlerInitializer.java new file mode 100644 index 00000000..b7a374e4 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/java/cn/iocoder/springboot/lab67/nettyserverdemo/server/NettyServerHandlerInitializer.java @@ -0,0 +1,31 @@ +package cn.iocoder.springboot.lab67.nettyserverdemo.server; + +import cn.iocoder.springboot.lab67.nettycommondemo.codec.InvocationDecoder; +import cn.iocoder.springboot.lab67.nettycommondemo.codec.InvocationEncoder; +import cn.iocoder.springboot.lab67.nettycommondemo.dispacher.MessageDispatcher; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class NettyServerHandlerInitializer extends ChannelInitializer { + + @Autowired + private MessageDispatcher messageDispatcher; + + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + // 空闲检测 +// .addLast(new ServerIdleStateHandler()) + // 编码器 + .addLast(new InvocationEncoder()) + // 解码器 + .addLast(new InvocationDecoder()) + // 服务端处理器 + .addLast(messageDispatcher) + ; + } + +} diff --git a/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/resources/application.yml b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/resources/application.yml new file mode 100644 index 00000000..26ad4057 --- /dev/null +++ b/lab-67/lab-67-netty-demo/lab-67-netty-demo-server/src/main/resources/application.yml @@ -0,0 +1,2 @@ +netty: + port: 8888 # Netty Server 端口 diff --git a/lab-67/lab-67-netty-demo/pom.xml b/lab-67/lab-67-netty-demo/pom.xml new file mode 100644 index 00000000..63d830bf --- /dev/null +++ b/lab-67/lab-67-netty-demo/pom.xml @@ -0,0 +1,21 @@ + + + + lab-67 + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-67-netty-demo + pom + + lab-67-netty-demo-server + lab-67-netty-demo-client + lab-67-netty-demo-common + + + + diff --git a/lab-67/pom.xml b/lab-67/pom.xml new file mode 100644 index 00000000..b27593c1 --- /dev/null +++ b/lab-67/pom.xml @@ -0,0 +1,19 @@ + + + + labs-parent + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-67 + pom + + lab-67-netty-demo + + + + diff --git a/pom.xml b/pom.xml index c89c0aa5..2ed43b88 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,7 @@ + lab-67