[
  {
    "path": ".gitignore",
    "content": "# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n*.DS_Store\n*.iml\n.idea/\ntarget/\n\n"
  },
  {
    "path": "README.md",
    "content": "# spring boot netty\n\n### 资源库说明\n\n首先,不是一个项目.而是一个DEMO性质的代码分享.\n\n**当前资源库中包含了两大部分**\n\n1. netty-demo下的可以在项目中使用的netty与SpringBoot集成的demo\n2. 在study-netty下的netty相关的基础学习demo\n\n## netty-demo\n\n#### 简述\n这是我之前博客中的集成代码,当然,如今的代码已经不完全是博文中所述那样.这里做了相应的优化与升级.主要包含以下几点:\n\n1. 性能优化,不再循环中与服务端创建连接.\n2. 代码优化,客户端代码添加了更多的注释,以便理解这样的做法.\n3. 功能优化,添加了保持长连接的心跳检测功能,并添加了测试类模拟测试.\n\n原博文地址: [开源中国](https://my.oschina.net/yzwjyw/blog/1614889) [CSDN](http://blog.csdn.net/yuanzhenwei521/article/details/79194275) [博客园](http://www.cnblogs.com/tdg-yyx/p/8376842.html)\n\n#### PS\n\n具体项目说明详见内部项目的说明文档\n\n## study-netty\n\n#### 简述\n\n当前目录下的项目为netty基础学习demo.\n\n#### PS\n\n具体项目说明详见内部项目的说明文档\n\n## End\n\n#### 作者QQ: 562638362\n\n#### 作者邮箱：marquis_xuan@163.com\n\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>spring-boot-netty</artifactId>\n        <groupId>org.yyx.netty</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>netty-commons</artifactId>\n\n\n</project>"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/MethodInvokeMeta.java",
    "content": "package org.yyx.netty.entity;\n\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\nimport lombok.ToString;\n\nimport java.io.Serializable;\n\n/**\n * 记录调用方法的元信息\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/14 - 22:46\n */\n@Data\n@NoArgsConstructor\n@EqualsAndHashCode\n@ToString\npublic class MethodInvokeMeta implements Serializable {\n    private static final long serialVersionUID = 5429914235135594820L;\n    /**\n     * 接口\n     */\n    private Class<?> interfaceClass;\n    /**\n     * 方法名\n     */\n    private String methodName;\n    /**\n     * 参数\n     */\n    private Object[] args;\n    /**\n     * 返回值类型\n     */\n    private Class<?> returnType;\n    /**\n     * 参数类型\n     */\n    private Class<?>[] parameterTypes;\n\n    public Object[] getArgs() {\n        return args;\n    }\n\n    public void setArgs(Object[] args) {\n        this.args = args;\n    }\n\n    public Class<?> getInterfaceClass() {\n        return interfaceClass;\n    }\n\n    public void setInterfaceClass(Class<?> interfaceClass) {\n        this.interfaceClass = interfaceClass;\n    }\n\n    public String getMethodName() {\n        return methodName;\n    }\n\n    public void setMethodName(String methodName) {\n        this.methodName = methodName;\n    }\n\n    public Class[] getParameterTypes() {\n        return parameterTypes;\n    }\n\n    public void setParameterTypes(Class<?>[] parameterTypes) {\n        this.parameterTypes = parameterTypes;\n    }\n\n    public Class getReturnType() {\n        return returnType;\n    }\n\n    public void setReturnType(Class returnType) {\n        this.returnType = returnType;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/NullWritable.java",
    "content": "package org.yyx.netty.entity;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * 服务器可能返回空的处理\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 11:57\n */\n@Data\npublic class NullWritable implements Serializable {\n    /**\n     * 序列化标识\n     */\n    private static final long serialVersionUID = 2123827169429254101L;\n    /**\n     * 单例\n     */\n    private static NullWritable instance = new NullWritable();\n\n    /**\n     * 私有构造器\n     */\n    private NullWritable() {\n    }\n\n    /**\n     * 返回代表Null的对象\n     *\n     * @return {@link NullWritable} 当方法返回值为void时或返回值为null时返回此对象\n     */\n    public static NullWritable nullWritable() {\n        return instance;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/User.java",
    "content": "package org.yyx.netty.entity;\n\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.msgpack.annotation.Message;\n\nimport java.io.Serializable;\nimport java.nio.ByteBuffer;\n\n/**\n * 用户实体\n * <p>\n * create by 叶云轩 at 2018/3/3-下午1:48\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 11:55\n */\n@Data\n@NoArgsConstructor\n@Message\npublic class User implements Serializable {\n    /**\n     * 序列化标识\n     */\n    private static final long serialVersionUID = -5462474276911290451L;\n    /**\n     * 编号\n     */\n    private int id;\n    /**\n     * 姓名\n     */\n    private String name;\n    /**\n     * 分数\n     */\n    private double source;\n    /**\n     * 领导\n     */\n    private User leader;\n\n    public byte[] codeC() {\n        ByteBuffer allocate = ByteBuffer.allocate(1024);\n        byte[] bytes = this.name.getBytes();\n        allocate.putInt(bytes.length);\n        allocate.put(bytes);\n        allocate.putInt(this.id);\n        allocate.flip();\n        bytes = null;\n        byte[] res = new byte[allocate.remaining()];\n        allocate.get(res);\n        return res;\n    }\n\n    public byte[] codeC(ByteBuffer buffer) {\n        buffer.clear();\n        byte[] bytes = this.name.getBytes();\n        buffer.putInt(bytes.length);\n        buffer.put(bytes);\n        buffer.putInt(this.id);\n        buffer.flip();\n        bytes = null;\n        byte[] res = new byte[buffer.remaining()];\n        buffer.get(res);\n        return res;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/exception/ErrorParamsException.java",
    "content": "package org.yyx.netty.exception;\n\n/**\n * 参数错误异常\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-14:41\n */\npublic class ErrorParamsException extends RuntimeException {\n    private static final long serialVersionUID = -623198335011996153L;\n\n    public ErrorParamsException() {\n        super();\n    }\n\n    public ErrorParamsException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/exception/NoUseableChannel.java",
    "content": "package org.yyx.netty.exception;\n\n/**\n * 没有可用的通道异常\n * <p>\n *\n * @author 叶云轩 at marquis_xuan@163.com\n * @date 2018/11/2-16:00\n */\npublic class NoUseableChannel extends RuntimeException{\n    private static final long serialVersionUID = 7762465537123947683L;\n\n    public NoUseableChannel() {\n        super();\n    }\n\n    public NoUseableChannel(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/util/ObjectCodec.java",
    "content": "package org.yyx.netty.util;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageCodec;\n\nimport java.util.List;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/3/3-下午1:42\n * contact by tdg_yyx@foxmail.com\n */\npublic class ObjectCodec extends MessageToMessageCodec<ByteBuf, Object> {\n\n    @Override\n    protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) {\n        byte[] data = ObjectSerializerUtils.serilizer(msg);\n        ByteBuf buf = Unpooled.buffer();\n        buf.writeBytes(data);\n        out.add(buf);\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {\n        byte[] bytes = new byte[msg.readableBytes()];\n        msg.readBytes(bytes);\n        Object deSerilizer = ObjectSerializerUtils.deSerilizer(bytes);\n        out.add(deSerilizer);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/util/ObjectSerializerUtils.java",
    "content": "package org.yyx.netty.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.*;\n\n/**\n * 对象序列化工具\n * <p>\n * create by 叶云轩 at 2018/3/3-下午1:43\n * contact by tdg_yyx@foxmail.com\n */\npublic class ObjectSerializerUtils {\n    /**\n     * ObjectSerializerUtils 日志控制器\n     * Create by 叶云轩 at 2018/3/3 下午1:43\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectSerializerUtils.class);\n\n    /**\n     * 反序列化\n     *\n     * @param data\n     * @return\n     */\n    public static Object deSerilizer(byte[] data) {\n        if (data != null && data.length > 0) {\n            try {\n                ByteArrayInputStream bis = new ByteArrayInputStream(data);\n                ObjectInputStream ois = new ObjectInputStream(bis);\n                return ois.readObject();\n            } catch (Exception e) {\n                LOGGER.info(\"[异常信息] {}\", e.getMessage());\n                e.printStackTrace();\n            }\n            return null;\n        } else {\n            LOGGER.info(\"[反序列化] 入参为空\");\n            return null;\n        }\n    }\n\n    /**\n     * 序列化对象\n     *\n     * @param obj\n     * @return\n     */\n    public static byte[] serilizer(Object obj) {\n        if (obj != null) {\n            try {\n                ByteArrayOutputStream bos = new ByteArrayOutputStream();\n                ObjectOutputStream oos = new ObjectOutputStream(bos);\n                oos.writeObject(obj);\n                oos.flush();\n                oos.close();\n                return bos.toByteArray();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            return null;\n        } else {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/README.md",
    "content": "# Netty-client\n\n> 该项目源自博主项目中实际应用场景。主要用作RPC通信中的客户端处理。\n\n## 阅读说明\n\n### 阅读顺序：\n\nnetty-client阅读顺序参考以下链条\n\n```java\nNettyConfiguration -> NettyBeanScanner -> PackageClassUtils(工具) -> RPCProxyFactoryBean -> WrapMethodUtils(工具) -> NettyClient -> CustomChannelInitializerClient -> ClientChannelHandlerAdapter\n```\n\n由NettyConfiguration入手，获取配置文件中的相关配置。\n\n### 项目阅读\n\n主要见博客：\n\n[叶云轩的知识库](http://xuan.wp.happyqing.com/2018/09/17/netty%E4%B8%8Espring-boot%E7%9A%84%E6%95%B4%E5%90%88/)、[开源中国](https://my.oschina.net/yzwjyw/blog/1614889)、[CSDN](http://blog.csdn.net/yuanzhenwei521/article/details/79194275)、[博客园](http://www.cnblogs.com/tdg-yyx/p/8376842.html)\n\n**更直接的参见源码注释。**\n\n### 更新说明\n\n------\n\n#### 2018-11-01\n\n1. 添加阅读思路，整理待优化细节\n\n------\n\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>netty-client</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>netty-client</name>\n    <description>netty-client with spring boot</description>\n\n    <parent>\n        <groupId>org.yyx.netty</groupId>\n        <artifactId>netty-demo</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.yyx.netty</groupId>\n            <artifactId>netty-commons</artifactId>\n            <version>0.0.1-SNAPSHOT</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/NettyClientApplication.java",
    "content": "package org.yyx.netty;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n/**\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:29\n */\n@SpringBootApplication\npublic class NettyClientApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(NettyClientApplication.class, args);\n    }\n}"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/action/MainAction.java",
    "content": "package org.yyx.netty.action;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\nimport org.yyx.netty.entity.User;\nimport org.yyx.netty.rpc.service.DemoService;\n\nimport javax.annotation.Resource;\n\n/**\n * 主要用来进行模拟测试的类.就不用写接口来进行测试了\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:05\n */\n@Component\npublic class MainAction {\n\n    /**\n     * MainAction 日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MainAction.class);\n\n    /**\n     * 测试业务\n     */\n    @Resource\n    private DemoService demoService;\n\n    /**\n     * 真正远程调用的方法\n     * @throws InterruptedException interruptedException\n     */\n    public void call() throws InterruptedException {\n        // 用于模拟服务器正常启动后，人工调用远程服务代码\n        Thread.sleep(10 * 1000);\n        LOGGER.warn(\"[准备进行业务测试]\");\n        LOGGER.warn(\"[rpc测试] \");\n        int sum = demoService.sum(5, 8);\n        LOGGER.warn(\"[rpc测试结果] {}\", sum);\n        LOGGER.warn(\"[字符串测试] \");\n        String print = demoService.print();\n        LOGGER.warn(\"[字符串测试结果] {}\", print);\n        LOGGER.warn(\"[对象测试] \");\n        User userInfo = demoService.getUserInfo();\n        LOGGER.warn(\"[对象测试结果] {}\", userInfo);\n        LOGGER.warn(\"[异常测试]\");\n        try {\n            double division = demoService.division(3, 0);\n            LOGGER.warn(\"[异常测试结果] {}\", division);\n        } catch (Exception e) {\n            LOGGER.error(\"[服务器异常] {}\", e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClient.java",
    "content": "package org.yyx.netty.client;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * netty客户端第二个版本\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:08\n */\npublic class NettyClient {\n    /**\n     * NettyClient 志控制器\n     * Create by 叶云轩 at 2018/3/3 下午2:08\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(NettyClient.class);\n    private static int retry = 0;\n    /**\n     * 初始化Bootstrap实例， 此实例是netty客户端应用开发的入口\n     */\n    private Bootstrap bootstrap;\n    /**\n     * 工人线程组\n     */\n    private EventLoopGroup worker;\n    /**\n     * 远程端口\n     */\n    private int port;\n    /**\n     * 远程服务器url\n     */\n    private String url;\n    /**\n     * 默认重连机制为10次\n     */\n    private int MAX_RETRY_TIMES = 10;\n\n    public NettyClient(int port, String url) {\n        this.port = port;\n        this.url = url;\n        bootstrap = new Bootstrap();\n        worker = new NioEventLoopGroup();\n        bootstrap.group(worker);\n        bootstrap.channel(NioSocketChannel.class);\n    }\n\n    public void start() {\n        LOGGER.info(\"{} -> [启动连接] {}:{}\", this.getClass().getName(), url, port);\n        bootstrap.handler(new NettyClientHandler());\n        ChannelFuture f = bootstrap.connect(url, port);\n        try {\n            f.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            retry++;\n            if (retry > MAX_RETRY_TIMES) {\n                throw new RuntimeException(\"调用Wrong\");\n            } else {\n                try {\n                    Thread.sleep(100);\n                } catch (InterruptedException e1) {\n                    e1.printStackTrace();\n                }\n                LOGGER.info(\"第{}次尝试....失败\", retry);\n                start();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientHandler.java",
    "content": "package org.yyx.netty.client;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.LengthFieldBasedFrameDecoder;\nimport io.netty.handler.codec.LengthFieldPrepender;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport org.yyx.netty.util.ObjectCodec;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:17\n */\npublic class NettyClientHandler extends ChannelInitializer<SocketChannel> {\n\n    @Override\n    protected void initChannel(SocketChannel ch) throws Exception {\n        ChannelPipeline pipeline = ch.pipeline();\n        // 基于定长的方式解决粘包/拆包问题\n        pipeline.addLast(new LengthFieldPrepender(2));\n        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 2, 0, 2));\n        // 序列化方式 可使用 MsgPack 或 Protobuf 进行序列化扩展 具体可参考study-netty项目下的相关使用例子\n        pipeline.addLast(new ObjectCodec());\n        // 心跳机制\n        pipeline.addLast(new IdleStateHandler(3, 10, 0, TimeUnit.SECONDS));\n        pipeline.addLast(new NettyClientHandlerAdapter());\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientHandlerAdapter.java",
    "content": "package org.yyx.netty.client;\n\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.rpc.util.ChannelUtil;\n\n/**\n * 自定义的NettyClientHandler\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:20\n */\npublic class NettyClientHandlerAdapter extends ChannelHandlerAdapter {\n\n    /**\n     * NettyClientHandlerAdapter 日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientHandlerAdapter.class);\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.error(\"{} -> [通道异常] {}\", this.getClass().getName(), ctx.channel().id());\n        ChannelUtil.remove(ctx.channel());\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {\n        LOGGER.info(\"{} -> [连接建立成功] {}\", this.getClass().getName(), channelHandlerContext.channel().id());\n        // 注册通道\n        ChannelUtil.registerChannel(channelHandlerContext.channel());\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (!(msg instanceof Exception)) {\n            LOGGER.info(\"{} -> [客户端收到的消息] {}\", this.getClass().getName(), msg);\n        }\n        String longText = ctx.channel().id().asLongText();\n        String resultKey = ChannelUtil.getResultKey(longText);\n        ChannelUtil.calculateResult(resultKey, msg);\n    }\n\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        LOGGER.info(\"{} -> [客户端消息接收完毕] {}\", this.getClass().getName(), ctx.channel().id());\n        super.channelReadComplete(ctx);\n        boolean active = ctx.channel().isActive();\n        LOGGER.info(\"{} -> [此时通道状态] {}\", this.getClass().getName(), active);\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        LOGGER.info(\"{} -> [客户端心跳监测发送] 通道编号：{}\", this.getClass().getName(), ctx.channel().id());\n        if (evt instanceof IdleStateEvent) {\n            ctx.writeAndFlush(\"ping-pong-ping-pong\");\n        } else {\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    @Override\n    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        LOGGER.info(\"{} -> [关闭通道] {}\", this.getClass().getName(), ctx.channel().id());\n        super.close(ctx, promise);\n//        ChannelUtil.remove(ctx.channel());\n    }\n}"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientListener.java",
    "content": "package org.yyx.netty.client;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.stereotype.Component;\nimport org.yyx.netty.action.MainAction;\nimport org.yyx.netty.config.NettyConfig;\n\nimport javax.annotation.Resource;\n\n/**\n * netty客户端监听器\n * <p>\n * 主要用于延迟测试RPC和启动NettyClient\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:03\n */\n@Order(0)\n@Component\npublic class NettyClientListener implements CommandLineRunner {\n    /**\n     * NettyClientListener 日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientListener.class);\n\n    /**\n     * netty客户端配置\n     */\n    @Resource\n    private NettyConfig nettyConfig;\n    /**\n     * 主要用于测试RPC场景的类。集成到自己的业务中就不需要此依赖\n     */\n    @Resource\n    private MainAction mainAction;\n\n    @Override\n    public void run(String... args) throws Exception {\n        LOGGER.info(\"{} -> [准备进行与服务端通信]\", this.getClass().getName());\n        // region 模拟RPC场景\n        Thread t1 = new Thread(() -> {\n            try {\n                mainAction.call();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        });\n        // 使用一个线程模拟Client启动完毕后RPC的场景\n        t1.start();\n        // endregion\n        // 获取服务器监听的端口\n        int port = nettyConfig.getPort();\n        // 获取服务器IP地址\n        String url = nettyConfig.getUrl();\n        // 启动NettyClient\n        new NettyClient(port, url).start();\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/RPCProxyFactoryBean.java",
    "content": "package org.yyx.netty.client;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.config.AbstractFactoryBean;\nimport org.yyx.netty.entity.MethodInvokeMeta;\nimport org.yyx.netty.exception.ErrorParamsException;\nimport org.yyx.netty.rpc.util.ChannelUtil;\nimport org.yyx.netty.rpc.util.WrapMethodUtils;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\nimport java.util.UUID;\n\n/**\n * JDK动态代理类\n * <p>\n *\n * @author 叶云轩 contact by marquis_xuan@163.com\n * @date 2018/11/1 - 15:49\n */\npublic class RPCProxyFactoryBean extends AbstractFactoryBean<Object> implements InvocationHandler {\n    /**\n     * RPCProxyFactoryBean 日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(RPCProxyFactoryBean.class);\n    /**\n     * 远程服务接口\n     */\n    private Class interfaceClass;\n\n    @Override\n    public Class<?> getObjectType() {\n        return interfaceClass;\n    }\n\n    /**\n     * 创建实例的方法\n     *\n     * @return 由工厂创建的实例\n     */\n    @Override\n    protected Object createInstance() {\n        LOGGER.info(\"[代理工厂] 初始化代理Bean : {}\", interfaceClass);\n        // 返回代理类\n        return Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this);\n    }\n\n    /**\n     * 动态调用方法的方法\n     * 该方法不会显示调用\n     *\n     * @param proxy  被代理的实例\n     * @param method 调用的方法\n     * @param args   参数列表\n     * @return 返回值\n     */\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws ErrorParamsException {\n        LOGGER.info(\"{} -> [准备进行远程服务调用] \", this.getClass().getName());\n        LOGGER.info(\"{} -> [封装调用信息] \", this.getClass().getName());\n        final MethodInvokeMeta methodInvokeMeta = WrapMethodUtils.readMethod(interfaceClass, method, args);\n        LOGGER.info(\"{} -> [远程服务调用封装完毕] 调用接口 -> {}\\n调用方法 -> {}\\n参数列表 -> {} \\n 参数类型 -> {}\" +\n                        \"\\n 返回值类型 -> {}\", this.getClass().getName(), methodInvokeMeta.getInterfaceClass(), methodInvokeMeta.getMethodName()\n                , methodInvokeMeta.getArgs(), methodInvokeMeta.getParameterTypes(), methodInvokeMeta.getReturnType());\n        // 构造一个时间戳\n        String uuid = System.currentTimeMillis() + UUID.randomUUID().toString();\n        // 真正开始使用netty进行通信的方法\n        ChannelUtil.remoteCall(methodInvokeMeta, uuid);\n        Object result;\n        do {\n            // 接收返回信息\n            result = ChannelUtil.getResult(uuid);\n        } while (result == null);\n        // 服务器有可能返回异常信息，所以在这里可以进行异常处理\n        if (result instanceof ErrorParamsException) {\n            throw (ErrorParamsException) result;\n        }\n        return result;\n    }\n\n    public void setInterfaceClass(Class interfaceClass) {\n        this.interfaceClass = interfaceClass;\n    }\n\n}"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/config/NettyConfig.java",
    "content": "package org.yyx.netty.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * netty客户端配置\n * <p>\n *\n * @author 叶云轩 at tdg_yyx@foxmail.com\n * @date 2018/11/1-17:13\n */\n@Component\n@ConfigurationProperties(prefix = \"netty\")\n@Data\npublic class NettyConfig {\n\n    private String url;\n\n    private int port;\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/config/NettyConfiguration.java",
    "content": "package org.yyx.netty.config;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.yyx.netty.rpc.util.NettyBeanScanner;\n\n/**\n * Netty相关的初始化入口\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:32\n */\n@Configuration\npublic class NettyConfiguration {\n\n    /**\n     * 初始化加载Netty相关bean的配置方法\n     *\n     * @param basePackage 配置的包名\n     * @param clientName  配置的Netty实例对象名\n     * @return NettyBeanScanner\n     */\n    @Bean\n    public static NettyBeanScanner initNettyBeanScanner(@Value(\"${netty.basePackage}\") String basePackage,\n                                                        @Value(\"${netty.clientName}\") String clientName) {\n        // 创建对象\n        return new NettyBeanScanner(basePackage, clientName);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/service/DemoService.java",
    "content": "package org.yyx.netty.rpc.service;\n\nimport org.yyx.netty.entity.User;\nimport org.yyx.netty.exception.ErrorParamsException;\n\n/**\n * 测试Service\n * <p>\n * create by 叶云轩 at 2018/3/3-下午1:46\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:29\n */\npublic interface DemoService {\n\n    /**\n     * 除法运算\n     *\n     * @param numberA 第一个数\n     * @param numberB 第二个数\n     * @return 结果\n     */\n    double division(int numberA, int numberB) throws ErrorParamsException;\n\n    /**\n     * 获取用户信息\n     *\n     * @return 用户信息\n     */\n    User getUserInfo();\n\n    /**\n     * 打印方法\n     *\n     * @return 一个字符串\n     */\n    String print();\n\n    /**\n     * 求和方法\n     *\n     * @param numberA 第一个数\n     * @param numberB 第二个数\n     * @return 两数之和\n     */\n    int sum(int numberA, int numberB);\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/ChannelUtil.java",
    "content": "package org.yyx.netty.rpc.util;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelId;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.entity.MethodInvokeMeta;\nimport org.yyx.netty.exception.NoUseableChannel;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentSkipListSet;\n\n/**\n * 通道Util\n * <p>\n *\n * @author 叶云轩 at marquis_xuan@163.com\n * @date 2018/11/2-15:39\n */\npublic class ChannelUtil {\n    /**\n     * 用于记录c-s连接后建立的通道\n     */\n    private static final Set<Channel> CHANNELS = new ConcurrentSkipListSet<>();\n    /**\n     * ChannelUtil日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ChannelUtil.class);\n    /**\n     * 用于记录通道响应的结果集\n     */\n    private static final Map<String, Object> RESULT_MAP = new ConcurrentHashMap<>();\n\n    private ChannelUtil() {\n    }\n\n    /**\n     * 计算结果集（存储响应结果）\n     *\n     * @param key    唯一标识\n     * @param result 结果集\n     */\n    public static void calculateResult(String key, Object result) {\n        RESULT_MAP.put(key, result);\n    }\n\n    /**\n     * 获取结果集的key\n     *\n     * @param key 保存的唯一标识\n     * @return 结果集的 key (通道标识)\n     */\n    public static String getResultKey(String key) {\n        return (String) getResult(key);\n    }\n\n    /**\n     * 根据结果集的 key 获取结果集\n     *\n     * @param key 结果集的key\n     * @return 结果集\n     */\n    public static Object getResult(String key) {\n        return RESULT_MAP.get(key);\n    }\n\n    /**\n     * 注册通道\n     *\n     * @param channel 通道\n     */\n    public static void registerChannel(Channel channel) {\n        ChannelId id = channel.id();\n        LOGGER.info(\"{} -> [添加通道] {}\", ChannelUtil.class.getName(), id);\n        CHANNELS.add(channel);\n    }\n\n    /**\n     * 获取回调结果\n     *\n     * @param methodInvokeMeta 远程调用方法信息\n     * @param key              用于取结果的key值\n     */\n    public static void remoteCall(MethodInvokeMeta methodInvokeMeta, String key) {\n        LOGGER.info(\"{} -> [远程调用] \", ChannelUtil.class.getName());\n        Iterator<Channel> iterator = CHANNELS.iterator();\n        Channel channel;\n        if (iterator.hasNext()) {\n            channel = iterator.next();\n        } else {\n            LOGGER.error(\"{} -> [没有活跃的通道] \", ChannelUtil.class);\n            throw new NoUseableChannel(\"没有活跃的通道\");\n        }\n        // 将用于获取结果的key保存,以通道id为键\n        String channelID = channel.id().asLongText();\n        LOGGER.info(\"{} -> [保存获取结果的key] key - {} 通道id - {}\", ChannelUtil.class, key, channelID);\n        RESULT_MAP.put(channelID, key);\n        ChannelFuture channelFuture = channel.writeAndFlush(methodInvokeMeta);\n//        channelFuture.addListener(ChannelFutureListener.CLOSE);\n    }\n\n    public static void remove(Channel channel) {\n        CHANNELS.remove(channel);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/NettyBeanScanner.java",
    "content": "package org.yyx.netty.rpc.util;\n\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanFactoryPostProcessor;\nimport org.springframework.beans.factory.config.ConfigurableListableBeanFactory;\nimport org.springframework.beans.factory.support.BeanDefinitionBuilder;\nimport org.springframework.beans.factory.support.DefaultListableBeanFactory;\nimport org.yyx.netty.client.RPCProxyFactoryBean;\n\nimport java.util.List;\n\n/**\n * 主要用于Netty框架初始化远程服务类\n * <p>\n * BeanFactoryPostProcessor : Spring初始化bean时对外暴露的扩展点,即可以在Spring工厂初始化的时候做点什么，属于Spring知识点\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:28\n */\npublic class NettyBeanScanner implements BeanFactoryPostProcessor {\n\n    /**\n     * 装载bean的工厂\n     */\n    private DefaultListableBeanFactory beanFactory;\n    /**\n     * 包名\n     */\n    private String basePackage;\n    /**\n     * bean名（引用名）\n     */\n    private String clientName;\n\n    /**\n     * 有参构造\n     *\n     * @param basePackage 待扫描包名\n     * @param clientName  netty客户端beanName\n     */\n    public NettyBeanScanner(String basePackage, String clientName) {\n        this.basePackage = basePackage;\n        this.clientName = clientName;\n    }\n\n\n    /**\n     * 注册远程接口Bean到Spring的bean工厂\n     *\n     * @param beanFactory 装载bean的工厂\n     * @throws BeansException bean异常\n     */\n    @Override\n    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {\n        this.beanFactory = (DefaultListableBeanFactory) beanFactory;\n        // 从目录中加载远程服务的接口\n        List<String> resolverClass = PackageClassUtils.resolver(basePackage);\n        for (String clazz : resolverClass) {\n            // 获取接口名\n            String simpleName;\n            // 接口全限定名\n            if (clazz.lastIndexOf('.') != -1) {\n                simpleName = clazz.substring(clazz.lastIndexOf('.') + 1);\n            } else {\n                simpleName = clazz;\n            }\n            // 使用建造者模式创建一个Bean定义\n            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RPCProxyFactoryBean.class);\n            // 对应 RPCProxyFactoryBean 类的 interfaceClass 属性\n            beanDefinitionBuilder.addPropertyValue(\"interfaceClass\", clazz);\n            // 对应 RPCProxyFactoryBean 的nettyClient 属性  --  已删\n//            beanDefinitionBuilder.addPropertyReference(\"nettyClient\", clientName);\n            // 注册对bean的定义\n            this.beanFactory.registerBeanDefinition(simpleName, beanDefinitionBuilder.getRawBeanDefinition());\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/PackageClassUtils.java",
    "content": "package org.yyx.netty.rpc.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:28\n */\npublic class PackageClassUtils {\n    private final static Logger LOGGER = LoggerFactory.getLogger(PackageClassUtils.class);\n\n    /**\n     * 获取一个目录下的所有文件\n     *\n     * @param s\n     * @param file\n     * @param classStrs\n     */\n    private static void getAllFile(String s, File file, List<String> classStrs) {\n        if (file.isDirectory()) {\n            File[] files = file.listFiles();\n            if (files != null)\n                for (File file1 : files) {\n                    getAllFile(s, file1, classStrs);\n                }\n        } else {\n            String path = file.getPath();\n            String cleanPath = path.replaceAll(\"/\", \".\");\n            String fileName = cleanPath.substring(cleanPath.indexOf(s), cleanPath.length());\n            LOGGER.info(\"[加载完成] 类文件：{}\", fileName);\n            classStrs.add(fileName);\n        }\n    }\n\n    /**\n     * 添加全限定类名到集合\n     *\n     * @param classStrs 集合\n     * @return 类名集合\n     */\n    private static List<String> getClassReferenceList(List<String> classStrs, File file, String s) {\n        File[] listFiles = file.listFiles();\n        if (listFiles != null && listFiles.length != 0) {\n            for (File file2 : listFiles) {\n                if (file2.isFile()) {\n                    String name = file2.getName();\n                    String fileName = s + \".\" + name.substring(0, name.lastIndexOf('.'));\n                    LOGGER.info(\"[加载完成] 类文件：{}\", fileName);\n                    classStrs.add(fileName);\n                }\n            }\n        }\n        return classStrs;\n    }\n\n    /**\n     * 解析包参数\n     *\n     * @param basePackage 包名\n     * @return 包名字符串集合\n     */\n    public static List<String> resolver(String basePackage) {\n        // 以\";\"分割开多个包名\n        String[] splitFHs = basePackage.split(\";\");\n        List<String> classStrs = new ArrayList<>();\n        // s: com.yyx.util.*\n        for (String s : splitFHs) {\n            LOGGER.info(\"[加载类目录] {}\", s);\n            // 路径中是否存在\".*\" com.yyx.util.*\n            boolean contains = s.contains(\".*\");\n            if (contains) {\n                // 截断星号  com.yyx.util\n                String filePathStr = s.substring(0, s.lastIndexOf(\".*\"));\n                // 组装路径 com/yyx/util\n                String filePath = filePathStr.replaceAll(\"\\\\.\", \"/\");\n                // 获取路径 xxx/classes/com/yyx/util\n                File file = new File(PackageClassUtils.class.getResource(\"/\").getPath() + \"/\" + filePath);\n                // 获取目录下获取文件\n                getAllFile(filePathStr, file, classStrs);\n            } else {\n                String filePath = s.replaceAll(\"\\\\.\", \"/\");\n                File file = new File(PackageClassUtils.class.getResource(\"/\").getPath() + \"/\" + filePath);\n                classStrs = getClassReferenceList(classStrs, file, s);\n            }\n        }\n        return classStrs;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/RemoteMethodInvokeUtil.java",
    "content": "package org.yyx.netty.rpc.util;\n\nimport org.springframework.beans.BeansException;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.yyx.netty.entity.MethodInvokeMeta;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:28\n */\npublic class RemoteMethodInvokeUtil implements ApplicationContextAware {\n\n    private ApplicationContext applicationContext;\n\n    public Object processMethod(MethodInvokeMeta methodInvokeMeta) throws InvocationTargetException, IllegalAccessException {\n        Class interfaceClass = methodInvokeMeta.getInterfaceClass();\n        Object bean = applicationContext.getBean(interfaceClass);\n        Method[] declaredMethods = interfaceClass.getDeclaredMethods();\n        Method method = null;\n        for (Method declaredMethod : declaredMethods) {\n            if (methodInvokeMeta.getMethodName().equals(declaredMethod.getName())) {\n                method = declaredMethod;\n            }\n        }\n        Object invoke = method.invoke(bean, methodInvokeMeta.getArgs());\n        return invoke;\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext app) throws BeansException {\n        applicationContext = app;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/WrapMethodUtils.java",
    "content": "package org.yyx.netty.rpc.util;\n\n\nimport org.yyx.netty.entity.MethodInvokeMeta;\n\nimport java.lang.reflect.Method;\n\n/**\n * 封装接口调用的工具\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:27\n */\npublic class WrapMethodUtils {\n    /**\n     * 封装 method 的元数据信息\n     *\n     * @param interfaceClass 接口类\n     * @param method         方法\n     * @param args           参数列表\n     * @return 封装的对象\n     */\n    public static MethodInvokeMeta readMethod(Class interfaceClass, Method method, Object[] args) {\n        MethodInvokeMeta methodInvokeMeta = new MethodInvokeMeta();\n        methodInvokeMeta.setInterfaceClass(interfaceClass);\n        methodInvokeMeta.setArgs(args);\n        methodInvokeMeta.setMethodName(method.getName());\n        methodInvokeMeta.setReturnType(method.getReturnType());\n        Class<?>[] parameterTypes = method.getParameterTypes();\n        methodInvokeMeta.setParameterTypes(parameterTypes);\n        return methodInvokeMeta;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/main/resources/application.yml",
    "content": "netty:\n  port: 12345\n  basePackage: org.yyx.netty.rpc.service;\n  clientName: nettyClient\n  url: 127.0.0.1"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-client/src/test/java/org/yyx/netty/client/NettyClientApplicationTests.java",
    "content": "package org.yyx.netty.client;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class NettyClientApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/README.md",
    "content": "# netty-server\n>博文源码,基于SOA架构的服务端,使用Netty做RPC通信.\n\n这是一个Netty服务端与SpringBoot集成的Demo."
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>netty-server</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>netty-server</name>\n    <description>\n        基于SpringBoot的Netty服务端整合代码\n    </description>\n\n    <parent>\n        <groupId>org.yyx.netty</groupId>\n        <artifactId>netty-demo</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n\n    <!-- region 依赖 -->\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.yyx.netty</groupId>\n            <artifactId>netty-commons</artifactId>\n            <version>0.0.1-SNAPSHOT</version>\n        </dependency>\n    </dependencies>\n    <!-- endregion -->\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/NettyServerApplication.java",
    "content": "package org.yyx.netty;\n\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.yyx.netty.server.listener.NettyServerListener;\n\nimport javax.annotation.Resource;\n\n/**\n * Netty服务器启动类\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:33\n */\n@SpringBootApplication\npublic class NettyServerApplication implements CommandLineRunner {\n\n    @Resource\n    private NettyServerListener nettyServerListener;\n\n    public static void main(String[] args) {\n        SpringApplication.run(NettyServerApplication.class, args);\n    }\n\n    @Override\n    public void run(String... args) {\n        nettyServerListener.start();\n    }\n}"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/rpc/service/DemoService.java",
    "content": "package org.yyx.netty.rpc.service;\n\n\nimport org.yyx.netty.entity.User;\nimport org.yyx.netty.exception.ErrorParamsException;\n\n/**\n * 测试Service\n * <p>\n * create by 叶云轩 at 2018/3/3-下午1:46\n * contact by tdg_yyx@foxmail.com\n */\npublic interface DemoService {\n\n    /**\n     * 除法运算\n     *\n     * @param numberA 第一个数\n     * @param numberB 第二个数\n     * @return 结果\n     */\n    double division(int numberA, int numberB) throws ErrorParamsException;\n\n    /**\n     * 获取用户信息\n     *\n     * @return 用户信息\n     */\n    User getUserInfo();\n\n    /**\n     * 打印方法\n     *\n     * @return 一个字符串\n     */\n    String print();\n\n    /**\n     * 求和方法\n     *\n     * @param numberA 第一个数\n     * @param numberB 第二个数\n     * @return 两数之和\n     */\n    int sum(int numberA, int numberB);\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/adapter/ServerChannelHandlerAdapter.java",
    "content": "package org.yyx.netty.server.adapter;\n\nimport io.netty.channel.ChannelHandler.Sharable;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.timeout.IdleState;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\nimport org.yyx.netty.entity.MethodInvokeMeta;\nimport org.yyx.netty.server.dispatcher.RequestDispatcher;\n\nimport javax.annotation.Resource;\n\n/**\n * NettyServer通道适配器\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:39\n */\n@Component\n@Sharable\npublic class ServerChannelHandlerAdapter extends ChannelHandlerAdapter {\n    /**\n     * ServerChannelHandlerAdapter 日志控制器\n     * Create by 叶云轩 at 2018/3/3 下午1:25\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ServerChannelHandlerAdapter.class);\n    /**\n     * 注入请求分排器\n     */\n    @Resource\n    private RequestDispatcher dispatcher;\n    private int lossConnectCount = 0;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        LOGGER.error(\"{} -> [连接异常] {}通道异常，异常原因：{}\", this.getClass().getName(),\n                ctx.channel().id(), cause.getMessage());\n        ctx.close();\n    }\n\n    /**\n     * 服务器接收到消息时进行进行的处理\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param msg                   msg\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) {\n        if (msg instanceof String) {\n            if (\"ping-pong-ping-pong\".equals(msg)) {\n                LOGGER.info(\"{} -> [心跳监测] {}：通道活跃\", this.getClass().getName(), channelHandlerContext.channel().id());\n                // 心跳消息\n                lossConnectCount = 0;\n                return;\n            }\n        }\n        // 转换为MethodInvokeMeta\n        MethodInvokeMeta invokeMeta = (MethodInvokeMeta) msg;\n        LOGGER.info(\"{} -> [客户端信息] \\n 方法名  - > {} \\n 参数列表  -> {} \\n \" +\n                        \"返回值  ->  {} \", this.getClass().getName(), invokeMeta.getMethodName(), invokeMeta.getArgs()\n                , invokeMeta.getReturnType());\n        // 具体的处理类\n        this.dispatcher.dispatcher(channelHandlerContext, invokeMeta);\n    }\n\n    /**\n     * 触发器\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param evt\n     * @throws Exception exception\n     */\n    @Override\n    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object evt) throws Exception {\n        LOGGER.info(\"{} -> [已经有5秒中没有接收到客户端的消息了]\", this.getClass().getName());\n        if (evt instanceof IdleStateEvent) {\n            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;\n            if (idleStateEvent.state() == IdleState.READER_IDLE) {\n                lossConnectCount++;\n                if (lossConnectCount > 2) {\n                    LOGGER.info(\"{} -> [释放不活跃通道] {}\", this.getClass().getName(), channelHandlerContext.channel().id());\n                    channelHandlerContext.channel().close();\n                }\n            }\n        } else {\n            super.userEventTriggered(channelHandlerContext, evt);\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/config/NettyServerConfig.java",
    "content": "package org.yyx.netty.server.config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * Netty服务器配置信息\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:39\n */\n@Component\n@ConfigurationProperties(prefix = \"netty\")\npublic class NettyServerConfig {\n\n    /**\n     * 端口\n     */\n    private int port;\n    /**\n     * 最大线程数\n     */\n    private int maxThreads;\n    /**\n     * 最大数据包长度\n     */\n    private int maxFrameLength;\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public int getMaxThreads() {\n        return maxThreads;\n    }\n\n    public void setMaxThreads(int maxThreads) {\n        this.maxThreads = maxThreads;\n    }\n\n    public int getMaxFrameLength() {\n        return maxFrameLength;\n    }\n\n    public void setMaxFrameLength(int maxFrameLength) {\n        this.maxFrameLength = maxFrameLength;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/dispatcher/RequestDispatcher.java",
    "content": "package org.yyx.netty.server.dispatcher;\n\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.BeansException;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.stereotype.Component;\nimport org.yyx.netty.entity.MethodInvokeMeta;\nimport org.yyx.netty.entity.NullWritable;\nimport org.yyx.netty.exception.ErrorParamsException;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\n/**\n * 请求分排器\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:37\n */\n@Component\npublic class RequestDispatcher implements ApplicationContextAware {\n\n    /**\n     * RequestDispatcher 日志输出\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(RequestDispatcher.class);\n    /**\n     * Spring上下文\n     */\n    private ApplicationContext applicationContext;\n\n    /**\n     * 发送\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param invokeMeta            invokeMeta:用于记录远程调用的服务信息，即调用哪个接口中的哪个方法\n     */\n    public void dispatcher(final ChannelHandlerContext channelHandlerContext, final MethodInvokeMeta invokeMeta) {\n        ChannelFuture channelFuture = null;\n        // 指向的接口类\n        Class<?> interfaceClass = invokeMeta.getInterfaceClass();\n        // 所调用的方法名\n        String name = invokeMeta.getMethodName();\n        // 方法的参数列表\n        Object[] args = invokeMeta.getArgs();\n        // 方法的参数列表按顺序对应的类型\n        Class<?>[] parameterTypes = invokeMeta.getParameterTypes();\n        // 通过Spring获取实际对象\n        Object targetObject = this.applicationContext.getBean(interfaceClass);\n        // 声明调用的方法对象\n        Method method;\n        try {\n            // 获取调用的方法对象\n            method = targetObject.getClass().getMethod(name, parameterTypes);\n        } catch (NoSuchMethodException e) {\n            // 尚未执行方法调用出现异常\n            LOGGER.error(\"[获取远程方法异常] {}\", e.getMessage());\n            // 响应给客户端\n            channelFuture = channelHandlerContext.writeAndFlush(e);\n            return;\n        } finally {\n            if (channelFuture != null) {\n                channelFuture.addListener(ChannelFutureListener.CLOSE);\n            }\n        }\n        try {\n            // 执行方法\n            Object result = method.invoke(targetObject, args);\n            if (result == null) {\n                // 方法没有返回值，返回NullWritable对象\n                LOGGER.info(\"{} -> [方法没有返回值，返回NullWritable对象]\", this.getClass().getName());\n                channelFuture = channelHandlerContext.writeAndFlush(NullWritable.nullWritable());\n            } else {\n                // 将方法执行结果写入到Channel\n                LOGGER.info(\"{} -> [返回结果] {}\", this.getClass().getName(), result);\n                channelFuture = channelHandlerContext.writeAndFlush(result);\n            }\n            /*\n            虽然可以通过ChannelFuture的get()方法获取异步操作的结果,但完成时间是无法预测的,若不设置超时时间则有可能导致线程长时间被阻塞;\n            若是不能精确的设置超时时间则可能导致I/O操作中断.\n            因此,Netty建议通过GenericFutureListener接口执行异步操作结束后的回调.\n             */\n            /// ChannelFutureListener.CLOSE = new ChannelFutureListener() {\n            ///    @Override\n            ///    public void operationComplete(ChannelFuture future) {\n            ///        future.channel().close();\n            ///    }\n            /// };\n        } catch (Exception e) {\n            Throwable targetException = ((InvocationTargetException) e).getTargetException();\n            LOGGER.error(\"{} -> [方法执行异常] {}\", this.getClass().getName());\n            if (targetException instanceof ErrorParamsException) {\n                LOGGER.error(\"{} -> [异常信息] {}\", this.getClass().getName(), targetException.getMessage());\n            }\n            channelFuture = channelHandlerContext.writeAndFlush(targetException);\n        } finally {\n            if (channelFuture != null) {\n//                channelFuture.addListener(ChannelFutureListener.CLOSE);\n            }\n        }\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        this.applicationContext = applicationContext;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/impl/DemoServiceImpl.java",
    "content": "package org.yyx.netty.server.impl;\n\nimport org.springframework.stereotype.Service;\nimport org.yyx.netty.entity.User;\nimport org.yyx.netty.exception.ErrorParamsException;\nimport org.yyx.netty.rpc.service.DemoService;\n\n/**\n * 测试Service实现类\n * <p>\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/7/9 - 上午9:36\n */\n@Service\npublic class DemoServiceImpl implements DemoService {\n\n    @Override\n    public double division(int numberA, int numberB) throws ErrorParamsException {\n        if (numberB == 0) {\n            throw new ErrorParamsException(\"除数不能为0\");\n        }\n        return numberA / numberB;\n    }\n\n    @Override\n    public User getUserInfo() {\n        User leader = new User();\n        leader.setId(1);\n        leader.setName(\"上级\");\n        leader.setSource(100);\n        User user = new User();\n        user.setSource(80);\n        user.setId(0);\n        user.setName(\"基层\");\n        user.setLeader(leader);\n        return user;\n    }\n\n    @Override\n    public String print() {\n        return \"这是来自服务器中DemoService接口的print方法打印的消息\";\n    }\n\n    @Override\n    public int sum(int numberA, int numberB) {\n        return numberA + numberB;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/listener/NettyServerListener.java",
    "content": "package org.yyx.netty.server.listener;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.codec.LengthFieldBasedFrameDecoder;\nimport io.netty.handler.codec.LengthFieldPrepender;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\nimport org.yyx.netty.server.adapter.ServerChannelHandlerAdapter;\nimport org.yyx.netty.server.config.NettyServerConfig;\nimport org.yyx.netty.util.ObjectCodec;\n\nimport javax.annotation.PreDestroy;\nimport javax.annotation.Resource;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Netty服务器监听器\n * <p>\n * create by 叶云轩 at 2018/3/3-下午12:21\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:26\n */\n@Component\npublic class NettyServerListener {\n    /**\n     * NettyServerListener 日志控制器\n     * Create by 叶云轩 at 2018/3/3 下午12:21\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerListener.class);\n\n    /**\n     * 创建bootstrap\n     */\n    private ServerBootstrap serverBootstrap = new ServerBootstrap();\n    /**\n     * BOSS\n     */\n    private EventLoopGroup boss = new NioEventLoopGroup();\n    /**\n     * Worker\n     */\n    private EventLoopGroup work = new NioEventLoopGroup();\n    /**\n     * 通道适配器\n     */\n    @Resource\n    private ServerChannelHandlerAdapter channelHandlerAdapter;\n    /**\n     * NETT服务器配置类\n     */\n    @Resource\n    private NettyServerConfig nettyConfig;\n\n    /**\n     * 关闭服务器方法\n     */\n    @PreDestroy\n    public void close() {\n        LOGGER.info(\"关闭服务器....\");\n        //优雅退出\n        boss.shutdownGracefully();\n        work.shutdownGracefully();\n    }\n\n    /**\n     * 开启及服务线程\n     */\n    public void start() {\n        // 从配置文件中(application.yml)获取服务端监听端口号\n        int port = nettyConfig.getPort();\n        serverBootstrap.group(boss, work)\n                .channel(NioServerSocketChannel.class)\n                .option(ChannelOption.SO_BACKLOG, 100)\n                .handler(new LoggingHandler(LogLevel.INFO));\n        try {\n            //设置事件处理\n            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {\n                @Override\n                protected void initChannel(SocketChannel ch) {\n                    ChannelPipeline pipeline = ch.pipeline();\n                    // 添加心跳支持\n                    pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));\n                    // 基于定长的方式解决粘包/拆包问题\n                    pipeline.addLast(new LengthFieldBasedFrameDecoder(nettyConfig.getMaxFrameLength()\n                            , 0, 2, 0, 2));\n                    pipeline.addLast(new LengthFieldPrepender(2));\n                    // 序列化\n                    pipeline.addLast(new ObjectCodec());\n                    pipeline.addLast(channelHandlerAdapter);\n                }\n            });\n            LOGGER.info(\"netty服务器在[{}]端口启动监听\", port);\n            ChannelFuture f = serverBootstrap.bind(port).sync();\n            f.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            LOGGER.info(\"[出现异常] 释放资源\");\n            boss.shutdownGracefully();\n            work.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/main/resources/application.yml",
    "content": "# netty配置\nnetty:\n  # 端口号\n  port: 12345\n  # 最大线程数\n  maxThreads: 1024\n  # 数据包的最大长度\n  max_frame_length: 65535"
  },
  {
    "path": "spring-boot-netty/netty-demo/netty-server/src/test/java/org/yyx/netty/NettyServerApplicationTests.java",
    "content": "package org.yyx.netty;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class NettyServerApplicationTests {\n\n\t@Test\n\tpublic void contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-netty/netty-demo/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>netty-demo</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>pom</packaging>\n\n    <name>netty-demo</name>\n    <description>可以正常使用的Netty与SpringBoot集成的Demo</description>\n\n    <parent>\n        <groupId>org.yyx.netty</groupId>\n        <artifactId>spring-boot-netty</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <modules>\n        <module>netty-server</module>\n        <module>netty-client</module>\n    </modules>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n\n    </dependencies>\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "spring-boot-netty/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>org.yyx.netty</groupId>\n    <artifactId>spring-boot-netty</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>pom</packaging>\n\n    <name>spring-boot-netty</name>\n    <description>\n        Netty 学习之路\n        netty-server是基于Netty开发的一款Server端代码\n        netty-client是基于Netty开发的一款Client端代码\n        study-netty是学习Netty的相关Demo，记录了从0到1的整合过程等\n    </description>\n\n    <modules>\n        <module>study-netty</module>\n        <module>netty-commons</module>\n        <module>netty-demo</module>\n    </modules>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.0.1.RELEASE</version>\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n        <netty>5.0.0.Alpha2</netty>\n        <lombok>1.16.20</lombok>\n        <msgpack>0.6.12</msgpack>\n        <fastjson>1.2.47</fastjson>\n        <jboss.marshalling>2.0.5.Final</jboss.marshalling>\n    </properties>\n\n    <dependencies>\n        <!-- region spring-boot-starter -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <!-- endregion -->\n        <!-- region test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <!-- endregion -->\n        <!-- region netty -->\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n            <version>${netty}</version>\n        </dependency>\n        <!-- endregion -->\n        <!-- region lombok -->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>${lombok}</version>\n        </dependency>\n        <!-- endregion -->\n        <!-- region jboss-marshalling -->\n        <!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling -->\n        <dependency>\n            <groupId>org.jboss.marshalling</groupId>\n            <artifactId>jboss-marshalling</artifactId>\n            <version>${jboss.marshalling}</version>\n        </dependency>\n        <!-- endregion -->\n        <!-- region messagePack -->\n        <dependency>\n            <groupId>org.msgpack</groupId>\n            <artifactId>msgpack</artifactId>\n            <version>${msgpack}</version>\n        </dependency>\n        <!-- endregion -->\n        <!-- region fast-json -->\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n            <version>${fastjson}</version>\n        </dependency>\n        <!-- endregion -->\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-netty/study-netty/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>study-netty</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>pom</packaging>\n\n    <name>study-netty</name>\n    <description>study netty</description>\n\n    <modules>\n        <module>study-server</module>\n        <module>study-client</module>\n    </modules>\n\n    <parent>\n        <groupId>org.yyx.netty</groupId>\n        <artifactId>spring-boot-netty</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.yyx.netty</groupId>\n            <artifactId>netty-commons</artifactId>\n            <version>0.0.1-SNAPSHOT</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\n\t<artifactId>study-client</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>study-client</name>\n\t<description>Demo project for Spring Boot</description>\n\n\t<parent>\n\t\t<groupId>org.yyx.netty</groupId>\n\t\t<artifactId>study-netty</artifactId>\n\t\t<version>0.0.1-SNAPSHOT</version>\n\t</parent>\n\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n\n</project>\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/StudyClientApplication.java",
    "content": "package org.yyx.netty.study;\n\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class StudyClientApplication implements CommandLineRunner {\n\n    public static void main(String[] args) {\n        SpringApplication.run(StudyClientApplication.class, args);\n    }\n\n    @Override\n    public void run(String... args) throws Exception {\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackDecoder.java",
    "content": "package org.yyx.netty.study.codec.msgpack;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageDecoder;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\n\n/**\n * MsgPack解码器\n * <p>\n * create by 叶云轩 at 2018/4/12-下午7:31\n * contact by tdg_yyx@foxmail.com\n */\npublic class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {\n    /**\n     * MsgPackDecoder 日志控制器\n     * Create by 叶云轩 at 2018/5/3 下午3:19\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackDecoder.class);\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {\n        final byte[] array;\n        final int length = msg.readableBytes();\n        array = new byte[length];\n        msg.getBytes(msg.readerIndex(), array, 0, length);\n        MessagePack messagePack = new MessagePack();\n        out.add(messagePack.read(array));\n    }\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackEncoder.java",
    "content": "package org.yyx.netty.study.codec.msgpack;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToByteEncoder;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\n\n/**\n * MsgPack编码器\n * <p>\n * create by 叶云轩 at 2018/4/12-下午7:29\n * contact by tdg_yyx@foxmail.com\n */\npublic class MsgPackEncoder extends MessageToByteEncoder<Object> {\n\n    /**\n     * MsgPackEncoder 日志控制器\n     * Create by 叶云轩 at 2018/5/3 下午3:18\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackEncoder.class);\n\n    @Override\n    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {\n        MessagePack messagePack = new MessagePack();\n        // 序列化\n        byte[] write = new byte[0];\n        try {\n            write = messagePack.write(msg);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        out.writeBytes(write);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/delimiter/EchoClient.java",
    "content": "package org.yyx.netty.study.echo.delimiter;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.DelimiterBasedFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\n\n/**\n * MessagePackClient\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:28\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoClient {\n\n    public void connect(int port, String host) throws Exception {\n        EventLoopGroup group = new NioEventLoopGroup();\n        try {\n            Bootstrap bootstrap = new Bootstrap();\n            bootstrap.group(group).channel(NioSocketChannel.class)\n                    .option(ChannelOption.TCP_NODELAY, true)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ByteBuf delimiter = Unpooled.copiedBuffer(\"$_$\".getBytes());\n                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));\n                            ch.pipeline().addLast(new StringDecoder());\n                            ch.pipeline().addLast(new EchoClientHandler());\n                        }\n                    });\n            ChannelFuture future = bootstrap.connect(host,port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            group.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/delimiter/EchoClientHandler.java",
    "content": "package org.yyx.netty.study.echo.delimiter;\n\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:57\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoClientHandler extends ChannelHandlerAdapter {\n    static final String ECHO_REQ = \"Hi,Welcome to Netty World.$_$\";\n    /**\n     * MessagePackClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:57\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoClientHandler.class);\n    /**\n     * 计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.error(\"--- [异常] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        for (int i = 0; i < 10; i++) {\n            ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));\n        }\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        LOGGER.info(\"--- [第{}次接收到服务器的消息] {} | [消息] {}\", ++counter, msg);\n    }\n\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        ctx.flush();\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/fixlength/EchoClient.java",
    "content": "package org.yyx.netty.study.echo.fixlength;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.FixedLengthFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\n\n/**\n * MessagePackClient\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:28\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoClient {\n\n    public void connect(int port, String host) throws Exception {\n        EventLoopGroup group = new NioEventLoopGroup();\n        try {\n            Bootstrap bootstrap = new Bootstrap();\n            bootstrap.group(group).channel(NioSocketChannel.class)\n                    .option(ChannelOption.TCP_NODELAY, true)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ByteBuf delimiter = Unpooled.copiedBuffer(\"$_$\".getBytes());\n                            ch.pipeline().addLast(new FixedLengthFrameDecoder(20));\n                            ch.pipeline().addLast(new StringDecoder());\n                            ch.pipeline().addLast(new EchoClientHandler());\n                        }\n                    });\n            ChannelFuture future = bootstrap.connect(host, port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            group.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/fixlength/EchoClientHandler.java",
    "content": "package org.yyx.netty.study.echo.fixlength;\n\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:57\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoClientHandler extends ChannelHandlerAdapter {\n    static final String ECHO_REQ = \"Hi,Welcome to Netty World.$_$\";\n    /**\n     * MessagePackClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:57\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoClientHandler.class);\n    /**\n     * 计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.error(\"--- [异常] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        for (int i = 0; i < 10; i++) {\n            ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));\n        }\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        LOGGER.info(\"--- [第{}次接收到服务器的消息] {} | [消息] {}\", ++counter, msg);\n    }\n\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        ctx.flush();\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackClient.java",
    "content": "package org.yyx.netty.study.echo.megpack;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport org.yyx.netty.study.codec.msgpack.MsgPackDecoder;\nimport org.yyx.netty.study.codec.msgpack.MsgPackEncoder;\n\n/**\n * MessagePackClient\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:28\n * contact by tdg_yyx@foxmail.com\n */\npublic class MessagePackClient {\n\n    /**\n     * 客户端连接方法\n     *\n     * @param port 端口号\n     * @param host 主机名\n     *\n     * @throws Exception 异常\n     */\n    public void connect(int port, String host, int sendNumber) throws Exception {\n        EventLoopGroup group = new NioEventLoopGroup();\n        try {\n            Bootstrap bootstrap = new Bootstrap();\n            bootstrap.group(group).channel(NioSocketChannel.class)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));\n//                            ch.pipeline().addLast(new LengthFieldPrepender(2));\n                            ch.pipeline().addLast(new MsgPackEncoder());\n//                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));\n                            ch.pipeline().addLast(new MsgPackDecoder());\n                            ch.pipeline().addLast(new MessagePackClientHandler(sendNumber));\n                        }\n                    });\n            ChannelFuture future = bootstrap.connect(host, port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            group.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackClientHandler.java",
    "content": "package org.yyx.netty.study.echo.megpack;\n\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.entity.User;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:57\n * contact by tdg_yyx@foxmail.com\n */\npublic class MessagePackClientHandler extends ChannelHandlerAdapter {\n\n    /**\n     * MessagePackClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:57\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackClientHandler.class);\n\n    private final int sendNumber;\n\n    public MessagePackClientHandler(int sendNumber) {\n        this.sendNumber = sendNumber;\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        LOGGER.error(\"--- [异常] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) {\n        User[] userInfo = UserInfo();\n        ctx.writeAndFlush(userInfo);\n        for (User info : userInfo) {\n            ctx.write(info);\n        }\n        ctx.flush();\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        LOGGER.info(\"--- [收到服务器的消息] {}\", msg);\n    }\n\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) {\n        ctx.flush();\n    }\n\n    private User[] UserInfo() {\n        User[] users = new User[sendNumber];\n        User user;\n        for (int i = 0; i < sendNumber; i++) {\n            user = new User();\n            user.setId(i);\n            user.setName(\"YYX --->\" + i);\n            users[i] = user;\n        }\n        return users;\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo1/TimeClient.java",
    "content": "package org.yyx.netty.study.time.demo1;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\n\n/**\n * Netty时间服务器客户端\n * <p>\n * create by 叶云轩 at 2018/4/12-上午9:53\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClient {\n\n    public void connect(int port, String host) throws Exception {\n        // 配置客户端的NIO线程组\n        EventLoopGroup clientGroup = new NioEventLoopGroup();\n        try {\n            // Client辅助启动类\n            Bootstrap bootstrap = new Bootstrap();\n            // 配置bootstrap\n            bootstrap.group(clientGroup)\n                    .channel(NioSocketChannel.class)\n                    .option(ChannelOption.TCP_NODELAY, true)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        /**\n                         * 创建NioSocketChannel成功之后，进行初始化时，\n                         * 将ChannelHandler设置到ChannelPipeline中，\n                         * 同样，用于处理网络I/O事件\n                         * @param ch\n                         * @throws Exception\n                         */\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ch.pipeline().addLast(new TimeClientHandler());\n                        }\n                    });\n            // 发起异步连接操作  同步方法待成功\n            ChannelFuture future = bootstrap.connect(host, port).sync();\n            // 等待客户端链路关闭\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放NIO线程组\n            clientGroup.shutdownGracefully();\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo1/TimeClientHandler.java",
    "content": "package org.yyx.netty.study.time.demo1;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-上午10:16\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClientHandler extends ChannelHandlerAdapter {\n\n    /**\n     * TimeClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午10:16\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class);\n    /**\n     *\n     */\n    private final ByteBuf firstMessage;\n    /**\n     *\n     */\n    private byte[] req;\n\n    public TimeClientHandler() {\n        byte[] req = \"QUERY TIME ORDER\".getBytes();\n        firstMessage = Unpooled.buffer(req.length);\n        firstMessage.writeBytes(req);\n    }\n\n    /**\n     * 捕捉异常\n     *\n     * @param ctx\n     * @param cause\n     *\n     * @throws Exception\n     */\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.warn(\"--- [异常，释放资源] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    /**\n     * 当客户端和服务端TCP链路建立成功之后调用此方法\n     * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端\n     *\n     * @param ctx\n     *\n     * @throws Exception\n     */\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        ctx.writeAndFlush(firstMessage);\n    }\n\n    /**\n     * 服务端返回应答消息时，调用此方法\n     *\n     * @param ctx\n     * @param msg\n     *\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        ByteBuf byteBuf = (ByteBuf) msg;\n        byte[] request = new byte[byteBuf.readableBytes()];\n        byteBuf.readBytes(request);\n        String body = new String(request, \"utf-8\");\n        LOGGER.info(\"--- [Now is] {}\", body);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo2/TimeClient.java",
    "content": "package org.yyx.netty.study.time.demo2;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\n\n/**\n * Netty时间服务器客户端\n * <p>\n * create by 叶云轩 at 2018/4/12-上午9:53\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClient {\n\n    public void connect(int port, String host) throws Exception {\n        // 配置客户端的NIO线程组\n        EventLoopGroup clientGroup = new NioEventLoopGroup();\n        try {\n            // Client辅助启动类\n            Bootstrap bootstrap = new Bootstrap();\n            // 配置bootstrap\n            bootstrap.group(clientGroup)\n                    .channel(NioSocketChannel.class)\n                    .option(ChannelOption.TCP_NODELAY, true)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        /**\n                         * 创建NioSocketChannel成功之后，进行初始化时，\n                         * 将ChannelHandler设置到ChannelPipeline中，\n                         * 同样，用于处理网络I/O事件\n                         * @param ch\n                         * @throws Exception\n                         */\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ch.pipeline().addLast(new TimeClientHandler());\n                        }\n                    });\n            // 发起异步连接操作  同步方法待成功\n            ChannelFuture future = bootstrap.connect(host, port).sync();\n            // 等待客户端链路关闭\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放NIO线程组\n            clientGroup.shutdownGracefully();\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo2/TimeClientHandler.java",
    "content": "package org.yyx.netty.study.time.demo2;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-上午10:16\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClientHandler extends ChannelHandlerAdapter {\n\n    /**\n     * TimeClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午10:16\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class);\n    /**\n     * 模拟粘包/拆包问题计数器\n     */\n    private int counter;\n\n    /**\n     *\n     */\n    private byte[] req;\n\n    public TimeClientHandler() {\n        // region 模拟粘包/拆包问题相关代码\n        req = (\"QUERY TIME ORDER\" + System.getProperty(\"line.separator\")).getBytes();\n        // endregion\n    }\n\n    /**\n     * 捕捉异常\n     *\n     * @param ctx\n     * @param cause\n     * @throws Exception\n     */\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.warn(\"--- [异常，释放资源] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    /**\n     * 当客户端和服务端TCP链路建立成功之后调用此方法\n     * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端\n     *\n     * @param ctx\n     * @throws Exception\n     */\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        // region 模拟粘包/拆包问题相关代码\n        ByteBuf message;\n        for (int i = 0; i < 100; i++) {\n            message = Unpooled.buffer(req.length);\n            message.writeBytes(req);\n            ctx.writeAndFlush(message);\n        }\n        // endregion\n    }\n\n    /**\n     * 服务端返回应答消息时，调用此方法\n     *\n     * @param ctx\n     * @param msg\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        ByteBuf byteBuf = (ByteBuf) msg;\n        byte[] request = new byte[byteBuf.readableBytes()];\n        byteBuf.readBytes(request);\n        String body = new String(request, \"utf-8\");\n        LOGGER.info(\"--- [Now is] {} | [counter] {}\", body, ++counter);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo3/TimeClient.java",
    "content": "package org.yyx.netty.study.time.demo3;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.LineBasedFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\n\n/**\n * Netty时间服务器客户端\n * <p>\n * create by 叶云轩 at 2018/4/12-上午9:53\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClient {\n\n    public void connect(int port, String host) throws Exception {\n        // 配置客户端的NIO线程组\n        EventLoopGroup clientGroup = new NioEventLoopGroup();\n        try {\n            // Client辅助启动类\n            Bootstrap bootstrap = new Bootstrap();\n            // 配置bootstrap\n            bootstrap.group(clientGroup)\n                    .channel(NioSocketChannel.class)\n                    .option(ChannelOption.TCP_NODELAY, true)\n                    .handler(new ChannelInitializer<SocketChannel>() {\n                        /**\n                         * 创建NioSocketChannel成功之后，进行初始化时，\n                         * 将ChannelHandler设置到ChannelPipeline中，\n                         * 同样，用于处理网络I/O事件\n                         * @param ch\n                         * @throws Exception\n                         */\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            // region 解决粘包/拆包问题相关代码\n                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));\n                            ch.pipeline().addLast(new StringDecoder());\n                            // endregion\n                            ch.pipeline().addLast(new TimeClientHandler());\n                        }\n                    });\n            // 发起异步连接操作  同步方法待成功\n            ChannelFuture future = bootstrap.connect(host, port).sync();\n            // 等待客户端链路关闭\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放NIO线程组\n            clientGroup.shutdownGracefully();\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo3/TimeClientHandler.java",
    "content": "package org.yyx.netty.study.time.demo3;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-上午10:16\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeClientHandler extends ChannelHandlerAdapter {\n\n    /**\n     * TimeClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午10:16\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class);\n    /**\n     * 模拟粘包/拆包问题计数器\n     */\n    private int counter;\n\n    /**\n     *\n     */\n    private byte[] req;\n\n    public TimeClientHandler() {\n        // region 模拟粘包/拆包问题相关代码\n        req = (\"QUERY TIME ORDER\" + System.getProperty(\"line.separator\")).getBytes();\n        // endregion\n    }\n\n    /**\n     * 捕捉异常\n     *\n     * @param ctx\n     * @param cause\n     * @throws Exception\n     */\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.warn(\"--- [异常，释放资源] {}\", cause.getMessage());\n        ctx.close();\n    }\n\n    /**\n     * 当客户端和服务端TCP链路建立成功之后调用此方法\n     * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端\n     *\n     * @param ctx\n     * @throws Exception\n     */\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        // region 模拟粘包/拆包问题相关代码\n        ByteBuf message;\n        for (int i = 0; i < 100; i++) {\n            message = Unpooled.buffer(req.length);\n            message.writeBytes(req);\n            ctx.writeAndFlush(message);\n        }\n        // endregion\n    }\n\n    /**\n     * 服务端返回应答消息时，调用此方法\n     *\n     * @param ctx\n     * @param msg\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        // region 解决粘包/拆包问题相关代码\n        String body = (String) msg;\n        // endregion\n        LOGGER.info(\"--- [Now is] {} | [counter] {}\", body, ++counter);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketClient.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.Channel;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\nimport io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;\nimport io.netty.handler.codec.http.websocketx.WebSocketVersion;\n\nimport java.net.URI;\n\n/**\n * webSocketClient\n * <p>\n * create by 叶云轩 at 2018/5/17-下午6:04\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketClient {\n\n    public void connect(int port, String host, String userName) throws Exception {\n        // 配置客户端的NIO线程组\n        EventLoopGroup clientGroup = new NioEventLoopGroup();\n        try {\n            // Client辅助启动类\n            Bootstrap bootstrap = new Bootstrap();\n            // 配置bootstrap\n            WebSocketClientHandler webSocketClientHandler = new WebSocketClientHandler(\n                    WebSocketClientHandshakerFactory.newHandshaker(new URI(\"ws://172.0.0.1:9999/websocket/\" + userName)\n                            , WebSocketVersion.V13, null, false, new DefaultHttpHeaders())\n            );\n            bootstrap.group(clientGroup)\n                    .channel(NioSocketChannel.class)\n                    .handler(new WebSocketHandlerClient(webSocketClientHandler));\n            // 发起异步连接操作  同步方法待成功\n            Channel channel = bootstrap.connect(host, port).sync().channel();\n            // 等待客户端链路关闭\n            channel.closeFuture().sync();\n        } finally {\n            // 优雅退出，释放NIO线程组\n            clientGroup.shutdownGracefully();\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketClientHandler.java",
    "content": "package org.yyx.netty.study.websocket;\n\n\nimport com.alibaba.fastjson.JSONObject;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;\nimport io.netty.handler.codec.http.websocketx.WebSocketFrame;\nimport io.netty.util.CharsetUtil;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/5/17-下午6:06\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {\n    /**\n     * WebSocketClientHandler 日志控制器\n     * Create by 叶云轩 at 2018/5/17 下午6:10\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketClientHandler.class);\n\n    private final WebSocketClientHandshaker webSocketClientHandshaker;\n    private ChannelPromise handshakeFuture;\n\n    WebSocketClientHandler(WebSocketClientHandshaker webSocketClientHandshaker) {\n        this.webSocketClientHandshaker = webSocketClientHandshaker;\n    }\n\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) {\n        handshakeFuture = ctx.newPromise();\n    }\n\n    /**\n     * 异常\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param cause                 异常\n     */\n    @Override\n    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [exception]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", cause.getMessage());\n        channelHandlerContext.close();\n    }\n\n    /**\n     * 当客户端主动链接服务端的链接后，调用此方法\n     *\n     * @param channelHandlerContext ChannelHandlerContext\n     */\n    @Override\n    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [建立连接]\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n\n        Channel channel = channelHandlerContext.channel();\n        // 握手\n        webSocketClientHandshaker.handshake(channel);\n    }\n\n    /**\n     * 与服务端断开连接时\n     *\n     * @param channelHandlerContext channelHandlerContext\n     */\n    @Override\n    public void channelInactive(ChannelHandlerContext channelHandlerContext) {\n        Channel channel = channelHandlerContext.channel();\n        WebSocketUsers.remove(channel);\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [断开连接]：client [{}]\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", channel.remoteAddress());\n\n    }\n\n    /**\n     * 读完之后调用的方法\n     *\n     * @param channelHandlerContext ChannelHandlerContext\n     */\n    @Override\n    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {\n        channelHandlerContext.flush();\n    }\n\n    @Override\n    protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {\n        // 获取通道\n        Channel channel = channelHandlerContext.channel();\n        // 如果没有握手完成进行握手\n        if (!webSocketClientHandshaker.isHandshakeComplete()) {\n            webSocketClientHandshaker.finishHandshake(channel, (FullHttpResponse) msg);\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [握手成功]\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n            handshakeFuture.setSuccess();\n            // 将当前登陆用户保存起来\n            WebSocketUsers.put(\"client1-\" + getUserNameInPath(), channel);\n            return;\n        }\n        channelHandlerContext.flush();\n\n        if (msg instanceof FullHttpResponse) {\n            FullHttpResponse response = (FullHttpResponse) msg;\n            throw new IllegalStateException(\n                    \"Unexpected FullHttpResponse (getStatus=\" + response.status() +\n                            \", content=\" + response.content().toString(CharsetUtil.UTF_8) + ')');\n        }\n\n        WebSocketFrame frame = (WebSocketFrame) msg;\n\n        if (frame instanceof TextWebSocketFrame) {\n            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) frame;\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [服务器响应消息]: {}\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", textWebSocketFrame.text());\n            WebSocketMessage webSocketMessage = new WebSocketMessage();\n            webSocketMessage.setHeader(WebSocketMessage.Type.send_user);\n            webSocketMessage.setAccept(\"yyx\");\n            webSocketMessage.setContent(\"Hello I'm YeYunXuan\");\n            String string = JSONObject.toJSONString(webSocketMessage);\n            WebSocketUsers.sendMessageToUser(\"client1-YeYunXuan\", string);\n        }\n    }\n\n    /**\n     * 获取登陆用户\n     *\n     * @return 用户名\n     */\n    private String getUserNameInPath() {\n        String path = webSocketClientHandshaker.uri().getPath();\n        int i = path.lastIndexOf(\"/\");\n        return path.substring(i + 1, path.length());\n    }\n}\n\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketConstant.java",
    "content": "package org.yyx.netty.study.websocket;\n\n/**\n * WebSocket常量\n * <p>\n * create by 叶云轩 at 2018/5/18-下午1:29\n * contact by tdg_yyx@foxmail.com\n */\npublic interface WebSocketConstant {\n\n    /**\n     * 点对点\n     */\n    String SEND_TO_USER = \"send_to_user\";\n\n    /**\n     * 群发 广播\n     */\n    String SEND_TO_USERS = \"send_to_users\";\n\n    /**\n     * 请求成功\n     */\n    String REQUEST_SUCCESS = \"request_success\";\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketHandlerClient.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.http.HttpClientCodec;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.stream.ChunkedWriteHandler;\nimport org.yyx.netty.study.codec.msgpack.MsgPackDecoder;\nimport org.yyx.netty.study.codec.msgpack.MsgPackEncoder;\n\n/**\n * webSocketChildHandler\n * <p>\n * create by 叶云轩 at 2018/5/15-下午4:54\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketHandlerClient extends ChannelInitializer<SocketChannel> {\n\n    private WebSocketClientHandler webSocketClientHandler;\n\n    public WebSocketHandlerClient(WebSocketClientHandler webSocketClientHandler) {\n        this.webSocketClientHandler = webSocketClientHandler;\n    }\n\n    /**\n     * 初始化Channel\n     *\n     * @param socketChannel socketChannel\n     */\n    @Override\n    protected void initChannel(SocketChannel socketChannel) throws Exception {\n        ChannelPipeline pipeline = socketChannel.pipeline();\n        // 将请求与应答消息编码或者解码为HTTP消息\n        pipeline.addLast(new HttpClientCodec());\n        // 将http消息的多个部分组合成一条完整的HTTP消息\n        pipeline.addLast(\"aggregator\", new HttpObjectAggregator(65536));\n        pipeline.addLast(\"http-chunked\", new ChunkedWriteHandler());\n        // 客户端Handler\n        pipeline.addLast(\"handler\", webSocketClientHandler);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketMessage.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * WebSocketMessage\n * <p>\n * create by 叶云轩 at 2018/5/18-下午3:02\n * contact by tdg_yyx@foxmail.com\n */\n@Data\npublic class WebSocketMessage implements Serializable {\n    private static final long serialVersionUID = -4666429837358506065L;\n\n    private String accept;\n    private String content;\n    private Type header;\n\n    enum Type {\n        send_user, send_users, request_success;\n    }\n\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketUsers.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.util.internal.PlatformDependent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * WebSocket用户集\n */\npublic class WebSocketUsers {\n\n    /**\n     * 用户集\n     */\n    private static final ConcurrentMap<String, Channel> USERS = PlatformDependent.newConcurrentHashMap();\n    /**\n     * WebSocketUsers 日志控制器\n     * Create by 叶云轩 at 2018/5/15 下午5:41\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);\n\n    private static WebSocketUsers ourInstance = new WebSocketUsers();\n\n    private WebSocketUsers() {\n    }\n\n    public static WebSocketUsers getInstance() {\n        return ourInstance;\n    }\n\n    /**\n     * 存储通道\n     *\n     * @param key     唯一键\n     * @param channel 通道\n     */\n    public static void put(String key, Channel channel) {\n        USERS.put(key, channel);\n    }\n\n    /**\n     * 移除通道\n     *\n     * @param channel 通道\n     *\n     * @return 移除结果\n     */\n    public static boolean remove(Channel channel) {\n        String key = null;\n        boolean b = USERS.containsValue(channel);\n        if (b) {\n            Set<Map.Entry<String, Channel>> entries = USERS.entrySet();\n            for (Map.Entry<String, Channel> entry : entries) {\n                Channel value = entry.getValue();\n                if (value.equals(channel)) {\n                    key = entry.getKey();\n                    break;\n                }\n            }\n        } else {\n            return true;\n        }\n        return remove(key);\n    }\n\n    /**\n     * 移出通道\n     *\n     * @param key 键\n     */\n    public static boolean remove(String key) {\n        Channel remove = USERS.remove(key);\n        boolean containsValue = USERS.containsValue(remove);\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [移出结果]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", containsValue ? \"失败\" : \"成功\");\n        return containsValue;\n    }\n\n    /**\n     * 获取在线用户列表\n     *\n     * @return 返回用户集合\n     */\n    public static ConcurrentMap<String, Channel> getUSERS() {\n        return USERS;\n    }\n\n    /**\n     * 群发消息\n     *\n     * @param message 消息内容\n     */\n    public static void sendMessageToUsers(String message) {\n        Collection<Channel> values = USERS.values();\n        for (Channel value : values) {\n            value.write(new TextWebSocketFrame(message));\n            value.flush();\n        }\n    }\n\n    /**\n     * 给某个人发送消息\n     *\n     * @param userName key\n     * @param message  消息\n     */\n    public static void sendMessageToUser(String userName, String message) {\n        Channel channel = USERS.get(userName);\n        channel.write(new TextWebSocketFrame(message));\n        channel.flush();\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/main/resources/application.properties",
    "content": ""
  },
  {
    "path": "spring-boot-netty/study-netty/study-client/src/test/java/org/yyx/netty/study/StudyClientApplicationTests.java",
    "content": "package org.yyx.netty.study;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.yyx.netty.study.echo.megpack.MessagePackClient;\nimport org.yyx.netty.study.websocket.WebSocketClient;\nimport org.yyx.netty.study.websocket.WebSocketUsers;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class StudyClientApplicationTests {\n\n    /**\n     * 测试端口\n     */\n    private final int port = 8080;\n    /**\n     * 测试IP\n     */\n    private final String host = \"127.0.0.1\";\n\n    // region 启动未解决粘包/拆包问题的netty客户端\n    @Test\n    public void startNettyClient1() throws Exception {\n        new org.yyx.netty.study.time.demo1.TimeClient().connect(port, host);\n    }\n    // endregion\n\n    // region 启动模拟粘包/拆包问题的netty客户端\n    @Test\n    public void startNettyClient2() throws Exception {\n        new org.yyx.netty.study.time.demo2.TimeClient().connect(port, host);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty客户端 - LineBasedFrameDecoder 实现\n    @Test\n    public void startNettyClient3() throws Exception {\n        new org.yyx.netty.study.time.demo3.TimeClient().connect(port, host);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty客户端 - DelimiterBasedFrameDecoder 实现\n    @Test\n    public void startNettyClient4() throws Exception {\n        new org.yyx.netty.study.echo.delimiter.EchoClient().connect(port, host);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty服务 - FixedLengthFrameDecoder 实现\n    @Test\n    public void startNettyServer5() throws Exception {\n        new org.yyx.netty.study.echo.fixlength.EchoClient().connect(port, host);\n    }\n    // endregion\n\n    // region 启动不考虑粘包/拆包问题 基于MessagePack编解码的Netty客户端\n    @Test\n    public void testMessagePackEchoClient() throws Exception {\n        new MessagePackClient().connect(port, host, 20);\n    }\n    // endregion\n\n    // region 启动WebSocketClient\n    @Test\n    public void startWebSocketClient() throws Exception {\n        new WebSocketClient().connect(9999, host, \"yyx\");\n    }\n\n    @Test\n    public void startWebSocketClient2() throws Exception {\n        new WebSocketClient().connect(9999, host, \"YeYunXuan\");\n    }\n    // endregion\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>study-server</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>study-server</name>\n    <description>Demo project for Spring Boot</description>\n\n    <parent>\n        <groupId>org.yyx.netty</groupId>\n        <artifactId>study-netty</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/StudyServerApplication.java",
    "content": "package org.yyx.netty.study;\n\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class StudyServerApplication implements CommandLineRunner {\n\n    public static void main(String[] args) {\n        SpringApplication.run(StudyServerApplication.class, args);\n    }\n\n    @Override\n    public void run(String... args) throws Exception {\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackDecoder.java",
    "content": "package org.yyx.netty.study.codec.msgpack;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageDecoder;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\n\n/**\n * MsgPack解码器\n * <p>\n * create by 叶云轩 at 2018/4/12-下午7:31\n * contact by tdg_yyx@foxmail.com\n */\npublic class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {\n\n\n    /**\n     * MsgPackDecoder 日志控制器\n     * Create by 叶云轩 at 2018/5/3 下午3:15\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackDecoder.class);\n\n    /**\n     * 解码 byte[] -> Object\n     */\n    @Override\n    protected void decode(ChannelHandlerContext channelHandlerContext\n            , ByteBuf msg, List<Object> out) throws Exception {\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [解码]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", msg);\n        final int length = msg.readableBytes();\n        final byte[] array = new byte[length];\n        msg.getBytes(msg.readerIndex(), array, 0, length);\n        MessagePack messagePack = new MessagePack();\n        out.add(messagePack.read(array));\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [out]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", out);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackEncoder.java",
    "content": "package org.yyx.netty.study.codec.msgpack;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToByteEncoder;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * MsgPack编码器\n * <p>\n * create by 叶云轩 at 2018/4/12-下午7:29\n * contact by tdg_yyx@foxmail.com\n */\npublic class MsgPackEncoder extends MessageToByteEncoder<Object> {\n    /**\n     * MsgPackEncoder 日志控制器\n     * Create by 叶云轩 at 2018/5/3 下午3:15\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackEncoder.class);\n\n    /**\n     * 编码 Object -> byte[]\n     */\n    @Override\n    protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf out) throws Exception {\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [编码]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", msg);\n        MessagePack messagePack = new MessagePack();\n        // 序列化\n        byte[] write = messagePack.write(msg);\n        out.writeBytes(write);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/delimiter/EchoServer.java",
    "content": "package org.yyx.netty.study.echo.delimiter;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.codec.DelimiterBasedFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * EchoServer服务端\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:07\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoServer {\n    /**\n     * MessagePackServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:09\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoServer.class);\n\n\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    .channel(NioServerSocketChannel.class)\n                    .option(ChannelOption.SO_BACKLOG, 100)\n                    .handler(new LoggingHandler(LogLevel.INFO))\n                    .childHandler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            // 设置分隔符为 $_$\n                            ByteBuf delimiter = Unpooled.copiedBuffer(\"$_$\".getBytes());\n                            // 单条消息最大长度不能超过1024\n                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));\n                            ch.pipeline().addLast(new StringDecoder());\n                            ch.pipeline().addLast(new EchoServerHandler());\n                        }\n                    });\n            ChannelFuture future = bootstrap.bind(port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/delimiter/EchoServerHandler.java",
    "content": "package org.yyx.netty.study.echo.delimiter;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:12\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoServerHandler extends ChannelHandlerAdapter {\n\n    /**\n     * MessagePackServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:20\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoServerHandler.class);\n    /**\n     * 计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.info(\"--- [发生异常] 释放资源\");\n        ctx.close();\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        String body = (String) msg;\n        LOGGER.info(\"--- [第{}次接收客户端消息] {}\", ++counter, body);\n        body += \"$_$\";\n        ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());\n        ctx.writeAndFlush(echo);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/fixlength/EchoServer.java",
    "content": "package org.yyx.netty.study.echo.fixlength;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.codec.FixedLengthFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * EchoServer服务端\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:07\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoServer {\n    /**\n     * MessagePackServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:09\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoServer.class);\n\n\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    .channel(NioServerSocketChannel.class)\n                    .option(ChannelOption.SO_BACKLOG, 100)\n                    .handler(new LoggingHandler(LogLevel.INFO))\n                    .childHandler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ch.pipeline().addLast(new FixedLengthFrameDecoder(20));\n                            ch.pipeline().addLast(new StringDecoder());\n                            ch.pipeline().addLast(new EchoServerHandler());\n                        }\n                    });\n            ChannelFuture future = bootstrap.bind(port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/fixlength/EchoServerHandler.java",
    "content": "package org.yyx.netty.study.echo.fixlength;\n\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:12\n * contact by tdg_yyx@foxmail.com\n */\npublic class EchoServerHandler extends ChannelHandlerAdapter {\n\n    /**\n     * MessagePackServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:20\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(EchoServerHandler.class);\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.info(\"--- [发生异常] 释放资源\");\n        ctx.close();\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        LOGGER.info(\"--- [接收到客户端的数据] {}\", msg);\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackServer.java",
    "content": "package org.yyx.netty.study.echo.megpack;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.study.codec.msgpack.MsgPackDecoder;\nimport org.yyx.netty.study.codec.msgpack.MsgPackEncoder;\n\n/**\n * EchoServer服务端\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:07\n * contact by tdg_yyx@foxmail.com\n */\npublic class MessagePackServer {\n    /**\n     * MessagePackServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:09\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackServer.class);\n\n\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    .channel(NioServerSocketChannel.class)\n                    .option(ChannelOption.SO_BACKLOG, 100)\n                    .childHandler(new ChannelInitializer<SocketChannel>() {\n                        @Override\n                        protected void initChannel(SocketChannel ch) throws Exception {\n                            ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));\n//                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));\n                            ch.pipeline().addLast(new MsgPackDecoder());\n//                            ch.pipeline().addLast(new LengthFieldPrepender(2));\n                            ch.pipeline().addLast(new MsgPackEncoder());\n                            ch.pipeline().addLast(new MessagePackServerHandler());\n                        }\n                    });\n            ChannelFuture future = bootstrap.bind(port).sync();\n            future.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackServerHandler.java",
    "content": "package org.yyx.netty.study.echo.megpack;\n\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.entity.User;\n\nimport java.util.List;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午4:12\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:32\n */\npublic class MessagePackServerHandler extends ChannelHandlerAdapter {\n\n    /**\n     * MessagePackServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午4:20\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackServerHandler.class);\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        LOGGER.info(\"--- [发生异常] 释放资源: {}\", cause.getMessage());\n        // todo\n        ctx.close();\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        ctx.writeAndFlush(\"Server connect success\");\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        List<User> userInfo = (List<User>) msg;\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [接收 ]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", userInfo);\n    }\n\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        ctx.flush();\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/ChildChannelHandler.java",
    "content": "package org.yyx.netty.study.time.demo1;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.socket.SocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * I/O事件处理类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:34\n * contact by tdg_yyx@foxmail.com\n */\npublic class ChildChannelHandler extends ChannelInitializer<SocketChannel> {\n\n    /**\n     * ChildChannelHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:29\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class);\n\n    /**\n     * 创建NioSocketChannel成功之后，进行初始化时，\n     * 将ChannelHandler设置到ChannelPipeline中，\n     * 同样，用于处理网络I/O事件\n     *\n     * @param ch\n     *\n     * @throws Exception\n     */\n    @Override\n    protected void initChannel(SocketChannel ch) throws Exception {\n        LOGGER.info(\"--- [通道初始化]\");\n        ch.pipeline().addLast(new TimeServerHandler());\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/TimeServer.java",
    "content": "package org.yyx.netty.study.time.demo1;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:33\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServer {\n    /*\n     NioEventLoopGroup本质是一个线程组，包含一组NIO线程专门用于网络事件的处理\n     一个用于服务端接受客户端的连接\n     一个用于进行SocketChannel的网络读写\n     */\n\n    /**\n     * TimeServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:26\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class);\n\n    /**\n     * 绑定端口\n     *\n     * @param port 端口号\n     * @throws Exception 异常\n     */\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类\n                    .channel(NioServerSocketChannel.class)\n                    // 配置NioServerSocketChannel的TCP参数 backlog设置为1024\n                    .option(ChannelOption.SO_BACKLOG, 1024)\n                    // 绑定I/O事件处理类\n                    .childHandler(new ChildChannelHandler());\n            // 绑定端口，同步等待成功\n            // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调\n            ChannelFuture channelFuture = bootstrap.bind(port).sync();\n            // 等待服务端监听端口关闭\n            channelFuture.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/TimeServerHandler.java",
    "content": "package org.yyx.netty.study.time.demo1;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Date;\n\n/**\n * 针对网络事件进行读写操作的类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:35\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServerHandler extends ChannelHandlerAdapter {\n    /**\n     * TimeServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:30\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class);\n    /**\n     * 模拟粘包/拆包问题计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        ctx.close();\n    }\n\n    /**\n     * 读事件\n     *\n     * @param ctx ChannelHandlerContext\n     * @param msg 消息\n     *\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        // 将消息转换成ByteBuf\n        ByteBuf buf = (ByteBuf) msg;\n        // 获取缓冲区中的字节数\n        byte[] req = new byte[buf.readableBytes()];\n        buf.readBytes(req);\n        String body = new String(req, \"utf-8\");\n        LOGGER.info(\"--- [接收到的数据] {}\", body);\n        String currentTime = \"QUERY TIME ORDER\".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() :\n                \"BAD ORDER\";\n        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());\n\n        // region 模拟粘包/拆包问题相关代码\n//        String body = new String(req, \"utf-8\").substring(0, req.length - System.getProperty(\"line.separator\").length());\n//        LOGGER.info(\"--- [接收到的数据] {} | [counter] {}\", body, ++counter);\n//        String currentTime = \"QUERY TIME ORDER\".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : \"BAD ORDER\";\n//        currentTime = currentTime + System.getProperty(\"line.separator\");\n//        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());\n        // endregion\n\n\n        // 异步发送应答消息给Client\n        ctx.write(resp); // --> 将消息放到发送缓冲数组中\n    }\n\n    /**\n     * 读完之后\n     *\n     * @param ctx\n     *\n     * @throws Exception\n     */\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        LOGGER.info(\"--- [服务器写消息] \");\n        // 将消息发送队列中的消息写到SocketChannel中\n        ctx.flush(); // --> 将消息写到 SocketChannel 中\n    }\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/ChildChannelHandler.java",
    "content": "package org.yyx.netty.study.time.demo2;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.socket.SocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * I/O事件处理类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:34\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:38\n */\npublic class ChildChannelHandler extends ChannelInitializer<SocketChannel> {\n\n    /**\n     * ChildChannelHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:29\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class);\n\n    /**\n     * 创建NioSocketChannel成功之后，进行初始化时，\n     * 将ChannelHandler设置到ChannelPipeline中，\n     * 同样，用于处理网络I/O事件\n     *\n     * @param ch\n     * @throws Exception\n     */\n    @Override\n    protected void initChannel(SocketChannel ch) throws Exception {\n        LOGGER.info(\"--- [通道初始化]\");\n        ch.pipeline().addLast(new TimeServerHandler());\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/TimeServer.java",
    "content": "package org.yyx.netty.study.time.demo2;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:33\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServer {\n    /*\n     NioEventLoopGroup本质是一个线程组，包含一组NIO线程专门用于网络事件的处理\n     一个用于服务端接受客户端的连接\n     一个用于进行SocketChannel的网络读写\n     */\n\n    /**\n     * TimeServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:26\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class);\n\n    /**\n     * 绑定端口\n     *\n     * @param port 端口号\n     * @throws Exception 异常\n     */\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类\n                    .channel(NioServerSocketChannel.class)\n                    // 配置NioServerSocketChannel的TCP参数 backlog设置为1024\n                    .option(ChannelOption.SO_BACKLOG, 1024)\n                    // 绑定I/O事件处理类\n                    .childHandler(new ChildChannelHandler());\n            // 绑定端口，同步等待成功\n            // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调\n            ChannelFuture channelFuture = bootstrap.bind(port).sync();\n            // 等待服务端监听端口关闭\n            channelFuture.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/TimeServerHandler.java",
    "content": "package org.yyx.netty.study.time.demo2;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Date;\n\n/**\n * 针对网络事件进行读写操作的类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:35\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServerHandler extends ChannelHandlerAdapter {\n    /**\n     * TimeServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:30\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class);\n    /**\n     * 模拟粘包/拆包问题计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        ctx.close();\n    }\n\n    /**\n     * 读事件\n     *\n     * @param ctx ChannelHandlerContext\n     * @param msg 消息\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        // 将消息转换成ByteBuf\n        ByteBuf buf = (ByteBuf) msg;\n        // 获取缓冲区中的字节数\n        byte[] req = new byte[buf.readableBytes()];\n        buf.readBytes(req);\n        // region 模拟粘包/拆包问题相关代码\n        String body = new String(req, \"utf-8\").substring(0, req.length - System.getProperty(\"line.separator\").length());\n        LOGGER.info(\"--- [接收到的数据] {} | [counter] {}\", body, ++counter);\n        String currentTime = \"QUERY TIME ORDER\".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : \"BAD ORDER\";\n        currentTime = currentTime + System.getProperty(\"line.separator\");\n        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());\n        // endregion\n        // 异步发送应答消息给Client\n        ctx.writeAndFlush(resp); // --> 将消息放到发送缓冲数组中\n    }\n\n    /**\n     * 读完之后\n     *\n     * @param ctx\n     * @throws Exception\n     */\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n        LOGGER.info(\"--- [服务器写消息] \");\n        // 将消息发送队列中的消息写到SocketChannel中\n        ctx.flush(); // --> 将消息写到 SocketChannel 中\n    }\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/ChildChannelHandler.java",
    "content": "package org.yyx.netty.study.time.demo3;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.LineBasedFrameDecoder;\nimport io.netty.handler.codec.string.StringDecoder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * I/O事件处理类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:34\n * contact by tdg_yyx@foxmail.com\n */\npublic class ChildChannelHandler extends ChannelInitializer<SocketChannel> {\n\n    /**\n     * ChildChannelHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:29\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class);\n\n\n    /**\n     * LineBasedFrameDecoder: 以换行符为结束标志的解码器\n     *  依次遍历ByteBuf中的可读字节，\n     *  如果有\"\\n\" 或者 \"\\r\\n\" -> 就以此位置为结束位置 之后将可读索引到结束位置区间的字节组成一行\n     */\n\n    /**\n     * 创建NioSocketChannel成功之后，进行初始化时，\n     * 将ChannelHandler设置到ChannelPipeline中，\n     * 同样，用于处理网络I/O事件\n     *\n     * @param ch\n     * @throws Exception\n     */\n    @Override\n    protected void initChannel(SocketChannel ch) throws Exception {\n        LOGGER.info(\"--- [通道初始化]\");\n        // region 解决粘包/拆包问题相关代码\n        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));\n        // 将接收到的对象转成字符串\n        ch.pipeline().addLast(new StringDecoder());\n        // endregion\n\n        ch.pipeline().addLast(new TimeServerHandler());\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/TimeServer.java",
    "content": "package org.yyx.netty.study.time.demo3;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:33\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServer {\n    /*\n     NioEventLoopGroup本质是一个线程组，包含一组NIO线程专门用于网络事件的处理\n     一个用于服务端接受客户端的连接\n     一个用于进行SocketChannel的网络读写\n     */\n\n    /**\n     * TimeServer 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:26\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class);\n\n    /**\n     * 绑定端口\n     *\n     * @param port 端口号\n     *\n     * @throws Exception 异常\n     */\n    public void bind(int port) throws Exception {\n        LOGGER.info(\"--- [绑定端口] {}\", port);\n        // 声明Boss线程组\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        // 声明Worker线程组\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            LOGGER.info(\"--- [启动NIO] \");\n            // Netty用于启动NIO服务端的辅助启动类，目的是降低服务端的开发复杂度\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            // 将两个NIO线程组传递到ServerBootStrap中\n            bootstrap.group(bossGroup, workerGroup)\n                    // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类\n                    .channel(NioServerSocketChannel.class)\n                    // 配置NioServerSocketChannel的TCP参数 backlog设置为1024\n                    .option(ChannelOption.SO_BACKLOG, 1024)\n                    // 绑定I/O事件处理类\n                    .childHandler(new ChildChannelHandler());\n            // 绑定端口，同步等待成功\n            // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调\n            ChannelFuture channelFuture = bootstrap.bind(port).sync();\n            // 等待服务端监听端口关闭\n            channelFuture.channel().closeFuture().sync();\n        } finally {\n            // 优雅退出，释放线程池资源\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/TimeServerHandler.java",
    "content": "package org.yyx.netty.study.time.demo3;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerAdapter;\nimport io.netty.channel.ChannelHandlerContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Date;\n\n/**\n * 针对网络事件进行读写操作的类\n * <p>\n * create by 叶云轩 at 2018/4/11-下午6:35\n * contact by tdg_yyx@foxmail.com\n */\npublic class TimeServerHandler extends ChannelHandlerAdapter {\n    /**\n     * TimeServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/4/12 上午11:30\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class);\n    /**\n     * 模拟粘包/拆包问题计数器\n     */\n    private int counter;\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        ctx.close();\n    }\n\n    /**\n     * 读事件\n     *\n     * @param ctx ChannelHandlerContext\n     * @param msg 消息\n     *\n     * @throws Exception\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        // region 解决模拟粘包/拆包问题相关代码\n        String body = (String) msg;\n        LOGGER.info(\"--- [接收到的数据] {} | [counter] {}\", body, ++counter);\n        String currentTime = \"QUERY TIME ORDER\".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : \"BAD ORDER\";\n        currentTime = currentTime + System.getProperty(\"line.separator\");\n        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());\n        // endregion\n\n        // 异步发送应答消息给Client\n        ctx.writeAndFlush(resp); // --> 将消息放到发送缓冲数组中\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketChildHandler.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.stream.ChunkedWriteHandler;\n\n/**\n * webSocketChildHandler\n * <p>\n * create by 叶云轩 at 2018/5/15-下午4:54\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketChildHandler extends ChannelInitializer<SocketChannel> {\n    /**\n     * 初始化Channel\n     *\n     * @param socketChannel socketChannel\n     */\n    @Override\n    protected void initChannel(SocketChannel socketChannel) throws Exception {\n        ChannelPipeline pipeline = socketChannel.pipeline();\n        // 将请求与应答消息编码或者解码为HTTP消息\n        pipeline.addLast(\"http-codec\", new HttpServerCodec());\n        // 将http消息的多个部分组合成一条完整的HTTP消息\n        pipeline.addLast(\"aggregator\", new HttpObjectAggregator(65536));\n        // 向客户端发送HTML5文件。主要用于支持浏览器和服务端进行WebSocket通信\n        pipeline.addLast(\"http-chunked\", new ChunkedWriteHandler());\n        // 服务端Handler\n        pipeline.addLast(\"handler\", new WebSocketServerHandler());\n\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketConstant.java",
    "content": "package org.yyx.netty.study.websocket;\n\n/**\n * WebSocket常量\n * <p>\n * create by 叶云轩 at 2018/5/18-下午1:29\n * contact by tdg_yyx@foxmail.com\n */\npublic interface WebSocketConstant {\n\n    /**\n     * 点对点\n     */\n    String SEND_TO_USER = \"send_to_user\";\n\n    /**\n     * 群发 广播\n     */\n    String SEND_TO_USERS = \"send_to_users\";\n\n    /**\n     * 请求成功\n     */\n    String REQUEST_SUCCESS = \"request_success\";\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketMessage.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * WebSocketMessage\n * <p>\n * create by 叶云轩 at 2018/5/18-下午3:02\n * contact by tdg_yyx@foxmail.com\n */\n@Data\npublic class WebSocketMessage implements Serializable {\n    private static final long serialVersionUID = -4666429837358506065L;\n\n    private String accept;\n    private String content;\n    private Type header;\n\n    enum Type {\n        send_user, send_users, request_success;\n    }\n\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketServer.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.Channel;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * webSocket服务器\n * <p>\n * create by 叶云轩 at 2018/5/11-上午11:42\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketServer {\n    /**\n     * WebSocketServer 日志控制器\n     * Create by 叶云轩 at 2018/5/11 上午11:48\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);\n\n    public void run(int port) throws Exception {\n        EventLoopGroup bossGroup = new NioEventLoopGroup();\n        EventLoopGroup workerGroup = new NioEventLoopGroup();\n        try {\n            ServerBootstrap bootstrap = new ServerBootstrap();\n            bootstrap.group(bossGroup, workerGroup)\n                    .channel(NioServerSocketChannel.class)\n                    .childHandler(new WebSocketChildHandler());\n            Channel ch = bootstrap.bind(port).sync().channel();\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [服务器启动]: {}\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", port);\n            ch.closeFuture().sync();\n        } finally {\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketServerHandler.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport com.alibaba.fastjson.JSONObject;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.PingWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.PongWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/5/11-上午11:49\n * contact by tdg_yyx@foxmail.com\n */\npublic class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {\n    /**\n     * WebSocketServerHandler 日志控制器\n     * Create by 叶云轩 at 2018/5/11 上午11:50\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class);\n\n    private WebSocketServerHandshaker socketServerHandShaker;\n\n    /**\n     * 异常\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param cause                 异常\n     */\n    @Override\n    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {\n        channelHandlerContext.close();\n    }\n\n    /**\n     * 当客户端主动链接服务端的链接后，调用此方法\n     *\n     * @param channelHandlerContext ChannelHandlerContext\n     */\n    @Override\n    public void channelActive(ChannelHandlerContext channelHandlerContext) {\n        // 使用一个结构存储通道\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                        \"\\t├ [建立连接]: client [{}]\\n\" +\n                        \"\\t├ [当前在线人数]: {}\\n\" +\n                        \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", channelHandlerContext.channel().remoteAddress()\n                , WebSocketUsers.getUSERS().size() + 1);\n    }\n\n    /**\n     * 与客户端断开连接时\n     *\n     * @param channelHandlerContext channelHandlerContext\n     */\n    @Override\n    public void channelInactive(ChannelHandlerContext channelHandlerContext) {\n        Channel channel = channelHandlerContext.channel();\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [断开连接]：client [{}]\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", channel.remoteAddress());\n        WebSocketUsers.remove(channel);\n        ConcurrentMap<String, Channel> users = WebSocketUsers.getUSERS();\n        LOGGER.info(\"\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n        for (String s : users.keySet()) {\n            LOGGER.info(\n                    \"\\t├ [当前在线]: {}\", s);\n        }\n        LOGGER.info(\"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n    }\n\n    /**\n     * 读完之后调用的方法\n     *\n     * @param channelHandlerContext ChannelHandlerContext\n     */\n    @Override\n    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {\n        channelHandlerContext.flush();\n    }\n\n    /**\n     * 接收客户端发送的消息\n     *\n     * @param ctx ChannelHandlerContext\n     * @param msg 消息\n     */\n    @Override\n    protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [收到客户端消息类型]: {} - {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", msg.getClass(), msg.toString());\n        // 传统http接入 第一次需要使用http建立握手\n        if (msg instanceof FullHttpRequest) {\n            handlerHttpRequest(ctx, (FullHttpRequest) msg);\n            ctx.channel().write(new TextWebSocketFrame(\"连接成功\"));\n        }\n        // WebSocket接入\n        else if (msg instanceof WebSocketFrame) {\n            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);\n        }\n\n    }\n\n    /**\n     * 第一次握手\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param req                   请求\n     */\n    private void handlerHttpRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) {\n\n        // 构造握手响应返回，本机测试\n        WebSocketServerHandshakerFactory wsFactory\n                = new WebSocketServerHandshakerFactory(\"ws://localhost:9999/websocket/{uri}\",\n                null, false);\n        String uri = req.uri();\n        String[] split = uri.split(\"/\");\n        String userName = split[2];\n        // 加入在线用户\n        WebSocketUsers.put(userName, channelHandlerContext.channel());\n        socketServerHandShaker = wsFactory.newHandshaker(req);\n        if (socketServerHandShaker == null) {\n            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channelHandlerContext.channel());\n        } else {\n            socketServerHandShaker.handshake(channelHandlerContext.channel(), req);\n        }\n    }\n\n    /**\n     * webSocket处理逻辑\n     *\n     * @param channelHandlerContext channelHandlerContext\n     * @param frame                 webSocketFrame\n     */\n    private void handlerWebSocketFrame(ChannelHandlerContext channelHandlerContext, WebSocketFrame frame) throws IOException {\n        Channel channel = channelHandlerContext.channel();\n        // region 判断是否是关闭链路的指令\n        if (frame instanceof CloseWebSocketFrame) {\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [关闭与客户端的链接]\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n            socketServerHandShaker.close(channel, (CloseWebSocketFrame) frame.retain());\n            return;\n        }\n        // endregion\n        // region 判断是否是ping消息\n        if (frame instanceof PingWebSocketFrame) {\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [Ping消息]\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\");\n            channel.write(new PongWebSocketFrame(frame.content().retain()));\n            return;\n        }\n        // endregion\n        if (frame instanceof TextWebSocketFrame) {\n            String text = ((TextWebSocketFrame) frame).text();\n            WebSocketMessage webSocketMessage = JSONObject.parseObject(text, WebSocketMessage.class);\n            String accept = webSocketMessage.getAccept();\n            WebSocketUsers.sendMessageToUser(accept, webSocketMessage.getContent());\n        }\n        if (frame instanceof BinaryWebSocketFrame) {\n            BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame;\n            ByteBuf content = binaryWebSocketFrame.content();\n            LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                    \"\\t├ [二进制数据]:{}\\n\" +\n                    \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", content);\n            final int length = content.readableBytes();\n            final byte[] array = new byte[length];\n            content.getBytes(content.readerIndex(), array, 0, length);\n            MessagePack messagePack = new MessagePack();\n            List<Object> list = new ArrayList<>();\n            list.add(messagePack.read(array));\n            for (Object o : list) {\n                LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                        \"\\t├ [解码数据]: {}\\n\" +\n                        \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", o);\n            }\n\n        }\n        // 非文本消息处理方式\n        if (!(frame instanceof TextWebSocketFrame)) {\n            throw new UnsupportedOperationException(String.format(\n                    \"%s 暂不支持非文本消息\", frame.getClass().getName()\n            ));\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketUsers.java",
    "content": "package org.yyx.netty.study.websocket;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.util.internal.PlatformDependent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * WebSocket用户集\n */\npublic class WebSocketUsers {\n\n    /**\n     * 用户集\n     */\n    private static final ConcurrentMap<String, Channel> USERS = PlatformDependent.newConcurrentHashMap();\n    /**\n     * WebSocketUsers 日志控制器\n     * Create by 叶云轩 at 2018/5/15 下午5:41\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);\n\n    private static WebSocketUsers ourInstance = new WebSocketUsers();\n\n    private WebSocketUsers() {\n    }\n\n    public static WebSocketUsers getInstance() {\n        return ourInstance;\n    }\n\n    /**\n     * 存储通道\n     *\n     * @param key     唯一键\n     * @param channel 通道\n     */\n    public static void put(String key, Channel channel) {\n        USERS.put(key, channel);\n    }\n\n    /**\n     * 移除通道\n     *\n     * @param channel 通道\n     *\n     * @return 移除结果\n     */\n    public static boolean remove(Channel channel) {\n        String key = null;\n        boolean b = USERS.containsValue(channel);\n        if (b) {\n            Set<Map.Entry<String, Channel>> entries = USERS.entrySet();\n            for (Map.Entry<String, Channel> entry : entries) {\n                Channel value = entry.getValue();\n                if (value.equals(channel)) {\n                    key = entry.getKey();\n                    break;\n                }\n            }\n        } else {\n            return true;\n        }\n        return remove(key);\n    }\n\n    /**\n     * 移出通道\n     *\n     * @param key 键\n     */\n    public static boolean remove(String key) {\n        Channel remove = USERS.remove(key);\n        boolean containsValue = USERS.containsValue(remove);\n        LOGGER.info(\"\\n\\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\\n\" +\n                \"\\t├ [移出结果]: {}\\n\" +\n                \"\\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\", containsValue ? \"失败\" : \"成功\");\n        return containsValue;\n    }\n\n    /**\n     * 获取在线用户列表\n     *\n     * @return 返回用户集合\n     */\n    public static ConcurrentMap<String, Channel> getUSERS() {\n        return USERS;\n    }\n\n    /**\n     * 群发消息\n     *\n     * @param message 消息内容\n     */\n    public static void sendMessageToUsers(String message) {\n        Collection<Channel> values = USERS.values();\n        for (Channel value : values) {\n            value.write(new TextWebSocketFrame(message));\n            value.flush();\n        }\n    }\n\n    /**\n     * 给某个人发送消息\n     *\n     * @param userName key\n     * @param message  消息\n     */\n    public static void sendMessageToUser(String userName, String message) {\n        Channel channel = USERS.get(userName);\n        channel.write(new TextWebSocketFrame(message));\n        channel.flush();\n    }\n}"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/main/resources/application.properties",
    "content": ""
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/test/java/org/yyx/netty/study/CodeCTest.java",
    "content": "package org.yyx.netty.study;\n\nimport org.junit.Test;\nimport org.msgpack.MessagePack;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yyx.netty.entity.User;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectOutputStream;\nimport java.nio.ByteBuffer;\n\n/**\n * <p>\n * create by 叶云轩 at 2018/4/12-下午6:50\n * contact by tdg_yyx@foxmail.com\n *\n * @author 叶云轩 contact by tdg_yyx@foxmail.com\n * @date 2018/8/15 - 12:34\n */\npublic class CodeCTest {\n    /**\n     * CodeCTest 日志控制器\n     * Create by 叶云轩 at 2018/4/12 下午6:53\n     * Concat at tdg_yyx@foxmail.com\n     */\n    private static final Logger LOGGER = LoggerFactory.getLogger(CodeCTest.class);\n\n    // region jdk序列化与基于ByteBuffer的二进制序列化字节数对比\n    @Test\n    public void testCodeC() throws IOException {\n        User user = new User();\n        user.setName(\"yyx\");\n        user.setId(1);\n        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);\n        objectOutputStream.writeObject(user);\n        objectOutputStream.flush();\n        objectOutputStream.close();\n        byte[] bytes = byteArrayOutputStream.toByteArray();\n        LOGGER.info(\"--- [JDK] {}\", bytes.length);\n        byteArrayOutputStream.close();\n        LOGGER.info(\"--- [byte] {}\", user.codeC().length);\n\n        MessagePack messagePack = new MessagePack();\n        // 序列化\n        byte[] write = messagePack.write(user);\n        LOGGER.info(\"--- [MessagePack] {}\", write.length);\n    }\n    // endregion\n\n    // region jdk序列化效率与基于ByteBuffer的二进制序列化效率对比\n    @Test\n    public void testCodec() throws Exception {\n        User user = new User();\n        user.setName(\"yyx\");\n        user.setId(1);\n        int loop = 1000000;\n        ByteArrayOutputStream bos;\n        ObjectOutputStream os;\n        long startTime = System.currentTimeMillis();\n        for (int i = 0; i < loop; i++) {\n            bos = new ByteArrayOutputStream();\n            os = new ObjectOutputStream(bos);\n            os.writeObject(user);\n            os.flush();\n            os.close();\n            byte[] bytes = bos.toByteArray();\n            bos.close();\n        }\n        long endTime = System.currentTimeMillis();\n        LOGGER.info(\"--- [jdk耗时] {}\", endTime - startTime + \"ms\");\n        ByteBuffer buffer = ByteBuffer.allocate(1024);\n        startTime = System.currentTimeMillis();\n        for (int i = 0; i < loop; i++) {\n            byte[] bytes = user.codeC(buffer);\n        }\n        endTime = System.currentTimeMillis();\n        LOGGER.info(\"--- [byte耗时] {}\", endTime - startTime + \"ms\");\n        startTime = System.currentTimeMillis();\n        MessagePack messagePack = new MessagePack();\n        for (int i = 0; i < loop; i++) {\n            // 序列化\n            byte[] write = messagePack.write(user);\n        }\n        endTime = System.currentTimeMillis();\n        LOGGER.info(\"--- [MessagePack 耗时] {}\", endTime - startTime + \"ms\");\n\n\n    }\n    // endregion\n\n}\n"
  },
  {
    "path": "spring-boot-netty/study-netty/study-server/src/test/java/org/yyx/netty/study/StudyServerApplicationTests.java",
    "content": "package org.yyx.netty.study;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.yyx.netty.study.echo.megpack.MessagePackServer;\nimport org.yyx.netty.study.websocket.WebSocketServer;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest\npublic class StudyServerApplicationTests {\n\n    private final int port = 8080;\n\n    // region 启动不考虑粘包/拆包问题的netty服务\n    @Test\n    public void startNettyServer1() throws Exception {\n        new org.yyx.netty.study.time.demo1.TimeServer().bind(port);\n    }\n    // endregion\n\n    // region 启动模拟粘包/拆包问题的netty服务\n    @Test\n    public void startNettyServer2() throws Exception {\n        new org.yyx.netty.study.time.demo2.TimeServer().bind(port);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty服务 - LineBasedFrameDecoder 实现\n    @Test\n    public void startNettyServer3() throws Exception {\n        new org.yyx.netty.study.time.demo3.TimeServer().bind(port);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty服务 - DelimiterBasedFrameDecoder 实现\n    @Test\n    public void startNettyServer4() throws Exception {\n        new org.yyx.netty.study.echo.delimiter.EchoServer().bind(port);\n    }\n    // endregion\n\n    // region 启动已解决粘包/拆包问题的netty服务 - FixedLengthFrameDecoder 实现\n    @Test\n    public void startNettyServer5() throws Exception {\n        new org.yyx.netty.study.echo.fixlength.EchoServer().bind(port);\n    }\n    // endregion\n\n    // region 启动不考虑粘包/拆包问题 基于MessagePack编解码的Netty服务\n    @Test\n    public void testMessagePackEchoServer() throws Exception {\n        new MessagePackServer().bind(port);\n    }\n    // endregion\n\n    // region 启动WebSocket服务器\n    @Test\n    public void startWebSocketServer() throws Exception {\n        new WebSocketServer().run(9999);\n    }\n    // endregion\n}"
  }
]