diff --git a/JdkLearn/pom.xml b/JdkLearn/pom.xml index 4a18492..9fb74db 100644 --- a/JdkLearn/pom.xml +++ b/JdkLearn/pom.xml @@ -30,7 +30,6 @@ io.netty netty-all - 4.1.6.Final diff --git a/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java b/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java new file mode 100644 index 0000000..aca54da --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java @@ -0,0 +1,71 @@ +package com.learnjava.collection; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Spliterator; +import java.util.function.Consumer; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/6 + */ +public class LRUCache implements Iterable { + + private int MAX = 3; + private LinkedHashMap cache = new LinkedHashMap<>(); + + public void cache(K key, V value) { + if (cache.containsKey(key)) { + cache.remove(key); + } else if (cache.size() >= MAX) { + Iterator iterator = cache.keySet().iterator(); + K first = iterator.next(); + cache.remove(first); + } + cache.put(key, value); + } + + public V getValue(K k) { + return cache.get(k); + } + + @Override + public void forEach(Consumer action) { + Iterable.super.forEach(action); + } + + @Override + public Spliterator spliterator() { + return Iterable.super.spliterator(); + } + + @Override + public Iterator iterator() { + Iterator iterator = cache.keySet().iterator(); + return new Iterator() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + return iterator.next(); + } + }; + } + + public static void main(String[] args) { + LRUCache cache = new LRUCache<>(); + cache.cache("1", "1A"); + cache.cache("2", "2A"); + cache.cache("3", "3A"); + cache.cache("1", "1A"); + + for (String next : cache) { + System.out.println(cache.getValue(next)); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java new file mode 100644 index 0000000..0d04fb8 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java @@ -0,0 +1,9 @@ +package com.learnjava.concurent; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/7 + */ +public class ReentrantLockDemo { +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java index b07604d..384d980 100644 --- a/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java +++ b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java @@ -58,7 +58,7 @@ private SnowFlake() { // String ip = instance.getDockerIp().replace(".", ""); // 模拟获取机器节点ip String ip = "127.0.0.1"; - long localIp = Long.valueOf(ip.replace(".", "")); + long localIp = Long.parseLong(ip.replace(".", "")); machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT; } /** diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java new file mode 100644 index 0000000..4a89467 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java @@ -0,0 +1,9 @@ +package com.learnjava.concurent; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/7 + */ +public class SynchronizeDemo { +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java new file mode 100644 index 0000000..5d5b5fc --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java @@ -0,0 +1,48 @@ +package com.learnjava.concurent; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/24 + */ +public class ThreadPoolExecutorDemo { + + public static void main(String[] args) { + testThreadPoolExecutorBinaryCalc(); + } + + + /** + * 验证ThreadPoolExecutor中的二进制位运算操作 + */ + private static void testThreadPoolExecutorBinaryCalc() { +// System.out.println(ctl.get()); +// System.out.println(Integer.toBinaryString(ctlOf(RUNNING, 0))); +// System.out.println(Integer.toBinaryString(RUNNING)); + // 修改线程状态-STOP + System.out.println(Integer.toBinaryString(~runStateOf(ctlOf(STOP, 10)))); + // 修改线程状态-TERMINATED +// System.out.println(runStateOf(3)); +// System.out.println(Integer.toBinaryString(~CAPACITY)); + } + + private static final int COUNT_BITS = Integer.SIZE - 3; + + private static final int CAPACITY = (1 << COUNT_BITS) - 1; + + private static final int RUNNING = -1 << COUNT_BITS; + private static final int SHUTDOWN = 0 << COUNT_BITS; + private static final int STOP = 1 << COUNT_BITS; + private static final int TIDYING = 2 << COUNT_BITS; + private static final int TERMINATED = 3 << COUNT_BITS; + + private static AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); + + private static int runStateOf(int c) { return c & ~CAPACITY; } + + private static int workerCountOf(int c) { return c & CAPACITY; } + + private static int ctlOf(int rs, int wc) { return rs | wc; } +} diff --git a/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java b/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java index bbbb766..2e2ec2d 100644 --- a/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java +++ b/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java @@ -1,7 +1,5 @@ package com.learnjava.reference; -import com.sun.webkit.graphics.Ref; - import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; diff --git a/README.md b/README.md index 648b81e..fae4153 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Spring-Security-OAuth2 - Netty + Netty Netty @@ -74,6 +74,8 @@ SpringCloud源码 - [一篇文章快速深入学习ThreadLocal](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/JDK/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%BF%AB%E9%80%9F%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0ThreadLocal.md) - [深入学习Java volatile关键字](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/JDK/%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0Java%20volatile%E5%85%B3%E9%94%AE%E5%AD%97.md) - [深入学习Thread底层原理](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/JDK/%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0Thread%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81.md) + - [深入学习JDK1.7、8 HashMap扩容原理]() + - [开源项目里那些看不懂的位运算分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/JDK/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E9%87%8C%E9%82%A3%E4%BA%9B%E7%9C%8B%E4%B8%8D%E6%87%82%E7%9A%84%E4%BD%8D%E8%BF%90%E7%AE%97%E5%88%86%E6%9E%90.md) - Spring源码学习 - Spring版本:5.2.1.RELEASE @@ -96,7 +98,7 @@ SpringCloud源码 - [深入浅出SpringBoot源码——SpringFactoriesLoader](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94SpringFactoriesLoader.md) - [深入浅出SpringBoot源码——监听器与事件机制](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%9B%91%E5%90%AC%E5%99%A8%E4%B8%8E%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6.md) - - [深入浅出SpringBoot源码——系统初始化器](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%B3%BB%E7%BB%9F.md) + - [深入浅出SpringBoot源码——系统初始化器](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96%E5%99%A8.md) - [深入浅出SpringBoot源码——启动加载器](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E5%90%AF%E5%8A%A8%E5%8A%A0%E8%BD%BD%E5%99%A8.md) - SpringSecurity&OAuth2源码学习 @@ -121,19 +123,25 @@ SpringCloud源码 - Dubbo底层源码学习(七)—— Dubbo的服务消费 - Netty底层源码解析 - - Netty版本:4.1.60.Final + - Netty版本:4.1.43.Final - [二进制运算以及源码、反码以及补码学习](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/%E4%BA%8C%E8%BF%9B%E5%88%B6.md) - [Netty源码包结构](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E6%BA%90%E7%A0%81%E5%8C%85%E7%BB%93%E6%9E%84.md) - - [Netty中的EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md) + - [Netty底层源码解析-EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md) - [Netty底层源码解析-初始Netty及其架构](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-%E5%88%9D%E5%A7%8BNetty%E5%8F%8A%E5%85%B6%E6%9E%B6%E6%9E%84.md) - [Netty底层源码解析-Netty服务端启动分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-Netty%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%90%AF%E5%8A%A8%E5%88%86%E6%9E%90.md) - [Netty底层源码解析-NioEventLoop原理分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-NioEventLoop%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md) - - Netty底层源码解析-Channel分析 - [Netty底层源码解析-ChannelPipeline分析(上)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8A%EF%BC%89.md) - [Netty底层源码解析-ChannelPipeline分析(下)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8B%EF%BC%89.md) + - [Netty底层源码解析-NioServerSocketChannel接受数据原理分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-NioServerSocketChannel%E6%8E%A5%E5%8F%97%E6%95%B0%E6%8D%AE%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md) + - Netty底层源码解析-NioSocketChannel接受、发送数据原理分析 - Netty底层源码解析-FastThreadLocal原理分析 - Netty底层源码解析-内存分配原理分析 - Netty底层源码解析-RocketMQ底层使用到的Netty + - [Netty底层的优化总结]() + - [实战+原理效果更佳!强烈推荐闪电侠大佬实战课:《Netty 入门与实战:仿写微信 IM 即时通讯系统》](https://juejin.cn/book/6844733738119593991) + +Netty实战课相关点位于:Spring-Netty,com/bruis/learnnetty/im包下,有需要的读者可前往查看。 + - RocketMQ底层源码解析 - RocketMQ版本:4.9.0 @@ -145,7 +153,7 @@ SpringCloud源码 todo -2021年年底完成了人生的两件大事,所以一直没时间持续输出源码分析,2022年开始需要继续努力,完成这个源码分析项目! +2021年年底完成了人生的两件大事,所以一直没时间持续输出源码分析,2022年开始需要继续努力,继续完成这个源码分析项目! - 完成Netty剩余源码分析文章 - 完成RocketMQ剩余源码分析文章 diff --git a/Spring-Netty/learnnetty.iml b/Spring-Netty/learnnetty.iml deleted file mode 100644 index 8cc03a7..0000000 --- a/Spring-Netty/learnnetty.iml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Spring-Netty/pom.xml b/Spring-Netty/pom.xml index d640f68..82d7684 100644 --- a/Spring-Netty/pom.xml +++ b/Spring-Netty/pom.xml @@ -16,7 +16,6 @@ 1.8 - 4.1.6.Final @@ -35,7 +34,6 @@ io.netty netty-all - ${netty-all.version} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java new file mode 100644 index 0000000..4a701b4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java @@ -0,0 +1,106 @@ +package com.bruis.learnnetty.im.client; + +import com.bruis.learnnetty.im.client.handler.*; +import com.bruis.learnnetty.im.codec.PacketDecoder; +import com.bruis.learnnetty.im.codec.PacketEncoder; +import com.bruis.learnnetty.im.codec.Spliter; +import com.bruis.learnnetty.im.console.ConsoleCommandManager; +import com.bruis.learnnetty.im.console.LoginConsoleCommand; +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.Date; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; + +/** + * @Description 客户端 + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class NettyClient { + private static final int MAX_RETRY = 5; + private static final String HOST = "127.0.0.1"; + private static final int PORT = 8000; + + + public static void main(String[] args) { + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + + Bootstrap bootstrap = new Bootstrap(); + bootstrap + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + // 拆包粘包处理 + ch.pipeline().addLast(new Spliter()); + // 编码 + ch.pipeline().addLast(new PacketDecoder()); + // 登录响应 + ch.pipeline().addLast(new LoginResponseHandler()); + // 消息返回 + ch.pipeline().addLast(new MessageResponseHandler()); + ch.pipeline().addLast(new CreateGroupResponseHandler()); + ch.pipeline().addLast(new JoinGroupResponseHandler()); + ch.pipeline().addLast(new QuitGroupResponseHandler()); + ch.pipeline().addLast(new ListGroupMembersResponseHandler()); + ch.pipeline().addLast(new GroupMessageResponseHandler()); + ch.pipeline().addLast(new LogoutResponseHandler()); + // 解码 + ch.pipeline().addLast(new PacketEncoder()); + } + }); + + connect(bootstrap, HOST, PORT, MAX_RETRY); + } + + private static void connect(Bootstrap bootstrap, String host, int port, int retry) { + bootstrap.connect(host, port).addListener(future -> { + if (future.isSuccess()) { + System.out.println(new Date() + ": 连接成功,启动控制台线程……"); + Channel channel = ((ChannelFuture) future).channel(); + startConsoleThread(channel); + } else if (retry == 0) { + System.err.println("重试次数已用完,放弃连接!"); + } else { + // 第几次重连 + int order = (MAX_RETRY - retry) + 1; + // 本次重连的间隔 + int delay = 1 << order; + System.err.println(new Date() + ": 连接失败,第" + order + "次重连……"); + bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit + .SECONDS); + } + }); + } + + private static void startConsoleThread(Channel channel) { + ConsoleCommandManager consoleCommandManager = new ConsoleCommandManager(); + LoginConsoleCommand loginConsoleCommand = new LoginConsoleCommand(); + Scanner scanner = new Scanner(System.in); + + new Thread(() -> { + while (!Thread.interrupted()) { + if (!SessionUtil.hasLogin(channel)) { + loginConsoleCommand.exec(scanner, channel); + } else { + consoleCommandManager.exec(scanner, channel); + } + } + }).start(); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java new file mode 100644 index 0000000..04125de --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.CreateGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, CreateGroupResponsePacket msg) throws Exception { + System.out.print("群创建成功,id 为[" + msg.getGroupId() + "], "); + System.out.println("群里面有:" + msg.getUserNameList()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java new file mode 100644 index 0000000..3dc9921 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java @@ -0,0 +1,20 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.GroupMessageResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, GroupMessageResponsePacket responsePacket) { + String fromGroupId = responsePacket.getFromGroupId(); + Session fromUser = responsePacket.getFromUser(); + System.out.println("收到群[" + fromGroupId + "]中[" + fromUser + "]发来的消息:" + responsePacket.getMessage()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java new file mode 100644 index 0000000..cc7efea --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java @@ -0,0 +1,21 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.JoinGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, JoinGroupResponsePacket responsePacket) throws Exception { + if (responsePacket.isSuccess()) { + System.out.println("加入群[" + responsePacket.getGroupId() + "]成功!"); + } else { + System.err.println("加入群[" + responsePacket.getGroupId() + "]失败,原因为:" + responsePacket.getReason()); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java new file mode 100644 index 0000000..0117eac --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java @@ -0,0 +1,18 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.ListGroupMembersResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ListGroupMembersResponsePacket responsePacket) { + System.out.println("群[" + responsePacket.getGroupId() + "]中的人包括:" + responsePacket.getSessionList()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java new file mode 100644 index 0000000..282afcc --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java @@ -0,0 +1,33 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.LoginResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description 登录响应的reponse + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LoginResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LoginResponsePacket loginResponsePacket) throws Exception { + String userId = loginResponsePacket.getUserId(); + String userName = loginResponsePacket.getUserName(); + + if (loginResponsePacket.isSuccess()) { + System.out.println("[" + userName + "]登录成功,userId 为: " + loginResponsePacket.getUserId()); + SessionUtil.bindSession(new Session(userId, userName), ctx.channel()); + } else { + System.out.println("[" + userName + "]登录失败,原因:" + loginResponsePacket.getReason()); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + System.out.println("客户端连接被关闭"); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java new file mode 100644 index 0000000..78fd173 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.LogoutResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LogoutResponsePacket logoutResponsePacket) { + SessionUtil.unBindSession(ctx.channel()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java new file mode 100644 index 0000000..ea6fac1 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.MessageResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class MessageResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageResponsePacket messageResponsePacket) throws Exception { + String fromUserId = messageResponsePacket.getFromUserId(); + String fromUserName = messageResponsePacket.getFromUserName(); + System.out.println(fromUserId + ":" + fromUserName + " -> " + messageResponsePacket.getMessage()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java new file mode 100644 index 0000000..be82bd5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java @@ -0,0 +1,22 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.QuitGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, QuitGroupResponsePacket responsePacket) throws Exception { + if (responsePacket.isSuccess()) { + System.out.println("退出群聊[" + responsePacket.getGroupId() + "]成功!"); + } else { + System.out.println("退出群聊[" + responsePacket.getGroupId() + "]失败!"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java new file mode 100644 index 0000000..35aa573 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java @@ -0,0 +1,35 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.Packet; +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; + +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/25 + */ +@ChannelHandler.Sharable +public class PacketCodecHandler extends MessageToMessageCodec { + + public static final PacketCodecHandler INSTANCE = new PacketCodecHandler(); + + private PacketCodecHandler() {} + + @Override + protected void encode(ChannelHandlerContext ctx, Packet msg, List out) throws Exception { + ByteBuf byteBuf = ctx.channel().alloc().ioBuffer(); + PacketCodeC.INSTANCE.encode(byteBuf, msg); + out.add(byteBuf); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { + out.add(PacketCodeC.INSTANCE.decode(msg)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java new file mode 100644 index 0000000..751d007 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java @@ -0,0 +1,20 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class PacketDecoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List out) throws Exception { + out.add(PacketCodeC.INSTANCE.decode(byteBuf)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java new file mode 100644 index 0000000..d3d4fa7 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.Packet; +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class PacketEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf byteBuf) throws Exception { + PacketCodeC.INSTANCE.encode(byteBuf, packet); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java new file mode 100644 index 0000000..c09b096 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java @@ -0,0 +1,30 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +/** + * @Description 拆包、粘包处理 + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class Spliter extends LengthFieldBasedFrameDecoder { + private static final int LENGTH_FIELD_OFFSET = 7; + private static final int LENGTH_FIELD_LENGTH = 4; + + public Spliter() { + super(Integer.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + // 校验协议 + if (in.getInt(in.readerIndex()) != PacketCodeC.MAGIC_NUMBER) { + ctx.channel().close(); + return null; + } + return super.decode(ctx, in); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java new file mode 100644 index 0000000..dd41e27 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java @@ -0,0 +1,14 @@ +package com.bruis.learnnetty.im.console; + +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description 指令接口 + * @Author luohaiyang + * @Date 2022/3/23 + */ +public interface ConsoleCommand { + void exec(Scanner scanner, Channel channel); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java new file mode 100644 index 0000000..8bf69f6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java @@ -0,0 +1,43 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class ConsoleCommandManager implements ConsoleCommand { + + private Map consoleCommandMap; + + public ConsoleCommandManager() { + consoleCommandMap = new HashMap<>(); + consoleCommandMap.put("sendToUser", new SendToUserConsoleCommand()); + consoleCommandMap.put("logout", new LogoutConsoleCommand()); + consoleCommandMap.put("createGroup", new CreateGroupConsoleCommand()); + consoleCommandMap.put("joinGroup", new JoinGroupConsoleCommand()); + consoleCommandMap.put("quitGroup", new QuitGroupConsoleCommand()); + consoleCommandMap.put("listGroup", new ListGroupMembersConsoleCommand()); + consoleCommandMap.put("sendToGroup", new SendToGroupConsoleCommand()); + } + + @Override + public void exec(Scanner scanner, Channel channel) { + String command = scanner.next(); + if (!SessionUtil.hasLogin(channel)) { + return; + } + ConsoleCommand consoleCommand = consoleCommandMap.get(command); + if (null != consoleCommand) { + consoleCommand.exec(scanner, channel); + } else { + System.err.println("无法识别[" + command + "]指令,请重新输入"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java new file mode 100644 index 0000000..db68b98 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.CreateGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Arrays; +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupConsoleCommand implements ConsoleCommand { + + private static final String USER_ID_SPLITER = ","; + + @Override + public void exec(Scanner scanner, Channel channel) { + CreateGroupRequestPacket createGroupRequestPacket = new CreateGroupRequestPacket(); + + System.out.print("【拉人群聊】输入 userId 列表,userId 之间英文逗号隔开:"); + String userIds = scanner.next(); + createGroupRequestPacket.setUserIdList(Arrays.asList(userIds.split(USER_ID_SPLITER))); + channel.writeAndFlush(createGroupRequestPacket); + } + +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java new file mode 100644 index 0000000..c3ff3f4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.JoinGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupConsoleCommand implements ConsoleCommand{ + + @Override + public void exec(Scanner scanner, Channel channel) { + JoinGroupRequestPacket requestPacket = new JoinGroupRequestPacket(); + System.out.println("输入groupId, 加入群聊:"); + String groupId = scanner.next(); + requestPacket.setGroupId(groupId); + channel.writeAndFlush(requestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java new file mode 100644 index 0000000..87e79c4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java @@ -0,0 +1,25 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.ListGroupMembersRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + ListGroupMembersRequestPacket listGroupMembersRequestPacket = new ListGroupMembersRequestPacket(); + + System.out.print("输入 groupId,获取群成员列表:"); + String groupId = scanner.next(); + + listGroupMembersRequestPacket.setGroupId(groupId); + channel.writeAndFlush(listGroupMembersRequestPacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java new file mode 100644 index 0000000..3e632ca --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java @@ -0,0 +1,38 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LoginConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + LoginRequestPacket loginRequestPacket = new LoginRequestPacket(); + + System.out.print("输入用户名登录: "); + String userIdStr; // 在退出登录logout之后 这里会读取到最后一个回车符 用户名就是空字符串会导致无法退出登录 + while ((userIdStr = scanner.nextLine()).isEmpty()) { + System.out.println("用户名异常, 请重新输入"); + } + loginRequestPacket.setUserName(userIdStr); + loginRequestPacket.setPassword("pwd"); + + // 发送登录数据包 + channel.writeAndFlush(loginRequestPacket); + waitForLoginResponse(); + } + + private static void waitForLoginResponse() { + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java new file mode 100644 index 0000000..2d13370 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.LogoutRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutConsoleCommand implements ConsoleCommand { + @Override + public void exec(Scanner scanner, Channel channel) { + LogoutRequestPacket logoutRequestPacket = new LogoutRequestPacket(); + channel.writeAndFlush(logoutRequestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java new file mode 100644 index 0000000..4b4b284 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java @@ -0,0 +1,25 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.QuitGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + QuitGroupRequestPacket quitGroupRequestPacket = new QuitGroupRequestPacket(); + + System.out.print("输入 groupId,退出群聊:"); + String groupId = scanner.next(); + + quitGroupRequestPacket.setGroupId(groupId); + channel.writeAndFlush(quitGroupRequestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java new file mode 100644 index 0000000..90f8f90 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java @@ -0,0 +1,24 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.GroupMessageRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class SendToGroupConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + System.out.print("发送消息给某个某个群组:"); + + String toGroupId = scanner.next(); + String message = scanner.next(); + channel.writeAndFlush(new GroupMessageRequestPacket(toGroupId, message)); + + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java new file mode 100644 index 0000000..9ead2b5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class SendToUserConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + System.out.print("发送消息给某个某个用户:"); + + String toUserId = scanner.next(); + String message = scanner.next(); + channel.writeAndFlush(new MessageRequestPacket(toUserId, message)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java new file mode 100644 index 0000000..fe72853 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java @@ -0,0 +1,13 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; +import io.netty.util.AttributeKey; + +/** + * @Description Netty 属性集 + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Attributes { + AttributeKey SESSION = AttributeKey.newInstance("session"); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java new file mode 100644 index 0000000..f84f29f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java @@ -0,0 +1,44 @@ +package com.bruis.learnnetty.im.model; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Command { + Byte LOGIN_REQUEST = 1; + + Byte LOGIN_RESPONSE = 2; + + Byte MESSAGE_REQUEST = 3; + + Byte MESSAGE_RESPONSE = 4; + + Byte LOGOUT_REQUEST = 5; + + Byte LOGOUT_RESPONSE = 6; + + Byte CREATE_GROUP_REQUEST = 7; + + Byte CREATE_GROUP_RESPONSE = 8; + + Byte LIST_GROUP_MEMBERS_REQUEST = 9; + + Byte LIST_GROUP_MEMBERS_RESPONSE = 10; + + Byte JOIN_GROUP_REQUEST = 11; + + Byte JOIN_GROUP_RESPONSE = 12; + + Byte QUIT_GROUP_REQUEST = 13; + + Byte QUIT_GROUP_RESPONSE = 14; + + Byte GROUP_MESSAGE_REQUEST = 15; + + Byte GROUP_MESSAGE_RESPONSE = 16; + + Byte HEARTBEAT_REQUEST = 17; + + Byte HEARTBEAT_RESPONSE = 18; +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java new file mode 100644 index 0000000..dc9ffc5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.im.model; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.CREATE_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupRequestPacket extends Packet { + + private List userIdList; + + @Override + public Byte getCommand() { + return CREATE_GROUP_REQUEST; + } + + public List getUserIdList() { + return userIdList; + } + + public void setUserIdList(List userIdList) { + this.userIdList = userIdList; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java new file mode 100644 index 0000000..6209205 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java @@ -0,0 +1,48 @@ +package com.bruis.learnnetty.im.model; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.CREATE_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupResponsePacket extends Packet { + private boolean success; + + private String groupId; + + private List userNameList; + + @Override + public Byte getCommand() { + + return CREATE_GROUP_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public List getUserNameList() { + return userNameList; + } + + public void setUserNameList(List userNameList) { + this.userNameList = userNameList; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java new file mode 100644 index 0000000..3a6f812 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.GROUP_MESSAGE_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageRequestPacket extends Packet { + private String toGroupId; + private String message; + + public GroupMessageRequestPacket(String toGroupId, String message) { + this.toGroupId = toGroupId; + this.message = message; + } + + @Override + public Byte getCommand() { + return GROUP_MESSAGE_REQUEST; + } + + public String getToGroupId() { + return toGroupId; + } + + public void setToGroupId(String toGroupId) { + this.toGroupId = toGroupId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java new file mode 100644 index 0000000..986333b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java @@ -0,0 +1,49 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; + +import static com.bruis.learnnetty.im.model.Command.GROUP_MESSAGE_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageResponsePacket extends Packet { + + private String fromGroupId; + + private Session fromUser; + + private String message; + + @Override + public Byte getCommand() { + + return GROUP_MESSAGE_RESPONSE; + } + + public String getFromGroupId() { + return fromGroupId; + } + + public void setFromGroupId(String fromGroupId) { + this.fromGroupId = fromGroupId; + } + + public Session getFromUser() { + return fromUser; + } + + public void setFromUser(Session fromUser) { + this.fromUser = fromUser; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java new file mode 100644 index 0000000..26fb73d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java @@ -0,0 +1,26 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.JOIN_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + return JOIN_GROUP_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java new file mode 100644 index 0000000..dce9a1d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java @@ -0,0 +1,46 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.JOIN_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupResponsePacket extends Packet { + + private String groupId; + + private boolean success; + + private String reason; + + @Override + public Byte getCommand() { + return JOIN_GROUP_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java new file mode 100644 index 0000000..886b19c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java @@ -0,0 +1,27 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LIST_GROUP_MEMBERS_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + + return LIST_GROUP_MEMBERS_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java new file mode 100644 index 0000000..dbc174e --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java @@ -0,0 +1,41 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.LIST_GROUP_MEMBERS_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersResponsePacket extends Packet { + + private String groupId; + + private List sessionList; + + @Override + public Byte getCommand() { + + return LIST_GROUP_MEMBERS_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public List getSessionList() { + return sessionList; + } + + public void setSessionList(List sessionList) { + this.sessionList = sessionList; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java new file mode 100644 index 0000000..d1122bd --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java @@ -0,0 +1,46 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGIN_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class LoginRequestPacket extends Packet { + + private String userId; + + private String userName; + + private String password; + + @Override + public Byte getCommand() { + return LOGIN_REQUEST; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java new file mode 100644 index 0000000..32599fd --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java @@ -0,0 +1,57 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGIN_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class LoginResponsePacket extends Packet { + + private String userId; + + private String userName; + + private boolean success; + + private String reason; + + + @Override + public Byte getCommand() { + return LOGIN_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java new file mode 100644 index 0000000..c66dc68 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGOUT_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutRequestPacket extends Packet { + @Override + public Byte getCommand() { + + return LOGOUT_REQUEST; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java new file mode 100644 index 0000000..73d2a71 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java @@ -0,0 +1,37 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGOUT_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class LogoutResponsePacket extends Packet { + + private boolean success; + + private String reason; + + + @Override + public Byte getCommand() { + return LOGOUT_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java new file mode 100644 index 0000000..4dae50c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java @@ -0,0 +1,43 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.MESSAGE_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class MessageRequestPacket extends Packet { + + private String toUserId; + + private String message; + + public MessageRequestPacket(){} + + public MessageRequestPacket(String toUserId, String message) { + this.toUserId = toUserId; + this.message = message; + } + + @Override + public Byte getCommand() { + return MESSAGE_REQUEST; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getToUserId() { + return toUserId; + } + + public void setToUserId(String toUserId) { + this.toUserId = toUserId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java new file mode 100644 index 0000000..372a33b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java @@ -0,0 +1,47 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.MESSAGE_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class MessageResponsePacket extends Packet { + + private String fromUserId; + + private String fromUserName; + + private String message; + + @Override + public Byte getCommand() { + + return MESSAGE_RESPONSE; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getFromUserId() { + return fromUserId; + } + + public void setFromUserId(String fromUserId) { + this.fromUserId = fromUserId; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java new file mode 100644 index 0000000..0100f52 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java @@ -0,0 +1,27 @@ +package com.bruis.learnnetty.im.model; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public abstract class Packet { + /** + * 协议版本 + */ + @JSONField(deserialize = false , serialize = false) + private Byte version = 1; + + @JSONField(serialize = false) + public abstract Byte getCommand(); + + public Byte getVersion() { + return version; + } + + public void setVersion(Byte version) { + this.version = version; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java new file mode 100644 index 0000000..7a81483 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java @@ -0,0 +1,102 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.serialize.Serializer; +import com.bruis.learnnetty.im.serialize.impl.JSONSerializer; +import io.netty.buffer.ByteBuf; + +import java.util.HashMap; +import java.util.Map; + +import static com.bruis.learnnetty.im.model.Command.*; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class PacketCodeC { + public static final int MAGIC_NUMBER = 0x12345678; + public static final PacketCodeC INSTANCE = new PacketCodeC(); + + private final Map> packetTypeMap; + private final Map serializerMap; + + + private PacketCodeC() { + packetTypeMap = new HashMap<>(); + packetTypeMap.put(LOGIN_REQUEST, LoginRequestPacket.class); + packetTypeMap.put(LOGIN_RESPONSE, LoginResponsePacket.class); + packetTypeMap.put(MESSAGE_REQUEST, MessageRequestPacket.class); + packetTypeMap.put(MESSAGE_RESPONSE, MessageResponsePacket.class); + packetTypeMap.put(LOGOUT_REQUEST, LogoutRequestPacket.class); + packetTypeMap.put(LOGOUT_RESPONSE, LogoutResponsePacket.class); + packetTypeMap.put(CREATE_GROUP_REQUEST, CreateGroupRequestPacket.class); + packetTypeMap.put(CREATE_GROUP_RESPONSE, CreateGroupResponsePacket.class); + packetTypeMap.put(JOIN_GROUP_REQUEST, JoinGroupRequestPacket.class); + packetTypeMap.put(JOIN_GROUP_RESPONSE, JoinGroupResponsePacket.class); + packetTypeMap.put(QUIT_GROUP_REQUEST, QuitGroupRequestPacket.class); + packetTypeMap.put(QUIT_GROUP_RESPONSE, QuitGroupResponsePacket.class); + packetTypeMap.put(LIST_GROUP_MEMBERS_REQUEST, ListGroupMembersRequestPacket.class); + packetTypeMap.put(LIST_GROUP_MEMBERS_RESPONSE, ListGroupMembersResponsePacket.class); + packetTypeMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequestPacket.class); + packetTypeMap.put(GROUP_MESSAGE_RESPONSE, GroupMessageResponsePacket.class); + + serializerMap = new HashMap<>(); + Serializer serializer = new JSONSerializer(); + serializerMap.put(serializer.getSerializerAlogrithm(), serializer); + } + + + public void encode(ByteBuf byteBuf, Packet packet) { + // 1. 序列化 java 对象 + byte[] bytes = Serializer.DEFAULT.serialize(packet); + + // 2. 实际编码过程 + byteBuf.writeInt(MAGIC_NUMBER); + byteBuf.writeByte(packet.getVersion()); + byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm()); + byteBuf.writeByte(packet.getCommand()); + byteBuf.writeInt(bytes.length); + byteBuf.writeBytes(bytes); + } + + + public Packet decode(ByteBuf byteBuf) { + // 跳过 magic number + byteBuf.skipBytes(4); + + // 跳过版本号 + byteBuf.skipBytes(1); + + // 序列化算法 + byte serializeAlgorithm = byteBuf.readByte(); + + // 指令 + byte command = byteBuf.readByte(); + + // 数据包长度 + int length = byteBuf.readInt(); + + byte[] bytes = new byte[length]; + byteBuf.readBytes(bytes); + + Class requestType = getRequestType(command); + Serializer serializer = getSerializer(serializeAlgorithm); + + if (requestType != null && serializer != null) { + return serializer.deserialize(requestType, bytes); + } + + return null; + } + + private Serializer getSerializer(byte serializeAlgorithm) { + + return serializerMap.get(serializeAlgorithm); + } + + private Class getRequestType(byte command) { + + return packetTypeMap.get(command); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java new file mode 100644 index 0000000..ca5342f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java @@ -0,0 +1,26 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + return QUIT_GROUP_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java new file mode 100644 index 0000000..99529c7 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java @@ -0,0 +1,47 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_REQUEST; +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupResponsePacket extends Packet { + + private String groupId; + + private boolean success; + + private String reason; + + @Override + public Byte getCommand() { + return QUIT_GROUP_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java new file mode 100644 index 0000000..1bce9c9 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.serialize; + + +import com.bruis.learnnetty.im.serialize.impl.JSONSerializer; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Serializer { + Serializer DEFAULT = new JSONSerializer(); + + /** + * 序列化算法 + * @return + */ + byte getSerializerAlogrithm(); + + /** + * java 对象转换成二进制 + */ + byte[] serialize(Object object); + + /** + * 二进制转换成 java 对象 + */ + T deserialize(Class clazz, byte[] bytes); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java new file mode 100644 index 0000000..ef50887 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java @@ -0,0 +1,13 @@ +package com.bruis.learnnetty.im.serialize; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface SerializerAlogrithm { + /** + * json 序列化 + */ + byte JSON = 1; +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java new file mode 100644 index 0000000..9a2881f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.serialize.impl; + +import com.alibaba.fastjson.JSON; +import com.bruis.learnnetty.im.serialize.Serializer; +import com.bruis.learnnetty.im.serialize.SerializerAlogrithm; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class JSONSerializer implements Serializer { + @Override + public byte getSerializerAlogrithm() { + return SerializerAlogrithm.JSON; + } + + @Override + public byte[] serialize(Object object) { + + return JSON.toJSONBytes(object); + } + + @Override + public T deserialize(Class clazz, byte[] bytes) { + + return JSON.parseObject(bytes, clazz); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java new file mode 100644 index 0000000..08d3f14 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.server; + +import com.bruis.learnnetty.im.codec.PacketCodecHandler; +import com.bruis.learnnetty.im.codec.PacketDecoder; +import com.bruis.learnnetty.im.codec.PacketEncoder; +import com.bruis.learnnetty.im.codec.Spliter; +import com.bruis.learnnetty.im.server.handler.*; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.Date; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class NettyServer { + + private static final int PORT = 8000; + + public static void main(String[] args) { + NioEventLoopGroup bossGroup = new NioEventLoopGroup(); + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 1024) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new Spliter()); + ch.pipeline().addLast(PacketCodecHandler.INSTANCE); + ch.pipeline().addLast(LoginRequestHandler.INSTANCE); + ch.pipeline().addLast(AuthHandler.INSTANCE); + ch.pipeline().addLast(IMHandler.INSTANCE); + } + }); + bind(serverBootstrap, PORT); + } + + private static void bind(final ServerBootstrap serverBootstrap, final int port) { + serverBootstrap.bind(port).addListener(future -> { + if (future.isSuccess()) { + System.out.println(new Date() + ": 端口[" + port + "]绑定成功!"); + } else { + System.err.println("端口[" + port + "]绑定失败!"); + } + }); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java new file mode 100644 index 0000000..ad5c784 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class AuthHandler extends ChannelInboundHandlerAdapter { + + public static final AuthHandler INSTANCE = new AuthHandler(); + + protected AuthHandler() {} + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!SessionUtil.hasLogin(ctx.channel())) { + ctx.channel().close(); + } else { + ctx.pipeline().remove(this); + super.channelRead(ctx, msg); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java new file mode 100644 index 0000000..64c57cf --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.CreateGroupRequestPacket; +import com.bruis.learnnetty.im.model.CreateGroupResponsePacket; +import com.bruis.learnnetty.im.util.IDUtil; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import org.omg.PortableServer.ID_UNIQUENESS_POLICY_ID; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class CreateGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final CreateGroupRequestHandler INSTANCE = new CreateGroupRequestHandler(); + + protected CreateGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, CreateGroupRequestPacket msg) throws Exception { + List userIdList = msg.getUserIdList(); + List userNameList = new ArrayList<>(); + + ChannelGroup channelGroup = new DefaultChannelGroup(ctx.executor()); + + for (String userId : userIdList) { + Channel channel = SessionUtil.getChannel(userId); + if (null != channel) { + channelGroup.add(channel); + userNameList.add(SessionUtil.getSession(channel).getUserName()); + } + } + + String groupId = IDUtil.randomUserId(); + CreateGroupResponsePacket createGroupResponsePacket = new CreateGroupResponsePacket(); + createGroupResponsePacket.setSuccess(true); + createGroupResponsePacket.setGroupId(groupId); + createGroupResponsePacket.setUserNameList(userNameList); + + channelGroup.writeAndFlush(createGroupResponsePacket); + + System.out.print("群创建成功,id 为[" + createGroupResponsePacket.getGroupId() + "], "); + System.out.println("群里面有:" + createGroupResponsePacket.getUserNameList()); + + SessionUtil.bindChannelGroup(groupId, channelGroup); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java new file mode 100644 index 0000000..5507f45 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java @@ -0,0 +1,34 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.GroupMessageRequestPacket; +import com.bruis.learnnetty.im.model.GroupMessageResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class GroupMessageRequestHandler extends SimpleChannelInboundHandler { + + public static final GroupMessageRequestHandler INSTANCE = new GroupMessageRequestHandler(); + + public GroupMessageRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, GroupMessageRequestPacket msg) throws Exception { + String toGroupId = msg.getToGroupId(); + GroupMessageResponsePacket responsePacket = new GroupMessageResponsePacket(); + responsePacket.setFromGroupId(toGroupId); + responsePacket.setMessage(msg.getMessage()); + responsePacket.setFromUser(SessionUtil.getSession(ctx.channel())); + + ChannelGroup channelGroup = SessionUtil.getChannelGroup(toGroupId); + channelGroup.writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java new file mode 100644 index 0000000..8420f57 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java @@ -0,0 +1,42 @@ +package com.bruis.learnnetty.im.server.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import com.bruis.learnnetty.im.model.Packet; + +import java.util.HashMap; +import java.util.Map; + +import static com.bruis.learnnetty.im.model.Command.*; + + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/25 + */ +@ChannelHandler.Sharable +public class IMHandler extends SimpleChannelInboundHandler { + + public static final IMHandler INSTANCE = new IMHandler(); + + private Map> handlerMap; + + private IMHandler() { + handlerMap = new HashMap<>(); + + handlerMap.put(MESSAGE_REQUEST, MessageRequestHandler.INSTANCE); + handlerMap.put(CREATE_GROUP_REQUEST, CreateGroupRequestHandler.INSTANCE); + handlerMap.put(JOIN_GROUP_REQUEST, JoinGroupRequestHandler.INSTANCE); + handlerMap.put(QUIT_GROUP_REQUEST, QuitGroupRequestHandler.INSTANCE); + handlerMap.put(LIST_GROUP_MEMBERS_REQUEST, ListGroupMembersRequestHandler.INSTANCE); + handlerMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequestHandler.INSTANCE); + handlerMap.put(LOGOUT_REQUEST, LogoutRequestHandler.INSTANCE); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Packet packet) throws Exception { + handlerMap.get(packet.getCommand()).channelRead(ctx, packet); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java new file mode 100644 index 0000000..1188ec6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.JoinGroupRequestPacket; +import com.bruis.learnnetty.im.model.JoinGroupResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class JoinGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final JoinGroupRequestHandler INSTANCE = new JoinGroupRequestHandler(); + + protected JoinGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, JoinGroupRequestPacket msg) throws Exception { + // 目标群聊id + String groupId = msg.getGroupId(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + JoinGroupResponsePacket responsePacket = new JoinGroupResponsePacket(); + responsePacket.setSuccess(true); + responsePacket.setGroupId(groupId); + if (null == channelGroup) { + responsePacket.setSuccess(false); + responsePacket.setReason("没有该群聊,请重试..."); + } else { + channelGroup.add(ctx.channel()); + } + ctx.channel().writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java new file mode 100644 index 0000000..8be361d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java @@ -0,0 +1,48 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.ListGroupMembersRequestPacket; +import com.bruis.learnnetty.im.model.ListGroupMembersResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class ListGroupMembersRequestHandler extends SimpleChannelInboundHandler { + + public static final ListGroupMembersRequestHandler INSTANCE = new ListGroupMembersRequestHandler(); + + protected ListGroupMembersRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ListGroupMembersRequestPacket requestPacket) { + // 1. 获取群的 ChannelGroup + String groupId = requestPacket.getGroupId(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + + // 2. 遍历群成员的 channel,对应的 session,构造群成员的信息 + List sessionList = new ArrayList<>(); + for (Channel channel : channelGroup) { + Session session = SessionUtil.getSession(channel); + sessionList.add(session); + } + + // 3. 构建获取成员列表响应写回到客户端 + ListGroupMembersResponsePacket responsePacket = new ListGroupMembersResponsePacket(); + + responsePacket.setGroupId(groupId); + responsePacket.setSessionList(sessionList); + ctx.channel().writeAndFlush(responsePacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java new file mode 100644 index 0000000..0286a96 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java @@ -0,0 +1,62 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import com.bruis.learnnetty.im.model.LoginResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.IDUtil; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.*; + +import java.util.Arrays; +import java.util.Date; + +/** + * @Description 接收客户端登录请求 + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class LoginRequestHandler extends SimpleChannelInboundHandler { + + public static final LoginRequestHandler INSTANCE = new LoginRequestHandler(); + + protected LoginRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) { + // 登录校验响应 + LoginResponsePacket loginResponsePacket = new LoginResponsePacket(); + loginResponsePacket.setVersion(loginRequestPacket.getVersion()); + loginResponsePacket.setUserName(loginRequestPacket.getUserName()); + + if (valid(loginRequestPacket)) { + loginResponsePacket.setSuccess(true); + String userId = IDUtil.randomUserId(); + loginResponsePacket.setUserId(userId); + System.out.println("[" + loginRequestPacket.getUserName() + "]登录成功"); + SessionUtil.bindSession(new Session(userId, loginRequestPacket.getUserName()), ctx.channel()); + } else { + loginResponsePacket.setReason("账号密码校验失败"); + loginResponsePacket.setSuccess(false); + System.out.println(new Date() + ": 登录失败!"); + } + + // 登录响应 + ctx.writeAndFlush(loginResponsePacket).addListener((ChannelFutureListener) future -> { + // 关闭channel成功 + Throwable cause = future.cause(); + if (null != cause) { + System.out.println(Arrays.toString(cause.getStackTrace())); + } + }); + } + + private boolean valid(LoginRequestPacket loginRequestPacket) { + return true; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + SessionUtil.unBindSession(ctx.channel()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java new file mode 100644 index 0000000..4436802 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.LogoutRequestPacket; +import com.bruis.learnnetty.im.model.LogoutResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class LogoutRequestHandler extends SimpleChannelInboundHandler { + + public static final LogoutRequestHandler INSTANCE = new LogoutRequestHandler(); + + protected LogoutRequestHandler () {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LogoutRequestPacket msg) { + SessionUtil.unBindSession(ctx.channel()); + LogoutResponsePacket logoutResponsePacket = new LogoutResponsePacket(); + logoutResponsePacket.setSuccess(true); + ctx.channel().writeAndFlush(logoutResponsePacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java new file mode 100644 index 0000000..b9b83a0 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java @@ -0,0 +1,45 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import com.bruis.learnnetty.im.model.MessageResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class MessageRequestHandler extends SimpleChannelInboundHandler { + + public static final MessageRequestHandler INSTANCE = new MessageRequestHandler(); + + protected MessageRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageRequestPacket messageRequestPacket) throws Exception { + // 1.拿到消息发送方的会话信息 + Session session = SessionUtil.getSession(channelHandlerContext.channel()); + + // 2.通过消息发送方的会话信息构造要发送的消息 + MessageResponsePacket messageResponsePacket = new MessageResponsePacket(); + messageResponsePacket.setFromUserId(session.getUserId()); + messageResponsePacket.setFromUserName(session.getUserName()); + messageResponsePacket.setMessage(messageRequestPacket.getMessage()); + + // 3.拿到消息接收方的 channel + Channel toUserChannel = SessionUtil.getChannel(messageRequestPacket.getToUserId()); + + // 4.将消息发送给消息接收方 + if (toUserChannel != null && SessionUtil.hasLogin(toUserChannel)) { + toUserChannel.writeAndFlush(messageResponsePacket); + } else { + System.err.println("[" + messageRequestPacket.getToUserId() + "] 不在线,发送失败!"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java new file mode 100644 index 0000000..86455bb --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java @@ -0,0 +1,37 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.QuitGroupRequestPacket; +import com.bruis.learnnetty.im.model.QuitGroupResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class QuitGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final QuitGroupRequestHandler INSTANCE = new QuitGroupRequestHandler(); + + protected QuitGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, QuitGroupRequestPacket msg) throws Exception { + String groupId = msg.getGroupId(); + Channel channel = ctx.channel(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + channelGroup.remove(channel); + + QuitGroupResponsePacket responsePacket = new QuitGroupResponsePacket(); + responsePacket.setSuccess(true); + responsePacket.setGroupId(groupId); + + channel.writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java new file mode 100644 index 0000000..7a7be2d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.session; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class Session { + + private String userId; + + private String userName; + + public Session(String userId, String userName) { + this.userId = userId; + this.userName = userName; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + @Override + public String toString() { + return userId + "->" + userName; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java new file mode 100644 index 0000000..3b5403f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.im.util; + +import java.util.UUID; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class IDUtil { + + public static String randomUserId() { + return UUID.randomUUID().toString().split("-")[0]; + } + +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java new file mode 100644 index 0000000..f4b41c3 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.util; + +import com.bruis.learnnetty.im.model.Attributes; +import com.bruis.learnnetty.im.session.Session; +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroup; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class SessionUtil { + + private static final Map userIdChannelMap = new ConcurrentHashMap<>(); + + private static final Map groupIdChannelGroupMap = new ConcurrentHashMap<>(); + + public static void bindSession(Session session, Channel channel) { + userIdChannelMap.put(session.getUserId(), channel); + channel.attr(Attributes.SESSION).set(session); + } + + public static void unBindSession(Channel channel) { + if (hasLogin(channel)) { + Session session = getSession(channel); + userIdChannelMap.remove(session.getUserId()); + channel.attr(Attributes.SESSION).set(null); + System.out.println(session + " 退出登录"); + } + } + + public static boolean hasLogin(Channel channel) { + + return channel.hasAttr(Attributes.SESSION); + } + + public static Session getSession(Channel channel) { + + return channel.attr(Attributes.SESSION).get(); + } + + public static Channel getChannel(String userId) { + + return userIdChannelMap.get(userId); + } + + public static void bindChannelGroup(String groupId, ChannelGroup channelGroup) { + groupIdChannelGroupMap.put(groupId, channelGroup); + } + + public static ChannelGroup getChannelGroup(String groupId) { + return groupIdChannelGroupMap.get(groupId); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java new file mode 100644 index 0000000..7d69f53 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java @@ -0,0 +1,17 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.alibaba.fastjson.JSONObject; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java new file mode 100644 index 0000000..cd3a840 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java @@ -0,0 +1,91 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.netty.connections.longconnections.ClientHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; + +/** + * @author lhy + * @date 2022/2/16 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + public static ChannelFuture future = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + final ClientHandler clientHandler = new ClientHandler(); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, + 0, 4, 0, 4)); + ch.pipeline().addLast(new StringDecoder()); + ch.pipeline().addLast(clientHandler); + ch.pipeline().addLast(new LengthFieldPrepender(4, false)); + ch.pipeline().addLast(new StringEncoder(Charset.forName("utf-8"))); + } + }); + try { + future = bootstrap.connect("127.0.0.1", 8080).sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 说明:对于这个长连接的例子中,使用了静态化,即单链接、长连接,如果是多链接的话不可使用静态化,需使用线程池。 + * @param msg + * @return + */ + public Object sendRequest(Object msg) { + try { + RequestFuture request = new RequestFuture(); + request.setRequest(msg); + String requestStr = JSONObject.toJSONString(request); + future.channel().writeAndFlush(requestStr); + myselfPrint("我阻塞了", null); + return request.get(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + public static void main(String[] args) { + NettyClient nettyClient = new NettyClient(); + for (int i = 0; i < 10; i++) { + Object result = nettyClient.sendRequest("hello"); + myselfPrint("拿到结果了", result); + } + } + + public static void myselfPrint(String description, Object value) { + StringBuilder builder = new StringBuilder(); + builder.append(Thread.currentThread().getName()); + if (!StringUtils.isEmpty(description)) { + builder.append("-").append(description); + } + if (!StringUtils.isEmpty(value)) { + builder.append("-").append(value); + } + System.out.println(builder.toString()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java new file mode 100644 index 0000000..d668c6b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java @@ -0,0 +1,52 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.bruis.learnnetty.netty.connections.shortconnections.ServerHandler; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void main(String[] args) { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java new file mode 100644 index 0000000..6c5167a --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java @@ -0,0 +1,130 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + public static final AtomicLong aid = new AtomicLong(); + + public RequestFuture() { + id = aid.incrementAndGet(); + addFuture(this); + } + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 表明服务端发送过来的结果已经接收到了,可以signal了 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java new file mode 100644 index 0000000..34ee0d0 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java new file mode 100644 index 0000000..6918c68 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.concurrent.Promise; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + private Promise promise; + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + promise.setSuccess(response); + } + + public Promise getPromise() { + return promise; + } + + public void setPromise(Promise promise) { + this.promise = promise; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java new file mode 100644 index 0000000..e00bc64 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java @@ -0,0 +1,66 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.RequestFuture; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.util.concurrent.DefaultPromise; +import io.netty.util.concurrent.Promise; + +import java.nio.charset.StandardCharsets; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } + public static void main(String[] args) { + try { + Promise promise = new DefaultPromise<>(group.next()); + final ClientHandler clientHandler = new ClientHandler(); + clientHandler.setPromise(promise); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(clientHandler) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder(StandardCharsets.UTF_8)); + } + }); + ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync(); + RequestFuture request = new RequestFuture(); + request.setId(1); + request.setRequest("hello world!"); + String requestString = JSONObject.toJSONString(request); + // 向服务端发送请求 + future.channel().writeAndFlush(requestString); + // 同步阻塞等待服务端响应请求 + Response response = promise.get(); + System.out.println(JSONObject.toJSONString(response)); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java new file mode 100644 index 0000000..4453bd6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java @@ -0,0 +1,51 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void main(String[] args) { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java new file mode 100644 index 0000000..ec172ec --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java @@ -0,0 +1,32 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.RequestFuture; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ServerHandler extends ChannelInboundHandlerAdapter { + /** + * 接受客户端发送过来的请求 + * @param ctx + * @param msg + * @throws Exception + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + RequestFuture request = JSONObject.parseObject(msg.toString(), RequestFuture.class); + long id = request.getId(); + Response response = new Response(); + response.setId(id); + response.setResult("我是服务端(" + id + ")"); + /** + * 给客户端发送响应结果 + */ + ctx.channel().writeAndFlush(JSONObject.toJSONString(response)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java similarity index 96% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java index e562ddb..92178e8 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.nio.demo01; +package com.bruis.learnnetty.nio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java similarity index 98% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java index c897f1e..15ec3f2 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.nio.demo01; +package com.bruis.learnnetty.nio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java new file mode 100644 index 0000000..2ceb2a8 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.rpc.client; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import com.bruis.learnnetty.rpc.utils.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java new file mode 100644 index 0000000..e80d8c1 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java @@ -0,0 +1,92 @@ +package com.bruis.learnnetty.rpc.client; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; + +/** + * @author lhy + * @date 2022/2/16 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + public static ChannelFuture future = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + final ClientHandler clientHandler = new ClientHandler(); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, + 0, 4, 0, 4)); + ch.pipeline().addLast(new StringDecoder()); + ch.pipeline().addLast(clientHandler); + ch.pipeline().addLast(new LengthFieldPrepender(4, false)); + ch.pipeline().addLast(new StringEncoder(Charset.forName("utf-8"))); + } + }); + try { + future = bootstrap.connect("127.0.0.1", 8080).sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 说明:对于这个长连接的例子中,使用了静态化,即单链接、长连接,如果是多链接的话不可使用静态化,需使用线程池。 + * @param msg + * @return + */ + public Object sendRequest(Object msg, String path) { + try { + RequestFuture request = new RequestFuture(); + request.setRequest(msg); + request.setPath(path); + String requestStr = JSONObject.toJSONString(request); + future.channel().writeAndFlush(requestStr); + myselfPrint("我阻塞了", null); + return request.get(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + public static void main(String[] args) { + NettyClient nettyClient = new NettyClient(); + for (int i = 0; i < 10; i++) { + Object result = nettyClient.sendRequest("hello-" + i, "getUserNameById"); + myselfPrint("拿到结果了", result); + } + } + + public static void myselfPrint(String description, Object value) { + StringBuilder builder = new StringBuilder(); + builder.append(Thread.currentThread().getName()); + if (!StringUtils.isEmpty(description)) { + builder.append("-").append(description); + } + if (!StringUtils.isEmpty(value)) { + builder.append("-").append(value); + } + System.out.println(builder.toString()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java new file mode 100644 index 0000000..2df7483 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java @@ -0,0 +1,17 @@ +package com.bruis.learnnetty.rpc.controller; + +import com.bruis.learnnetty.rpc.utils.Remote; +import org.springframework.stereotype.Controller; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Controller +public class UserController { + @Remote(value = "getUserNameById") + public Object getUserNameById(String userId) { + System.out.println(Thread.currentThread().getName() + "-> 接受到请求:" + userId); + return "做了业务处理了,结果是用户编号userId为" + userId + "的用户姓名为张三"; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java new file mode 100644 index 0000000..b8fbb85 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java @@ -0,0 +1,38 @@ +package com.bruis.learnnetty.rpc.server; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author lhy + * @date 2022/2/17 + */ +public class ApplicationMain { + + private static volatile boolean running = true; + + public static void main(String[] args) { + try { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.bruis.learnnetty.rpc"); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + context.stop(); + } catch (Exception e) {} + + synchronized (ApplicationMain.class) { + running = false; + ApplicationMain.class.notify(); + } + })); + context.start(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.out.println("服务器已启动"); + synchronized (ApplicationMain.class) { + try { + ApplicationMain.class.wait(); + } catch (Exception e) {} + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java new file mode 100644 index 0000000..6942514 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java @@ -0,0 +1,55 @@ +package com.bruis.learnnetty.rpc.server; + +import com.bruis.learnnetty.rpc.utils.Mediator; +import com.bruis.learnnetty.rpc.utils.Remote; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Component +public class InitLoadRemoteMethod implements ApplicationListener, Ordered { + + @Override + public void onApplicationEvent(ContextRefreshedEvent context) { + // 获取Spring容器中带有@Controller的注解类 + Map controllerBeans = context.getApplicationContext() + .getBeansWithAnnotation(Controller.class); + for (String beanName : controllerBeans.keySet()) { + Object beanObj = controllerBeans.get(beanName); + // 获取这个bean的方法集合 + Method[] methods = beanObj.getClass().getMethods(); + for (Method method : methods) { + // 判断这个方法是否带有@Remote注解 + if (method.isAnnotationPresent(Remote.class)) { + Remote remote = method.getAnnotation(Remote.class); + // 注解的值 + String remoteValue = remote.value(); + // 缓存这个类 + Mediator.MethodBean methodBean = new Mediator.MethodBean(); + methodBean.setBean(beanObj); + methodBean.setMethod(method); + // @Remote的value值作为key,MethodBean作为value + Mediator.methodBeans.put(remoteValue, methodBean); + } + } + } + } + + /** + * 值越小优先级越高 + * @return + */ + @Override + public int getOrder() { + return -1; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java new file mode 100644 index 0000000..dd63c71 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.rpc.server; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Component +public class NettyApplicationListener implements ApplicationListener { + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 开启额外线程启动Netty服务 + new Thread() { + @Override + public void run() { + NettyServer.start(); + } + }.start(); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java new file mode 100644 index 0000000..cc8bfee --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java @@ -0,0 +1,51 @@ +package com.bruis.learnnetty.rpc.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void start() { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java new file mode 100644 index 0000000..7daa7ed --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.rpc.server; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.Mediator; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import com.bruis.learnnetty.rpc.utils.Response; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +@ChannelHandler.Sharable +public class ServerHandler extends ChannelInboundHandlerAdapter { + /** + * 接受客户端发送过来的请求 + * @param ctx + * @param msg + * @throws Exception + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + RequestFuture request = JSONObject.parseObject(msg.toString(), RequestFuture.class); + Response response = Mediator.process(request); + ctx.channel().writeAndFlush(JSONObject.toJSONString(response)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java new file mode 100644 index 0000000..99ccf31 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java @@ -0,0 +1,80 @@ +package com.bruis.learnnetty.rpc.utils; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * 存储RPC中的映射以及方法Bean + * + * @author lhy + * @date 2022/2/17 + */ +public class Mediator { + + public static Map methodBeans; + + static { + methodBeans = new HashMap<>(); + } + + public static Response process(RequestFuture future) { + Response response = new Response(); + try { + String path = future.getPath(); + MethodBean methodBean = methodBeans.get(path); + if (null != methodBean) { + Object bean = methodBean.getBean(); + Method method = methodBean.getMethod(); + Object request = future.getRequest(); + Class[] parameterTypes = method.getParameterTypes(); + // 此处只支持一个参数,所以写死固定0为索引 + Class parameterType = parameterTypes[0]; + Object param = null; + // 如果参数是List类型 + if (parameterType.isAssignableFrom(List.class)) { + param = JSONArray.parseArray(JSONArray.toJSONString(request), parameterType); + } else if (parameterType.getName().equalsIgnoreCase(String.class.getName())) { + param = request; + } else { + param = JSONObject.parseObject(JSONObject.toJSONString(request), parameterType); + } + // 反射调用方法 + Object result = method.invoke(bean, param); + response.setResult(result); + } + } catch (Exception e) { + e.printStackTrace(); + } + response.setId(future.getId()); + return response; + } + + public static class MethodBean { + + private Object bean; + + private Method method; + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java new file mode 100644 index 0000000..c173567 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.rpc.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Remote { + String value(); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java new file mode 100644 index 0000000..340f30a --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java @@ -0,0 +1,143 @@ +package com.bruis.learnnetty.rpc.utils; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + /** + * 请求路径 + */ + private String path; + + public static final AtomicLong aid = new AtomicLong(); + + public RequestFuture() { + id = aid.incrementAndGet(); + addFuture(this); + } + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java new file mode 100644 index 0000000..ac5478f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.rpc.utils; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java new file mode 100644 index 0000000..d0de69c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java @@ -0,0 +1,64 @@ +package com.bruis.learnnetty.thread.reentranlock; + +import java.util.ArrayList; +import java.util.List; + +/** + * 模拟Netty通讯过程 + * 主线程,获取子线程的结果 + * + * @author lhy + * @date 2022/2/10 + */ +public class FutureMain { + private static List reqs = new ArrayList<>(); + public static void main(String[] args) { + mockClient(); + mockServer(); + } + + /** + * 模拟服务端 接受结果 + */ + private static void mockServer() { + for (RequestFuture req : reqs) { + /** + * 主线程获取结果 + */ + Object result = req.get(); + System.out.println("服务端接受到响应结果:" + result.toString()); + } + } + /** + * 模拟客户端 发送请求 + */ + private static void mockClient() { + for (int i = 0; i < 100; i++) { + long id = i; + RequestFuture req = new RequestFuture(); + req.setId(id); + req.setRequest("hello world"); + /** + * 把请求缓存起来 + */ + RequestFuture.addFuture(req); + /** + * 将请求放入到请求列表中 + */ + reqs.add(req); + sendMsg(req); + SubThread subThread = new SubThread(req); + /** + * 开启子线程 + */ + subThread.start(); + } + } + /** + * 模拟请求处理 + * @param req + */ + private static void sendMsg(RequestFuture req) { + System.out.println("客户端发送数据,请求id为===============" + req.getId()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java new file mode 100644 index 0000000..1fd7dec --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java @@ -0,0 +1,123 @@ +package com.bruis.learnnetty.thread.reentranlock; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java new file mode 100644 index 0000000..ae1852c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.thread.reentranlock; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java new file mode 100644 index 0000000..9101e54 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java @@ -0,0 +1,30 @@ +package com.bruis.learnnetty.thread.reentranlock; + +/** + * 子线程,用于模拟服务端处理 + * + * @author lhy + * @date 2022/2/10 + */ +public class SubThread extends Thread { + + private RequestFuture request; + + public SubThread(RequestFuture request) { + this.request = request; + } + + @Override + public void run() { + Response response = new Response(); + response.setId(request.getId()); + response.setResult("服务端响应了结果,线程id: " + Thread.currentThread().getId() + ", 请求id:" + response.getId()); + // 子线程睡眠1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java new file mode 100644 index 0000000..bd4ee93 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java @@ -0,0 +1,64 @@ +package com.bruis.learnnetty.thread.synchronize; + +import java.util.ArrayList; +import java.util.List; + +/** + * 模拟Netty通讯过程 + * 主线程,获取子线程的结果 + * + * @author lhy + * @date 2022/2/10 + */ +public class FutureMain { + private static List reqs = new ArrayList<>(); + public static void main(String[] args) { + mockClient(); + mockServer(); + } + + /** + * 模拟服务端 接受结果 + */ + private static void mockServer() { + for (RequestFuture req : reqs) { + /** + * 主线程获取结果 + */ + Object result = req.get(); + System.out.println("服务端接受到响应结果:" + result.toString()); + } + } + /** + * 模拟客户端 发送请求 + */ + private static void mockClient() { + for (int i = 0; i < 100; i++) { + long id = i; + RequestFuture req = new RequestFuture(); + req.setId(id); + req.setRequest("hello world"); + /** + * 把请求缓存起来 + */ + RequestFuture.addFuture(req); + /** + * 将请求放入到请求列表中 + */ + reqs.add(req); + sendMsg(req); + SubThread subThread = new SubThread(req); + /** + * 开启子线程 + */ + subThread.start(); + } + } + /** + * 模拟请求处理 + * @param req + */ + private static void sendMsg(RequestFuture req) { + System.out.println("客户端发送数据,请求id为===============" + req.getId()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java new file mode 100644 index 0000000..f1dca80 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java @@ -0,0 +1,103 @@ +package com.bruis.learnnetty.thread.synchronize; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + synchronized (this) { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + this.wait(timeout); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + synchronized (Objects.requireNonNull(future, "RequestFuture")) { + future.notify(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java new file mode 100644 index 0000000..c5dfc05 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.thread.synchronize; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java new file mode 100644 index 0000000..16e2a57 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java @@ -0,0 +1,31 @@ +package com.bruis.learnnetty.thread.synchronize; + +/** + * 子线程,用于模拟服务端处理 + * + * @author lhy + * @date 2022/2/10 + */ +public class SubThread extends Thread { + + private RequestFuture request; + + public SubThread(RequestFuture request) { + this.request = request; + } + + @Override + public void run() { + Response response = new Response(); + response.setId(request.getId()); + response.setResult("服务端响应了结果,线程id: " + Thread.currentThread().getId() + ", 请求id:" + response.getId()); + // 子线程睡眠1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(this + " -> 当前线程准备调用received: " + Thread.currentThread().getName()); + RequestFuture.received(response); + } +} diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" index d99cbec..8bd2172 100644 --- "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" @@ -51,8 +51,9 @@ SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultEx } return loader; } -```Java -getExtensionLoader方法首先回去判断EXTENSION_LOADERS缓存中是否已经缓存了该类型的扩展点加载器,如果没有则new一个该类型的ExtensionLoader并添加进EXTENSION_LOADERS中。但需要注意的是ExtensionLoader的构造方法 +``` + +getExtensionLoader方法首先会去判断EXTENSION_LOADERS缓存中是否已经缓存了该类型的扩展点加载器,如果没有则new一个该类型的ExtensionLoader并添加进EXTENSION_LOADERS中。但需要注意的是ExtensionLoader的构造方法 中,是会先创建默认的ExtensionFactory类型的ExtensionLoader对象,然后调用getAdaptiveExtension()方法创建适配类型的扩展点实现类。 ```Java @@ -112,7 +113,7 @@ getExtensionLoader方法首先回去判断EXTENSION_LOADERS缓存中是否已经 loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); } - // 这里只会返回非Adaptive和非Wrapper类型的扩展点实现类Class,因为Adaptive会被缓存到cachedAdaptiveClasses缓存中,儿Wrapper类型的类会被缓存到cachedWrapperClasses缓存中。 + // 这里只会返回非Adaptive和非Wrapper类型的扩展点实现类Class,因为Adaptive会被缓存到cachedAdaptiveClasses缓存中,而Wrapper类型的类会被缓存到cachedWrapperClasses缓存中。 return extensionClasses; } @@ -267,10 +268,10 @@ public class AdaptiveExtensionFactory implements ExtensionFactory { } ``` -① 中逻辑是这样的,调用ExtensionLoader#getSupportedExtensions()回去加载ExtensionFactory所有的扩展点实现类,并返回一个扩展点名称作为Key,扩展点实现类Class对象为Value的Map集合, +① 中逻辑是这样的,调用ExtensionLoader#getSupportedExtensions()会去加载ExtensionFactory所有的扩展点实现类,并返回一个扩展点名称作为Key,扩展点实现类Class对象为Value的Map集合, 在上面的SPI配置文件中已经展示出来了,所以这里获取到的是spi。 -// 有人可能会问,上面的SPI配置文件不是还有一个adaptive吗?为什么没加载进来呢?这是因为getSupportedExtension()中实际是调用getExtensionClasses()方法去获取Map集合,而其底层是去从cachedClasses缓存中 +有人可能会问,上面的SPI配置文件不是还有一个adaptive吗?为什么没加载进来呢?这是因为getSupportedExtension()中实际是调用getExtensionClasses()方法去获取Map集合,而其底层是去从cachedClasses缓存中 获取,而adaptive扩展点实现类是缓存在了cachedAdaptiveClass中的。 @@ -385,4 +386,6 @@ public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext ### 3. @Activate注解 +TODO + diff --git "a/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" "b/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" new file mode 100644 index 0000000..cec5185 --- /dev/null +++ "b/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" @@ -0,0 +1,96 @@ +相信看过几个流行框架源码的小伙伴,或多或少都见到过底层代码运用的位运算,不知道有多少是能够一眼看懂了的,一眼看懂了的都是“真大佬”。如果看不懂的话就老老实实的通过二进制分析来看下这些二进制算法的作用。 + +## 1. JDK1.8 HashMap里运用到的为运算 + +## 2. Netty里运用的位运算 + +## 3. JDK ThreadPoolExecutor里的位运算 + +```java +public class ThreadPoolExecutor extends AbstractExecutorService { + + // ... 其他代码省略 + + private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); + private static final int COUNT_BITS = Integer.SIZE - 3; + private static final int CAPACITY = (1 << COUNT_BITS) - 1; + + private static final int RUNNING = -1 << COUNT_BITS; + private static final int SHUTDOWN = 0 << COUNT_BITS; + private static final int STOP = 1 << COUNT_BITS; + private static final int TIDYING = 2 << COUNT_BITS; + private static final int TERMINATED = 3 << COUNT_BITS; + + private static int runStateOf(int c) { return c & ~CAPACITY; } + private static int workerCountOf(int c) { return c & CAPACITY; } + private static int ctlOf(int rs, int wc) { return rs | wc; } + + private static boolean runStateLessThan(int c, int s) { + return c < s; + } + + private static boolean runStateAtLeast(int c, int s) { + return c >= s; + } + + private static boolean isRunning(int c) { + return c < SHUTDOWN; + } + + // ... 其他代码省略 +} +``` +首先看下ctlOf()方法,入参是int rs和int wc,这里rs其实是线程池里线程的状态,而wc表示的时线程数,基于这两个点我们进行位运算分析。 + +首先看先成变量: +```java +private static final int COUNT_BITS = Integer.SIZE - 3; +private static final int RUNNING = -1 << COUNT_BITS; +``` +Integer.SIZE = 32,所以COUNT_BITS = 29,这里RUNNING就是-1的二进制位左移29位,得到的结果就是(提示:-1的二进制是: 1111 1111 1111 1111 ... 三十二位全是1) +``` +1110 0000 0000 0000 0000 0000 0000 0000 +``` +这就是RUNNING的二进制值。 +同理我们可以分别得到SHUTDOWN、STOP、TIDYING、TERMINATED的二进制值 +``` +0000 0000 0000 0000 0000 0000 0000 0000 // SHUTDOWN +0010 0000 0000 0000 0000 0000 0000 0000 // STOP +0100 0000 0000 0000 0000 0000 0000 0000 // TIDYING +0110 0000 0000 0000 0000 0000 0000 0000 // TERMINATED +``` +这里其实已经可以看出作者的用意了,就是让高3位作为线程池的状态,低29位用来表示线程数量。对于 +```java +private static int ctlOf(int rs, int wc) { return rs | wc; } +// 位运算“或”,遇1得1,否则为0 +``` +所以ctlOf就表示将rs代表的线程状态和wc代表的线程数计算在同一个32位二进制中,互相不影响。 +所以如下: +```java +private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); +// 1110 0000 0000 0000 0000 0000 0000 0000 +``` +接着,再来分析下另外两个方法:runStateOf()、workerCountOf(),这两个方法都喝CAPACITY有关,先看下CAPACITY属性 +```java +private static final int CAPACITY = (1 << COUNT_BITS) - 1; +// 1 << 29 => 0010 0000 0000 0000 0000 0000 0000 0000 +// 1 << 29 - 1 => 0001 1111 1111 1111 1111 1111 1111 1111 + + +private static int runStateOf(int c) { return c & ~CAPACITY; } +// ~CAPACITY => 1110 0000 0000 0000 0000 0000 0000 0000 +// 运算“与”表示11得1,否则为0,所以 c & ~CAPACITY实际上就只能操作高三位, +// 也就是只能计算线程状态,并且~CAPACITY表示的是RUNNING时的状态 + + +private static int workerCountOf(int c) { return c & CAPACITY; } +// CAPACITY => 0001 1111 1111 1111 1111 1111 1111 1111 +// 所以 c & CAPACITY 就表示只能操作低29位,所以workerCountOf就只能操作线程数 +``` +这里需要注意的是,runStateOf()和workerCountOf()传入的数字都是需要由:ctlOf()计算返回的,否则计算会出错。 + +线程池位运算相关验证代码于,读者可自行测试以加强理解。 +[ThreadPoolExecutorDemo](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java) + + +未完待续... \ No newline at end of file diff --git "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" index 79f3bfd..015b838 100644 --- "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" +++ "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" @@ -51,7 +51,7 @@ Lock引起的将当前处理器缓存该变量的数据写回到系统内存中 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期,当处理器发现自己缓存行对于数据的内存地址被修改了,就会将当前缓存行设置为无效。当处理器对这个数据进行修改操作时,会重新从系统内存中读取该数据到处理器缓存中。 -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTawJOHf-1596181331575)(https://note.youdao.com/yws/api/personal/file/AA87E3ABBEDB4A37B69D8E75B5ED12C1?method=download&shareKey=f9788b07ab72368f3613b2744614eecf)] +![volatile-01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-01.png) 为了实现volatile的内存语义,编译期在生成字节码时会对使用volatile关键字修饰的变量进行处理,在字节码文件里对应位置生成一个Lock前缀指令,Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。 @@ -96,7 +96,7 @@ d = 4; //语句五 **volatile内存语义的底层实现原理——内存屏障** 为了实现volatile的内存语义,编译期在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。下图看看JMM针对编译期指定的volatile重排序的规则表: -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUC08aj9-1596181331578)(https://note.youdao.com/yws/api/personal/file/2DB4A9DDE8D243E680668BEDA1EA931D?method=download&shareKey=03684bd761521c57dfea00548eadeb15)] +![volatile-04](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-04.png) 就上面的图标,是什么含义呢? 举例来说, @@ -117,12 +117,12 @@ volatile读之后的操作不会被编译器重排序到volatile读之前。 2. 在每个volatile写操作后插入StoreLoad屏障 3. 在每个volatile读前面插入一个LoadLoad屏障 4. 在每个volatile读后面插入一个LoadStore屏障 - -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1N3KBZj-1596181331583)(https://note.youdao.com/yws/api/personal/file/E11087F8FD5B4673ABD8C58F6F8DA232?method=download&shareKey=cf78d935c04cb11b039399e1d4825b74)] + +![volatile-02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-02.png) - StoreStore屏障可以保证在volatile写之前,所有的普通写操作已经对所有处理器可见,StoreStore屏障保障了在volatile写之前所有的普通写操作已经刷新到主存。 - StoreLoad屏障避免volatile写与下面有可能出现的volatile读/写操作重排。因为编译器无法准确判断一个volatile写后面是否需要插入一个StoreLoad屏障(写之后直接就return了,这时其实没必要加StoreLoad屏障),为了能实现volatile的正确内存语意,JVM采取了保守的策略。在每个volatile写之后或每个volatile读之前加上一个StoreLoad屏障,而大多数场景是一个线程写volatile变量多个线程去读volatile变量,同一时刻读的线程数量其实远大于写的线程数量。选择在volatile写后面加入StoreLoad屏障将大大提升执行效率(上面已经说了StoreLoad屏障的开销是很大的)。 -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRcUS5Mm-1596181331589)(https://note.youdao.com/yws/api/personal/file/2A92B2D468A345F6A55C75249A89845A?method=download&shareKey=ac99a6bcd169bf4bcda8b0fbd33e0003)] +![volatile-03](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-03.png) - LoadLoad屏障保证了volatile读不会与下面的普通读发生重排 - LoadStore屏障保证了volatile读不回与下面的普通写发生重排。 diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" index 99d24f3..986e355 100644 --- "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" @@ -1,15 +1,24 @@ # Netty 服务端启动分析 -在Java中,网络通信是通过Socket来进行的,那么在Netty中,服务端的Socket是在哪里进行初始化的?并且在哪里进行accept连接? Netty里的Channel是啥,有啥作用呢?带着这三个问题,进入本文的Netty服务端启动分析。 +在Java中,网络通信是通过Socket来进行的,那么在Netty中,服务端要用到的Socket是在哪里进行初始化的?并且在哪里进行accept接受客户端连接的? Netty里的Channel是啥,有啥作用呢?带着这三个问题,进入本文的Netty服务端启动分析。 -本文分析将分为四大步: +本文分析将分为五大步: -1. 创建服务端Channel; -2. 初始化服务端Channel; -3. 注册selector; -4. 端口绑定; +1. Netty中的Channel; +2. 创建服务端Channel; +3. 初始化服务端Channel; +4. 注册selector; +5. 端口绑定; -## 1. 创建服务端Channel +## 1. Netty中的Channel + +在Netty中的Channel是用来定义对网络IO进行读/写的相关接口,与NIO中的Channel接口类似。Channel的功能主要有网络IO的读写、客户端发起的连接、主动关闭连接、关闭链路、获取通信双方的网络地址等。Channel接口下有一个重要的抽象类————AbstractChannel,一些公共的基础方法都在这个抽象类中实现,但对于一些特定的功能则需要不同的实现类去实现,这样最大限度地实现了功能和接口的重用。 + +在AbstractChannel中的网络IO模型和协议种类比较多,除了TCP协议,Netty还支持了HTTP2协议,如:AbstractHttp2StreamChannel。 + +Netty对于不同的网络模型以及IO模型,在AbstractChannel的基础上又抽象出了一层,如:AbstractNioChannel、AbstractEpollChannel、AbstractHttp2StreamChannel。 + +## 2. 创建服务端Channel 创建服务端Channel又可以分为四步,如下: @@ -54,13 +63,101 @@ public final class Server { } ``` -服务端构建好ServerBootstrap之后,通过bind()方法进行绑定。进入ServerBootstrap的父类AbstractBootstrap后,一路调用来到AbstractBootstrap#doBind()方法,首先就是初始化并注册Channel。 +服务端构建好ServerBootstrap之后,通过bind()方法进行绑定。进入ServerBootstrap的父类AbstractBootstrap后,线程经过调用栈的调用后来到AbstractBootstrap#doBind()方法,首先就是初始化并注册Channel。 + +AbstractBootstrap#doBind() +```java + private ChannelFuture doBind(final SocketAddress localAddress) { + // 注册channel + final ChannelFuture regFuture = initAndRegister(); + final Channel channel = regFuture.channel(); + if (regFuture.cause() != null) { + return regFuture; + } + + // regFuture如果完成了,则isDone为true,否则给regFuture添加一个监听器,当完成的时候再进行doBind0的操作 + if (regFuture.isDone()) { + // 此时我们已经知道NioServerSocketChannel已经完成了注册 + ChannelPromise promise = channel.newPromise(); + doBind0(regFuture, channel, localAddress, promise); + return promise; + } else { + // Registration future is almost always fulfilled already, but just in case it's not. + final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); + + // 给regFuture添加一个监听器,当注册chanel完成的时候,会回调进来 + regFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + Throwable cause = future.cause(); + if (cause != null) { + // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an + // IllegalStateException once we try to access the EventLoop of the Channel. + promise.setFailure(cause); + } else { + // Registration was successful, so set the correct executor to use. + // See https://github.com/netty/netty/issues/2586 + promise.registered(); + + doBind0(regFuture, channel, localAddress, promise); + } + } + }); + return promise; + } + } + + final ChannelFuture initAndRegister() { + Channel channel = null; + try { + // 拿到ReflectiveChannelFactory,然后通过其newChannel生成一个服务端Channel,底层就是通过反射newInstance()获取实例 + // 这里自然是NioServerSocketChannel实例对象 + channel = channelFactory.newChannel(); + // 初始化channel + init(channel); + } catch (Throwable t) { + if (channel != null) { + // channel can be null if newChannel crashed (eg SocketException("too many open files")) + channel.unsafe().closeForcibly(); + // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor + return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); + } + // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor + return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t); + } + + /** + * config() -> ServerBootstrapConfig + * group() -> NioEventLoopGroup,返回的是MultithreadEventLoopGroup + * register() -> 就是通过chooser选取到NioEventLoop对象 + */ + ChannelFuture regFuture = config().group().register(channel); + if (regFuture.cause() != null) { + if (channel.isRegistered()) { + channel.close(); + } else { + channel.unsafe().closeForcibly(); + } + } + return regFuture; + } +``` -![netty01png](https://coderbruis.github.io/javaDocs/img/netty/source/netty01_01.png) +在initAndRegister处channelFactory是ReflectiveChannelFactory,具体赋值处是在ServerBootstrap#channel()方法中定义的,并且传入的channel是:NioServerSocketChannel。 -在initAndRegister处channelFactory是ReflectiveChannelFactory,具体赋值处是在ServerBootstrap#channel()方法中定义的,并且传入的channel是:NioServerSocketChannel,上图中可以见。 +ReflectiveChannelFactory#newChannel +```java + @Override + public T newChannel() { + try { + return constructor.newInstance(); + } catch (Throwable t) { + throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t); + } + } +``` -查看到ReflectiveChannelFactory#newChannel()方法,实际就是调用的NioServerSocketChannel#newInstance()方法反射构建一个channel对象。 +查看到ReflectiveChannelFactory#newChannel()方法,T的类型是NioServerSocketChannel,所以实际就是调用的NioServerSocketChannel#newInstance()方法反射构建一个channel对象。 那么,我们看下NioServerSocketChannel底层是如何获取通过反射创建服务端Channel的呢? @@ -136,15 +233,14 @@ AbstractChannel类 } ``` -总结下服务端创建Channel的三件重要事情: +通过源码阅读,可以总结出Netty服务端创建Channel的三件重要事情: 1. 通过反射来创建JDK底层的channel; 2. 设置Channel为非阻塞模式ch.configureBlocking(false); 3. 创建一个pipeline对象; - -## 2. 初始化服务端Channel +## 3. 初始化服务端Channel 初始化服务端Channel可以分为如下的几步: @@ -208,17 +304,13 @@ ServerBoostrap端初始化过程 } ``` -## 3. 注册selector +## 4. 将Channel注册到selector 整个注册selector过程可以分为以下几步: -- AbstractChannel#register(channel) 入口 - - this.eventLoop = eventLoop 绑定线程 - - register0() 实际注册 - - doRegister() 调用jdk底层进行注册 - - invokeHandlerAddedIfNeeded() - - fireChannelRegistered() 传播注册成功的事件 - +1. AbstractChannel$AbstractUnsafe#register(channel) +2. AbstractUnsafe#register0() +3. AbstractUnsafe#doRegister() AbstractChannel ```java @@ -322,15 +414,16 @@ AbstractNioChannel.java 就这样,NioServerSocketChannel就以Accept事件注册到了Selector上了。 -## 4. 端口绑定 +这里需要注意一点,javaChannel()返回的是AbstractSelectableChannel,调用其register方法用于在给定的selector上注册这个通道channel,并返回一个选这件selectionKey。传入的操作位为0表示对任何事件都不感兴趣,仅仅是完成注册操作。 + +## 5. 端口绑定 端口绑定流程如下: -- AbstractUnsafe#bind() 入口 - - dobind() - - javaChannel().bind() jdk底层绑定 - - pipeline.fireChanelActive() 传播事件 - - HeadContext.readIfIsAutoRead() +1. AbstractBootstrap#bind() +2. AbstractBootstrap#dobind() +3. AbstractChannel#bind() +4. NioServerSocketChannel#doBind() AbstractChannel.AbstractUnsafe#bind() @@ -402,3 +495,4 @@ Netty服务端核心启动流程主要是为了创建NioServerSocketChannel, - AbstractChannel.AbstractUnsafe#register() 将服务端Channel注册到Selector上 - AbstractChannel.AbstractUnsafe#doBind() 注册端口号 + diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 0000000..05499f2 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,264 @@ +## NioServerSocketChannel读取数据原理分析 + +NioServerSocketChannel是AbstractNioMessageChannel的子类,而NioSocketChannel是AbstractNioByteChannel的子类,并且他们都有两个公共的父类:AbstractNioChannel、AbstractChannel。 + +在Netty中Channel是用来定义对网络IO的读写操作的相关接口,与NIO的Channel接口类似。Channel的功能主要有网络IO的读写、客户端发起的连接、主动关闭连接、关闭链路、获取通信双方的网络地址等。 +一些公共的基础方法都在这个AbstractChannel抽象类中实现,几个核心的方法如:channel的注册,channel撤销注册,网络IO的读、写。但对于一些特定的功能则需要不同的实现类去实现,这样最大限度地实现了功能和接口的重用, +就如AbstractNioChannel中主要定义了doRegister()、doConnect()、newDirectBuffer()方法。 + +## 1. NioServerSocketChannel源码分析 + +NioServerSocketChannel是AbstractNioMessageChannel的子类,由于它由服务端使用,并且只负责监听Socket的接入,不关心IO的读写,所以与NioSocketChannel相比要简单得多。 + +NioServerSocketChannel封装了NIO中的ServerSocketChannel,并通过newSocket()方法打开了ServerSocketChannel + +NioServerSocketChannel.class + +```java + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException( + "Failed to open a server socket.", e); + } + } +``` + +对于NioServerSocketChannel注册至selector上的操作,是在AbstractNioChannel中实现的,源码如下: + +```java + @Override + protected void doRegister() throws Exception { + boolean selected = false; + for (;;) { + try { + selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + eventLoop().selectNow(); + selected = true; + } else { + throw e; + } + } + } + } +``` + +在ServerSocketChannel的开启,selector上的注册等前期工作完成后,NioServerSocketChannel的开始监听新连接的加入,源码如下: + +```java + @Override + protected int doReadMessages(List buf) throws Exception { + // 拿到jdk底层channel + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + if (ch != null) { + // new出一个NioSocketChannel,将jdk SocketChannel封装成NioSocketChannel,并且这里给NioSocketChannel注册了一个SelectionKey.OP_READ事件 + buf.add(new NioSocketChannel(this, ch)); // 往buf里写入NioSocketChannel + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +``` + +上面的源码展示了Netty最终拿到新连接请求后,将jdk底层的SocketChannel封装NioSocketChannel的过程,那么selector是如何获取到accept事件后,调用到这个doReadMessages方法的呢? + +为了分析原理的延续,故事还要回到bossGroup的NioEventLoop里,当bossGroup启动,NioServerSocketChannel实例新建并注册到selector之后,Netty的bossGroup就会运行一个NioEventLoop,它的核心工作就是作为一个selector一直去监听客户端发出的accept、connect、read、write等事件。具体逻辑查看NioEventLoop#run()方法,详细的原理请回看之前的NioEventLoop的原理分析,此处只分析NioEventLoop#run()获取到链接事件到调用NioServerSocketChannel#doReadMessages()的链路。 + +1. NioEventLoop#run()一直轮训,监听这客户端发出的事件,在轮训过程中如果有任务产生,则会优先执行这些任务,调用非阻塞的selectNow(),否则调用select(deadlineNanos)阻塞指定时间去监听客户端事件。 +2. 调用NioEventLoop#processSelectedKeys(),Netty默认用的是优化过后的selectedKey,所以调用的是NioEventLoop#processSelectedKeysOptimized()方法。 +3. 在processSelectedKeysOptimized方法里会遍历selectedKeys,去拿selectedKeys中的SelectionKey,这个key就是从网络中获取到的感兴趣事件。 +4. 先通过SelectionKey获取attachment,及对应的事件channel。由于这里是获取的是accept事件,所以SelectionKey#attachment()获取到的是NioServerSocketChannel对象。 +5. 在NioEventLoop#processSelectedKey()方法中,首先拿到NioServerSocketChannel父类AbstractNioMessageChannel中的NioMessageUnsafe对象,接着根据readyOps进行判断,这里当然就是SelectionKey.OP_ACCEPT事件。 +6. 调用NioMessageUnsafe#read()方法,最终该方法调用了NioServerSocketChannel#doReadMessages(),完了之后会新建一个对SelectionKey.OP_READ事件感兴趣的NioSocketChannel对象,并存放在readBuf的一个集合中。 +7. 接着调用ChannelPipeline#fireChannelRead()方法,目的在于最终调用ServerBootstrapAcceptor#channelRead()方法,调用childGroup#register(child),把新建的NioSocketChannel对象注册到selector上。 + +这样,NioServerSocketChannel监听accept事件,接收到客户端连接后,封装客户端的“连接”到NioSocketChannel对象,并注册到selector上,后面的网络IO的读写操作都由这个NioSocketChannel对象来负责处理。 + +上述核心的6步源码如下: + +NioEventLoop.class +```java + @Override + protected void run() { + for (;;) { + try { + try { + switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { + // ... 省略 + case SelectStrategy.SELECT: + select(wakenUp.getAndSet(false)); + // ... 省略 + if (wakenUp.get()) { + selector.wakeup(); + } + // fall through + default: + } + } catch (IOException e) { + rebuildSelector0(); + handleLoopException(e); + continue; + } + // ... 省略 + + // 步骤1 + processSelectedKeys(); + runAllTasks(); + + // ... 省略 + } catch (Throwable t) { + handleLoopException(t); + // ... 省略 + } + } + } +``` + +NioEventLoop.class +```java + // 步骤2 + private void processSelectedKeysOptimized() { + for (int i = 0; i < selectedKeys.size; ++i) { + // 步骤3 + final SelectionKey k = selectedKeys.keys[i]; + selectedKeys.keys[i] = null; + + // 步骤4 + final Object a = k.attachment(); + + if (a instanceof AbstractNioChannel) { + // 步骤5 + processSelectedKey(k, (AbstractNioChannel) a); + } else { + @SuppressWarnings("unchecked") + NioTask task = (NioTask) a; + processSelectedKey(k, task); + } + + if (needsToSelectAgain) { + selectedKeys.reset(i + 1); + + selectAgain(); + i = -1; + } + } + } +``` + +NioEventLoop.class +```java + private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { + final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); + if (!k.isValid()) { + final EventLoop eventLoop; + try { + eventLoop = ch.eventLoop(); + } catch (Throwable ignored) { + return; + } + if (eventLoop != this || eventLoop == null) { + return; + } + unsafe.close(unsafe.voidPromise()); + return; + } + + try { + int readyOps = k.readyOps(); + if ((readyOps & SelectionKey.OP_CONNECT) != 0) { + int ops = k.interestOps(); + ops &= ~SelectionKey.OP_CONNECT; + k.interestOps(ops); + + unsafe.finishConnect(); + } + + if ((readyOps & SelectionKey.OP_WRITE) != 0) { + ch.unsafe().forceFlush(); + } + + // 步骤5 + if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { + unsafe.read(); + } + } catch (CancelledKeyException ignored) { + unsafe.close(unsafe.voidPromise()); + } + } +``` + +NioServerSocketChannel.class + +```java + @Override + protected int doReadMessages(List buf) throws Exception { + // 拿到jdk 的SocketChannel,代表着和客户端的一个连接socket + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + if (ch != null) { + // 步骤6 + // 封装一个NioSocketChannel对象,并且设置感兴趣事件为:SelectionKey.OP_READ + buf.add(new NioSocketChannel(this, ch)); + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +``` + +ServerBootstrapAcceptor.class + +```java + public void channelRead(ChannelHandlerContext ctx, Object msg) { + final Channel child = (Channel) msg; + + child.pipeline().addLast(childHandler); + + setChannelOptions(child, childOptions, logger); + setAttributes(child, childAttrs); + + try { + // 步骤7 + // 在workerGroup的NioEventLoop上的selector注册了NioSocketChannel + childGroup.register(child).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + forceClose(child, future.cause()); + } + } + }); + } catch (Throwable t) { + forceClose(child, t); + } + } +``` + +以上就是Netty中有关NioServerSocketChannel读取数据的底层原理分析。 + +下一篇分析NioSocketChannel的发送、读取数据底层原理。 \ No newline at end of file diff --git "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" index 436b444..248f495 100644 --- "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" +++ "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" @@ -28,7 +28,7 @@ ApplicationContext bf = new ClassPathXmlApplicationContext("applicationContext.x ### DefaultListableBeanFactory DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。下面看看DefaultListableBeanFactory的层次结构图。 -![图片1](https://note.youdao.com/yws/api/personal/file/A91C9C5BB33B48A4B501435C157FFD99?method=download&shareKey=2b9a7ef7fd42d051fec83fe3f5eef7a8) +![spring-01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-01.png) 从上往下开始介绍各个类以及接口的作用: - AliasRegistry(接口):alias指的是bean的别名,而aliasRegistry定义了对alias的增删改查等操作。 - SimpleAliasRegistry(类):主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。 @@ -48,7 +48,7 @@ DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册 ### XmlBeanDefinitionReader XML配置文件的读取是Spring中最重要的功能,因为Spring的大部分功能都是以配置作为切入点的,XmlBeanDefinitionReader实现了对资源文件的读取、解析以及注册。先看一下XmlBeanDefinitionReader的层次结构图。 -![图片2](https://note.youdao.com/yws/api/personal/file/477FF4A409A94CBB8CF9A05A16D8F7D4?method=download&shareKey=de7d24b623d4c5bb7e65bb440438e271) +![spring-02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-02.png) - EnvironmentCapable(接口):定义获取Environment方法,Environment代表了配置文件。 - BeanDefinitionReader(接口):主要定义资源文件读取并转换为BeanDefinition的各个功能。 @@ -96,7 +96,7 @@ Person{name='Bruis', age=23} ** 前方高能 ** -![图片3](https://note.youdao.com/yws/api/personal/file/219238FD61C146C99E137E303D52EA66?method=download&shareKey=d5e5aaa1e9fa782eeb056b89119c3565) +![spring-03](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-03.jpg) 通过在断点debug,跟踪程序运行。 @@ -146,7 +146,7 @@ public void setConfigLocations(String... locations) { 下面我们来重点看看refresh()过程。 -![Image](https://note.youdao.com/yws/api/personal/file/76AE8FEDAFF54B6881C336B056AC5B0A?method=download&shareKey=430f5263180efd8467df6e6434456f3d) +![spring-04](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-04.jpg) 1. AbstractApplicationContext.class ```Java @@ -239,8 +239,8 @@ protected final void refreshBeanFactory() throws BeansException { } ``` 这里先看看上面代码的loadBeanDefinitions()方法运行完后的结果 -![图片](https://note.youdao.com/yws/api/personal/file/59FBCD3CC1B54136A05309EA6B88FEB3?method=download&shareKey=80bdcfcbde0362b73eb633390c5b1042) -![图片](https://note.youdao.com/yws/api/personal/file/E258907852284A6F93A2C305319EBB64?method=download&shareKey=7e1dba96d3b53ca9b6af017552f8fd31) +![spring-05](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-05.png) +![spring-06](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-06.png) 从图中可以知道,loadBeanDefinitions()方法运行完后,在beanFactory变量里面存放着一个ConcurrentHashMap变量,用于存放着person这个KV键值对,Key为person,Value为一个ArrayList的变量,里面存放着person的两个属性:age、name。 那么,person的属性是怎么被封装到beanFactory里面的呢?请看下面的源码解析。 @@ -409,7 +409,7 @@ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 下面,继续深入registerBeanDefinitions方法。 -![图片](https://note.youdao.com/yws/api/personal/file/861658D89B0D4B48A7ED56B554CF3028?method=download&shareKey=c3bc974e751495bac74d9ac9ec56cb75) +![spring-07](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-07.jpg) 1. XmlBeanDefinitionReader.class ```Java @@ -636,8 +636,8 @@ public void parsePropertyElement(Element ele, BeanDefinition bd) { } ``` -![Images](https://note.youdao.com/yws/api/personal/file/75CAC9D21AD64BAB89B0D25C8BBE7598?method=download&shareKey=89e73cf46fe18b1b85aecf8d58006f8e) -![Images](https://note.youdao.com/yws/api/personal/file/CF65BB80EB934EBEBA49466CFAB261A0?method=download&shareKey=8b9f0078cf5a3171dfd69d00d9ba55f6) +![spring-08](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-08.png) +![spring-09](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-09.png) 然后,就会一路返回到refresh()方法里的加载bean定义信息的方法——loadBeanDefinitions(),此时beanFactory里面就会存在一个带有KV对的ConcurrentHashMap,而这个beanFactory会存放在Spring容器里面。 ```Java @@ -648,8 +648,8 @@ customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); ``` 再看看DefaultListableBeanFactory里面的内容 -![Images](https://note.youdao.com/yws/api/personal/file/59FBCD3CC1B54136A05309EA6B88FEB3?method=download&shareKey=80bdcfcbde0362b73eb633390c5b1042) -![Images](https://note.youdao.com/yws/api/personal/file/E258907852284A6F93A2C305319EBB64?method=download&shareKey=7e1dba96d3b53ca9b6af017552f8fd31) +![spring-10](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-10.png) +![spring-11](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-11.png) 上面的过程,就已经完成了Spring容器的初始化过程,相信读者也已经对Spring容器的初始化有了一个大致的了解。下面总结一下Spring容器的初始化: - 第一个过程是Resource定位过程。这个Resource定位过程指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。这个定位过程类似于容器寻找数据的过程,就像使用水桶装水先要把水找到一样。 @@ -670,7 +670,7 @@ bean的创建和初始化过程是在refresh方法里的invokeBeanFactoryPostPro - 当容器关闭时,调用Bean的销毁方法 下面先看看创建bean和初始化bean的时序图。 -![Images](https://note.youdao.com/yws/api/personal/file/8B415614A97D45B481925159264C344F?method=download&shareKey=1083828cfcea581b0aa5cae56e3f3090) +![spring-12](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-12.jpg) 1. AbstractApplicationContext.class ```Java @@ -967,7 +967,7 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { ``` 无图无真相: -![Images](https://note.youdao.com/yws/api/personal/file/4C30C0DA143E422FBD27E50AE71AC179?method=download&shareKey=2f4dff65df0e9761ede47d26782dd977) +![spring-13](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-13.png) 5. AbstractAutowireCapableBeanFactory.class ```Java diff --git "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" index 7f8691e..89fdf81 100644 --- "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" +++ "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" @@ -79,7 +79,7 @@ SpringIOC容器是如何在Web环境中被加载并起作用的?SpringIOC容 IOC容器的启动过程就是建立Spring上下文的过程,该上下文是与ServletContext相伴而生的,同时也是IOC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上,还有一个与Web MVC相关的上下文应用来保存控制器(DispatcherServlet)需要的MVC对象,**作为根上下文的子上下文**,构成一个层次化的上下文体系,这个与Web MVC相关的上下文——WebApplicationContext。在Web容器中启动Spring应用程序时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由ContextLoader来完成的。简单点说,ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。 先看看Web程序启动到SpringIOC容器创建和初始化的整个过程。 -![image](https://note.youdao.com/yws/api/personal/file/9755412D703C4DE287B26AF2396E57BD?method=download&shareKey=52783ceb34f405ad47140c22da34275e) +![spring-14](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-14.jpg) 结合着时序图,再去调试源码,思路会清晰很多。 @@ -272,7 +272,7 @@ protected Class determineContextClass(ServletContext servletContext) { ``` 下面看看默认的IOC容器是什么。有图有真相: -![image](https://note.youdao.com/yws/api/personal/file/AB1007BC2A7549D7898417D6231AE4E3?method=download&shareKey=e851d344aedd461f319dba3b8e2c6fe8) +![spring-15](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-15.jpg) ```Java protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { diff --git "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" index dbff5b4..868e146 100644 --- "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" +++ "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" @@ -1,7 +1,7 @@ ## 前言 前一章已经讲解了SpringBoot的SpringFactoriesLoader类的功能以及作用,即读取spring.factories文件中的工厂类,其中就包括了系统初始化器。在SpringBoot中,系统初始化器名称为ApplicationContextInitializer,它是一个接口,只定义了一个initialize方法。下面将详细介绍下SpringBoot的系统初始化器的原理以及作用,并且自定义一个系统初始化器,并在此基础上讲解下常见的使用场景。 -**SpringBoot版本:5.2.1.RELEASE** +**SpringBoot 版本:2.2.1.RELEASE** ## 正文 diff --git a/note/images/JDK/volatile-01.png b/note/images/JDK/volatile-01.png new file mode 100644 index 0000000..61eb672 Binary files /dev/null and b/note/images/JDK/volatile-01.png differ diff --git a/note/images/JDK/volatile-02.png b/note/images/JDK/volatile-02.png new file mode 100644 index 0000000..a288136 Binary files /dev/null and b/note/images/JDK/volatile-02.png differ diff --git a/note/images/JDK/volatile-03.png b/note/images/JDK/volatile-03.png new file mode 100644 index 0000000..7ace8b9 Binary files /dev/null and b/note/images/JDK/volatile-03.png differ diff --git a/note/images/JDK/volatile-04.png b/note/images/JDK/volatile-04.png new file mode 100644 index 0000000..44b287a Binary files /dev/null and b/note/images/JDK/volatile-04.png differ diff --git a/note/images/spring/spring-01.png b/note/images/spring/spring-01.png new file mode 100644 index 0000000..26d87c9 Binary files /dev/null and b/note/images/spring/spring-01.png differ diff --git a/note/images/spring/spring-02.png b/note/images/spring/spring-02.png new file mode 100644 index 0000000..e309335 Binary files /dev/null and b/note/images/spring/spring-02.png differ diff --git a/note/images/spring/spring-03.jpg b/note/images/spring/spring-03.jpg new file mode 100644 index 0000000..83952d4 Binary files /dev/null and b/note/images/spring/spring-03.jpg differ diff --git a/note/images/spring/spring-04.jpg b/note/images/spring/spring-04.jpg new file mode 100644 index 0000000..654d4ec Binary files /dev/null and b/note/images/spring/spring-04.jpg differ diff --git a/note/images/spring/spring-05.png b/note/images/spring/spring-05.png new file mode 100644 index 0000000..6191186 Binary files /dev/null and b/note/images/spring/spring-05.png differ diff --git a/note/images/spring/spring-06.png b/note/images/spring/spring-06.png new file mode 100644 index 0000000..f6912dc Binary files /dev/null and b/note/images/spring/spring-06.png differ diff --git a/note/images/spring/spring-07.jpg b/note/images/spring/spring-07.jpg new file mode 100644 index 0000000..cf74eaf Binary files /dev/null and b/note/images/spring/spring-07.jpg differ diff --git a/note/images/spring/spring-08.png b/note/images/spring/spring-08.png new file mode 100644 index 0000000..4f12e5f Binary files /dev/null and b/note/images/spring/spring-08.png differ diff --git a/note/images/spring/spring-09.png b/note/images/spring/spring-09.png new file mode 100644 index 0000000..baff012 Binary files /dev/null and b/note/images/spring/spring-09.png differ diff --git a/note/images/spring/spring-10.png b/note/images/spring/spring-10.png new file mode 100644 index 0000000..6191186 Binary files /dev/null and b/note/images/spring/spring-10.png differ diff --git a/note/images/spring/spring-11.png b/note/images/spring/spring-11.png new file mode 100644 index 0000000..f6912dc Binary files /dev/null and b/note/images/spring/spring-11.png differ diff --git a/note/images/spring/spring-12.jpg b/note/images/spring/spring-12.jpg new file mode 100644 index 0000000..ee53f53 Binary files /dev/null and b/note/images/spring/spring-12.jpg differ diff --git a/note/images/spring/spring-13.png b/note/images/spring/spring-13.png new file mode 100644 index 0000000..5f32cb1 Binary files /dev/null and b/note/images/spring/spring-13.png differ diff --git a/note/images/spring/spring-14.jpg b/note/images/spring/spring-14.jpg new file mode 100644 index 0000000..fe86220 Binary files /dev/null and b/note/images/spring/spring-14.jpg differ diff --git a/note/images/spring/spring-15.png b/note/images/spring/spring-15.png new file mode 100644 index 0000000..1309372 Binary files /dev/null and b/note/images/spring/spring-15.png differ