Repository: marquisXuan/netty Branch: master Commit: 4c3c96c00a7e Files: 95 Total size: 146.3 KB Directory structure: gitextract_3mkei390/ ├── .gitignore ├── README.md └── spring-boot-netty/ ├── netty-commons/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── yyx/ │ └── netty/ │ ├── entity/ │ │ ├── MethodInvokeMeta.java │ │ ├── NullWritable.java │ │ └── User.java │ ├── exception/ │ │ ├── ErrorParamsException.java │ │ └── NoUseableChannel.java │ └── util/ │ ├── ObjectCodec.java │ └── ObjectSerializerUtils.java ├── netty-demo/ │ ├── netty-client/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── yyx/ │ │ │ │ └── netty/ │ │ │ │ ├── NettyClientApplication.java │ │ │ │ ├── action/ │ │ │ │ │ └── MainAction.java │ │ │ │ ├── client/ │ │ │ │ │ ├── NettyClient.java │ │ │ │ │ ├── NettyClientHandler.java │ │ │ │ │ ├── NettyClientHandlerAdapter.java │ │ │ │ │ ├── NettyClientListener.java │ │ │ │ │ └── RPCProxyFactoryBean.java │ │ │ │ ├── config/ │ │ │ │ │ ├── NettyConfig.java │ │ │ │ │ └── NettyConfiguration.java │ │ │ │ └── rpc/ │ │ │ │ ├── service/ │ │ │ │ │ └── DemoService.java │ │ │ │ └── util/ │ │ │ │ ├── ChannelUtil.java │ │ │ │ ├── NettyBeanScanner.java │ │ │ │ ├── PackageClassUtils.java │ │ │ │ ├── RemoteMethodInvokeUtil.java │ │ │ │ └── WrapMethodUtils.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── yyx/ │ │ └── netty/ │ │ └── client/ │ │ └── NettyClientApplicationTests.java │ ├── netty-server/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── yyx/ │ │ │ │ └── netty/ │ │ │ │ ├── NettyServerApplication.java │ │ │ │ ├── rpc/ │ │ │ │ │ └── service/ │ │ │ │ │ └── DemoService.java │ │ │ │ └── server/ │ │ │ │ ├── adapter/ │ │ │ │ │ └── ServerChannelHandlerAdapter.java │ │ │ │ ├── config/ │ │ │ │ │ └── NettyServerConfig.java │ │ │ │ ├── dispatcher/ │ │ │ │ │ └── RequestDispatcher.java │ │ │ │ ├── impl/ │ │ │ │ │ └── DemoServiceImpl.java │ │ │ │ └── listener/ │ │ │ │ └── NettyServerListener.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── yyx/ │ │ └── netty/ │ │ └── NettyServerApplicationTests.java │ └── pom.xml ├── pom.xml └── study-netty/ ├── pom.xml ├── study-client/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── yyx/ │ │ │ └── netty/ │ │ │ └── study/ │ │ │ ├── StudyClientApplication.java │ │ │ ├── codec/ │ │ │ │ └── msgpack/ │ │ │ │ ├── MsgPackDecoder.java │ │ │ │ └── MsgPackEncoder.java │ │ │ ├── echo/ │ │ │ │ ├── delimiter/ │ │ │ │ │ ├── EchoClient.java │ │ │ │ │ └── EchoClientHandler.java │ │ │ │ ├── fixlength/ │ │ │ │ │ ├── EchoClient.java │ │ │ │ │ └── EchoClientHandler.java │ │ │ │ └── megpack/ │ │ │ │ ├── MessagePackClient.java │ │ │ │ └── MessagePackClientHandler.java │ │ │ ├── time/ │ │ │ │ ├── demo1/ │ │ │ │ │ ├── TimeClient.java │ │ │ │ │ └── TimeClientHandler.java │ │ │ │ ├── demo2/ │ │ │ │ │ ├── TimeClient.java │ │ │ │ │ └── TimeClientHandler.java │ │ │ │ └── demo3/ │ │ │ │ ├── TimeClient.java │ │ │ │ └── TimeClientHandler.java │ │ │ └── websocket/ │ │ │ ├── WebSocketClient.java │ │ │ ├── WebSocketClientHandler.java │ │ │ ├── WebSocketConstant.java │ │ │ ├── WebSocketHandlerClient.java │ │ │ ├── WebSocketMessage.java │ │ │ └── WebSocketUsers.java │ │ └── resources/ │ │ └── application.properties │ └── test/ │ └── java/ │ └── org/ │ └── yyx/ │ └── netty/ │ └── study/ │ └── StudyClientApplicationTests.java └── study-server/ ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── org/ │ │ └── yyx/ │ │ └── netty/ │ │ └── study/ │ │ ├── StudyServerApplication.java │ │ ├── codec/ │ │ │ └── msgpack/ │ │ │ ├── MsgPackDecoder.java │ │ │ └── MsgPackEncoder.java │ │ ├── echo/ │ │ │ ├── delimiter/ │ │ │ │ ├── EchoServer.java │ │ │ │ └── EchoServerHandler.java │ │ │ ├── fixlength/ │ │ │ │ ├── EchoServer.java │ │ │ │ └── EchoServerHandler.java │ │ │ └── megpack/ │ │ │ ├── MessagePackServer.java │ │ │ └── MessagePackServerHandler.java │ │ ├── time/ │ │ │ ├── demo1/ │ │ │ │ ├── ChildChannelHandler.java │ │ │ │ ├── TimeServer.java │ │ │ │ └── TimeServerHandler.java │ │ │ ├── demo2/ │ │ │ │ ├── ChildChannelHandler.java │ │ │ │ ├── TimeServer.java │ │ │ │ └── TimeServerHandler.java │ │ │ └── demo3/ │ │ │ ├── ChildChannelHandler.java │ │ │ ├── TimeServer.java │ │ │ └── TimeServerHandler.java │ │ └── websocket/ │ │ ├── WebSocketChildHandler.java │ │ ├── WebSocketConstant.java │ │ ├── WebSocketMessage.java │ │ ├── WebSocketServer.java │ │ ├── WebSocketServerHandler.java │ │ └── WebSocketUsers.java │ └── resources/ │ └── application.properties └── test/ └── java/ └── org/ └── yyx/ └── netty/ └── study/ ├── CodeCTest.java └── StudyServerApplicationTests.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* *.DS_Store *.iml .idea/ target/ ================================================ FILE: README.md ================================================ # spring boot netty ### 资源库说明 首先,不是一个项目.而是一个DEMO性质的代码分享. **当前资源库中包含了两大部分** 1. netty-demo下的可以在项目中使用的netty与SpringBoot集成的demo 2. 在study-netty下的netty相关的基础学习demo ## netty-demo #### 简述 这是我之前博客中的集成代码,当然,如今的代码已经不完全是博文中所述那样.这里做了相应的优化与升级.主要包含以下几点: 1. 性能优化,不再循环中与服务端创建连接. 2. 代码优化,客户端代码添加了更多的注释,以便理解这样的做法. 3. 功能优化,添加了保持长连接的心跳检测功能,并添加了测试类模拟测试. 原博文地址: [开源中国](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) #### PS 具体项目说明详见内部项目的说明文档 ## study-netty #### 简述 当前目录下的项目为netty基础学习demo. #### PS 具体项目说明详见内部项目的说明文档 ## End #### 作者QQ: 562638362 #### 作者邮箱:marquis_xuan@163.com ================================================ FILE: spring-boot-netty/netty-commons/pom.xml ================================================ spring-boot-netty org.yyx.netty 0.0.1-SNAPSHOT 4.0.0 netty-commons ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/MethodInvokeMeta.java ================================================ package org.yyx.netty.entity; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.io.Serializable; /** * 记录调用方法的元信息 * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/14 - 22:46 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class MethodInvokeMeta implements Serializable { private static final long serialVersionUID = 5429914235135594820L; /** * 接口 */ private Class interfaceClass; /** * 方法名 */ private String methodName; /** * 参数 */ private Object[] args; /** * 返回值类型 */ private Class returnType; /** * 参数类型 */ private Class[] parameterTypes; public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } public Class getInterfaceClass() { return interfaceClass; } public void setInterfaceClass(Class interfaceClass) { this.interfaceClass = interfaceClass; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Class[] getParameterTypes() { return parameterTypes; } public void setParameterTypes(Class[] parameterTypes) { this.parameterTypes = parameterTypes; } public Class getReturnType() { return returnType; } public void setReturnType(Class returnType) { this.returnType = returnType; } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/NullWritable.java ================================================ package org.yyx.netty.entity; import lombok.Data; import java.io.Serializable; /** * 服务器可能返回空的处理 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 11:57 */ @Data public class NullWritable implements Serializable { /** * 序列化标识 */ private static final long serialVersionUID = 2123827169429254101L; /** * 单例 */ private static NullWritable instance = new NullWritable(); /** * 私有构造器 */ private NullWritable() { } /** * 返回代表Null的对象 * * @return {@link NullWritable} 当方法返回值为void时或返回值为null时返回此对象 */ public static NullWritable nullWritable() { return instance; } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/entity/User.java ================================================ package org.yyx.netty.entity; import lombok.Data; import lombok.NoArgsConstructor; import org.msgpack.annotation.Message; import java.io.Serializable; import java.nio.ByteBuffer; /** * 用户实体 *

* create by 叶云轩 at 2018/3/3-下午1:48 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 11:55 */ @Data @NoArgsConstructor @Message public class User implements Serializable { /** * 序列化标识 */ private static final long serialVersionUID = -5462474276911290451L; /** * 编号 */ private int id; /** * 姓名 */ private String name; /** * 分数 */ private double source; /** * 领导 */ private User leader; public byte[] codeC() { ByteBuffer allocate = ByteBuffer.allocate(1024); byte[] bytes = this.name.getBytes(); allocate.putInt(bytes.length); allocate.put(bytes); allocate.putInt(this.id); allocate.flip(); bytes = null; byte[] res = new byte[allocate.remaining()]; allocate.get(res); return res; } public byte[] codeC(ByteBuffer buffer) { buffer.clear(); byte[] bytes = this.name.getBytes(); buffer.putInt(bytes.length); buffer.put(bytes); buffer.putInt(this.id); buffer.flip(); bytes = null; byte[] res = new byte[buffer.remaining()]; buffer.get(res); return res; } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/exception/ErrorParamsException.java ================================================ package org.yyx.netty.exception; /** * 参数错误异常 *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-14:41 */ public class ErrorParamsException extends RuntimeException { private static final long serialVersionUID = -623198335011996153L; public ErrorParamsException() { super(); } public ErrorParamsException(String message) { super(message); } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/exception/NoUseableChannel.java ================================================ package org.yyx.netty.exception; /** * 没有可用的通道异常 *

* * @author 叶云轩 at marquis_xuan@163.com * @date 2018/11/2-16:00 */ public class NoUseableChannel extends RuntimeException{ private static final long serialVersionUID = 7762465537123947683L; public NoUseableChannel() { super(); } public NoUseableChannel(String message) { super(message); } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/util/ObjectCodec.java ================================================ package org.yyx.netty.util; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import java.util.List; /** *

* create by 叶云轩 at 2018/3/3-下午1:42 * contact by tdg_yyx@foxmail.com */ public class ObjectCodec extends MessageToMessageCodec { @Override protected void encode(ChannelHandlerContext ctx, Object msg, List out) { byte[] data = ObjectSerializerUtils.serilizer(msg); ByteBuf buf = Unpooled.buffer(); buf.writeBytes(data); out.add(buf); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) { byte[] bytes = new byte[msg.readableBytes()]; msg.readBytes(bytes); Object deSerilizer = ObjectSerializerUtils.deSerilizer(bytes); out.add(deSerilizer); } } ================================================ FILE: spring-boot-netty/netty-commons/src/main/java/org/yyx/netty/util/ObjectSerializerUtils.java ================================================ package org.yyx.netty.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; /** * 对象序列化工具 *

* create by 叶云轩 at 2018/3/3-下午1:43 * contact by tdg_yyx@foxmail.com */ public class ObjectSerializerUtils { /** * ObjectSerializerUtils 日志控制器 * Create by 叶云轩 at 2018/3/3 下午1:43 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(ObjectSerializerUtils.class); /** * 反序列化 * * @param data * @return */ public static Object deSerilizer(byte[] data) { if (data != null && data.length > 0) { try { ByteArrayInputStream bis = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } catch (Exception e) { LOGGER.info("[异常信息] {}", e.getMessage()); e.printStackTrace(); } return null; } else { LOGGER.info("[反序列化] 入参为空"); return null; } } /** * 序列化对象 * * @param obj * @return */ public static byte[] serilizer(Object obj) { if (obj != null) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.flush(); oos.close(); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } else { return null; } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/README.md ================================================ # Netty-client > 该项目源自博主项目中实际应用场景。主要用作RPC通信中的客户端处理。 ## 阅读说明 ### 阅读顺序: netty-client阅读顺序参考以下链条 ```java NettyConfiguration -> NettyBeanScanner -> PackageClassUtils(工具) -> RPCProxyFactoryBean -> WrapMethodUtils(工具) -> NettyClient -> CustomChannelInitializerClient -> ClientChannelHandlerAdapter ``` 由NettyConfiguration入手,获取配置文件中的相关配置。 ### 项目阅读 主要见博客: [叶云轩的知识库](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) **更直接的参见源码注释。** ### 更新说明 ------ #### 2018-11-01 1. 添加阅读思路,整理待优化细节 ------ ================================================ FILE: spring-boot-netty/netty-demo/netty-client/pom.xml ================================================ 4.0.0 netty-client 0.0.1-SNAPSHOT jar netty-client netty-client with spring boot org.yyx.netty netty-demo 0.0.1-SNAPSHOT org.springframework.boot spring-boot-configuration-processor true org.yyx.netty netty-commons 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/NettyClientApplication.java ================================================ package org.yyx.netty; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:29 */ @SpringBootApplication public class NettyClientApplication { public static void main(String[] args) { SpringApplication.run(NettyClientApplication.class, args); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/action/MainAction.java ================================================ package org.yyx.netty.action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.yyx.netty.entity.User; import org.yyx.netty.rpc.service.DemoService; import javax.annotation.Resource; /** * 主要用来进行模拟测试的类.就不用写接口来进行测试了 *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:05 */ @Component public class MainAction { /** * MainAction 日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(MainAction.class); /** * 测试业务 */ @Resource private DemoService demoService; /** * 真正远程调用的方法 * @throws InterruptedException interruptedException */ public void call() throws InterruptedException { // 用于模拟服务器正常启动后,人工调用远程服务代码 Thread.sleep(10 * 1000); LOGGER.warn("[准备进行业务测试]"); LOGGER.warn("[rpc测试] "); int sum = demoService.sum(5, 8); LOGGER.warn("[rpc测试结果] {}", sum); LOGGER.warn("[字符串测试] "); String print = demoService.print(); LOGGER.warn("[字符串测试结果] {}", print); LOGGER.warn("[对象测试] "); User userInfo = demoService.getUserInfo(); LOGGER.warn("[对象测试结果] {}", userInfo); LOGGER.warn("[异常测试]"); try { double division = demoService.division(3, 0); LOGGER.warn("[异常测试结果] {}", division); } catch (Exception e) { LOGGER.error("[服务器异常] {}", e.getMessage()); } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClient.java ================================================ package org.yyx.netty.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * netty客户端第二个版本 *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:08 */ public class NettyClient { /** * NettyClient 志控制器 * Create by 叶云轩 at 2018/3/3 下午2:08 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(NettyClient.class); private static int retry = 0; /** * 初始化Bootstrap实例, 此实例是netty客户端应用开发的入口 */ private Bootstrap bootstrap; /** * 工人线程组 */ private EventLoopGroup worker; /** * 远程端口 */ private int port; /** * 远程服务器url */ private String url; /** * 默认重连机制为10次 */ private int MAX_RETRY_TIMES = 10; public NettyClient(int port, String url) { this.port = port; this.url = url; bootstrap = new Bootstrap(); worker = new NioEventLoopGroup(); bootstrap.group(worker); bootstrap.channel(NioSocketChannel.class); } public void start() { LOGGER.info("{} -> [启动连接] {}:{}", this.getClass().getName(), url, port); bootstrap.handler(new NettyClientHandler()); ChannelFuture f = bootstrap.connect(url, port); try { f.channel().closeFuture().sync(); } catch (InterruptedException e) { retry++; if (retry > MAX_RETRY_TIMES) { throw new RuntimeException("调用Wrong"); } else { try { Thread.sleep(100); } catch (InterruptedException e1) { e1.printStackTrace(); } LOGGER.info("第{}次尝试....失败", retry); start(); } } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientHandler.java ================================================ package org.yyx.netty.client; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.timeout.IdleStateHandler; import org.yyx.netty.util.ObjectCodec; import java.util.concurrent.TimeUnit; /** *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:17 */ public class NettyClientHandler extends ChannelInitializer { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 基于定长的方式解决粘包/拆包问题 pipeline.addLast(new LengthFieldPrepender(2)); pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 2, 0, 2)); // 序列化方式 可使用 MsgPack 或 Protobuf 进行序列化扩展 具体可参考study-netty项目下的相关使用例子 pipeline.addLast(new ObjectCodec()); // 心跳机制 pipeline.addLast(new IdleStateHandler(3, 10, 0, TimeUnit.SECONDS)); pipeline.addLast(new NettyClientHandlerAdapter()); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientHandlerAdapter.java ================================================ package org.yyx.netty.client; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.timeout.IdleStateEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.rpc.util.ChannelUtil; /** * 自定义的NettyClientHandler *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:20 */ public class NettyClientHandlerAdapter extends ChannelHandlerAdapter { /** * NettyClientHandlerAdapter 日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientHandlerAdapter.class); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error("{} -> [通道异常] {}", this.getClass().getName(), ctx.channel().id()); ChannelUtil.remove(ctx.channel()); } @Override public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception { LOGGER.info("{} -> [连接建立成功] {}", this.getClass().getName(), channelHandlerContext.channel().id()); // 注册通道 ChannelUtil.registerChannel(channelHandlerContext.channel()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (!(msg instanceof Exception)) { LOGGER.info("{} -> [客户端收到的消息] {}", this.getClass().getName(), msg); } String longText = ctx.channel().id().asLongText(); String resultKey = ChannelUtil.getResultKey(longText); ChannelUtil.calculateResult(resultKey, msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { LOGGER.info("{} -> [客户端消息接收完毕] {}", this.getClass().getName(), ctx.channel().id()); super.channelReadComplete(ctx); boolean active = ctx.channel().isActive(); LOGGER.info("{} -> [此时通道状态] {}", this.getClass().getName(), active); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { LOGGER.info("{} -> [客户端心跳监测发送] 通道编号:{}", this.getClass().getName(), ctx.channel().id()); if (evt instanceof IdleStateEvent) { ctx.writeAndFlush("ping-pong-ping-pong"); } else { super.userEventTriggered(ctx, evt); } } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { LOGGER.info("{} -> [关闭通道] {}", this.getClass().getName(), ctx.channel().id()); super.close(ctx, promise); // ChannelUtil.remove(ctx.channel()); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/NettyClientListener.java ================================================ package org.yyx.netty.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.yyx.netty.action.MainAction; import org.yyx.netty.config.NettyConfig; import javax.annotation.Resource; /** * netty客户端监听器 *

* 主要用于延迟测试RPC和启动NettyClient * * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:03 */ @Order(0) @Component public class NettyClientListener implements CommandLineRunner { /** * NettyClientListener 日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientListener.class); /** * netty客户端配置 */ @Resource private NettyConfig nettyConfig; /** * 主要用于测试RPC场景的类。集成到自己的业务中就不需要此依赖 */ @Resource private MainAction mainAction; @Override public void run(String... args) throws Exception { LOGGER.info("{} -> [准备进行与服务端通信]", this.getClass().getName()); // region 模拟RPC场景 Thread t1 = new Thread(() -> { try { mainAction.call(); } catch (InterruptedException e) { e.printStackTrace(); } }); // 使用一个线程模拟Client启动完毕后RPC的场景 t1.start(); // endregion // 获取服务器监听的端口 int port = nettyConfig.getPort(); // 获取服务器IP地址 String url = nettyConfig.getUrl(); // 启动NettyClient new NettyClient(port, url).start(); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/client/RPCProxyFactoryBean.java ================================================ package org.yyx.netty.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.yyx.netty.entity.MethodInvokeMeta; import org.yyx.netty.exception.ErrorParamsException; import org.yyx.netty.rpc.util.ChannelUtil; import org.yyx.netty.rpc.util.WrapMethodUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.UUID; /** * JDK动态代理类 *

* * @author 叶云轩 contact by marquis_xuan@163.com * @date 2018/11/1 - 15:49 */ public class RPCProxyFactoryBean extends AbstractFactoryBean implements InvocationHandler { /** * RPCProxyFactoryBean 日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(RPCProxyFactoryBean.class); /** * 远程服务接口 */ private Class interfaceClass; @Override public Class getObjectType() { return interfaceClass; } /** * 创建实例的方法 * * @return 由工厂创建的实例 */ @Override protected Object createInstance() { LOGGER.info("[代理工厂] 初始化代理Bean : {}", interfaceClass); // 返回代理类 return Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this); } /** * 动态调用方法的方法 * 该方法不会显示调用 * * @param proxy 被代理的实例 * @param method 调用的方法 * @param args 参数列表 * @return 返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws ErrorParamsException { LOGGER.info("{} -> [准备进行远程服务调用] ", this.getClass().getName()); LOGGER.info("{} -> [封装调用信息] ", this.getClass().getName()); final MethodInvokeMeta methodInvokeMeta = WrapMethodUtils.readMethod(interfaceClass, method, args); LOGGER.info("{} -> [远程服务调用封装完毕] 调用接口 -> {}\n调用方法 -> {}\n参数列表 -> {} \n 参数类型 -> {}" + "\n 返回值类型 -> {}", this.getClass().getName(), methodInvokeMeta.getInterfaceClass(), methodInvokeMeta.getMethodName() , methodInvokeMeta.getArgs(), methodInvokeMeta.getParameterTypes(), methodInvokeMeta.getReturnType()); // 构造一个时间戳 String uuid = System.currentTimeMillis() + UUID.randomUUID().toString(); // 真正开始使用netty进行通信的方法 ChannelUtil.remoteCall(methodInvokeMeta, uuid); Object result; do { // 接收返回信息 result = ChannelUtil.getResult(uuid); } while (result == null); // 服务器有可能返回异常信息,所以在这里可以进行异常处理 if (result instanceof ErrorParamsException) { throw (ErrorParamsException) result; } return result; } public void setInterfaceClass(Class interfaceClass) { this.interfaceClass = interfaceClass; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/config/NettyConfig.java ================================================ package org.yyx.netty.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * netty客户端配置 *

* * @author 叶云轩 at tdg_yyx@foxmail.com * @date 2018/11/1-17:13 */ @Component @ConfigurationProperties(prefix = "netty") @Data public class NettyConfig { private String url; private int port; } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/config/NettyConfiguration.java ================================================ package org.yyx.netty.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.yyx.netty.rpc.util.NettyBeanScanner; /** * Netty相关的初始化入口 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:32 */ @Configuration public class NettyConfiguration { /** * 初始化加载Netty相关bean的配置方法 * * @param basePackage 配置的包名 * @param clientName 配置的Netty实例对象名 * @return NettyBeanScanner */ @Bean public static NettyBeanScanner initNettyBeanScanner(@Value("${netty.basePackage}") String basePackage, @Value("${netty.clientName}") String clientName) { // 创建对象 return new NettyBeanScanner(basePackage, clientName); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/service/DemoService.java ================================================ package org.yyx.netty.rpc.service; import org.yyx.netty.entity.User; import org.yyx.netty.exception.ErrorParamsException; /** * 测试Service *

* create by 叶云轩 at 2018/3/3-下午1:46 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:29 */ public interface DemoService { /** * 除法运算 * * @param numberA 第一个数 * @param numberB 第二个数 * @return 结果 */ double division(int numberA, int numberB) throws ErrorParamsException; /** * 获取用户信息 * * @return 用户信息 */ User getUserInfo(); /** * 打印方法 * * @return 一个字符串 */ String print(); /** * 求和方法 * * @param numberA 第一个数 * @param numberB 第二个数 * @return 两数之和 */ int sum(int numberA, int numberB); } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/ChannelUtil.java ================================================ package org.yyx.netty.rpc.util; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.entity.MethodInvokeMeta; import org.yyx.netty.exception.NoUseableChannel; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; /** * 通道Util *

* * @author 叶云轩 at marquis_xuan@163.com * @date 2018/11/2-15:39 */ public class ChannelUtil { /** * 用于记录c-s连接后建立的通道 */ private static final Set CHANNELS = new ConcurrentSkipListSet<>(); /** * ChannelUtil日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(ChannelUtil.class); /** * 用于记录通道响应的结果集 */ private static final Map RESULT_MAP = new ConcurrentHashMap<>(); private ChannelUtil() { } /** * 计算结果集(存储响应结果) * * @param key 唯一标识 * @param result 结果集 */ public static void calculateResult(String key, Object result) { RESULT_MAP.put(key, result); } /** * 获取结果集的key * * @param key 保存的唯一标识 * @return 结果集的 key (通道标识) */ public static String getResultKey(String key) { return (String) getResult(key); } /** * 根据结果集的 key 获取结果集 * * @param key 结果集的key * @return 结果集 */ public static Object getResult(String key) { return RESULT_MAP.get(key); } /** * 注册通道 * * @param channel 通道 */ public static void registerChannel(Channel channel) { ChannelId id = channel.id(); LOGGER.info("{} -> [添加通道] {}", ChannelUtil.class.getName(), id); CHANNELS.add(channel); } /** * 获取回调结果 * * @param methodInvokeMeta 远程调用方法信息 * @param key 用于取结果的key值 */ public static void remoteCall(MethodInvokeMeta methodInvokeMeta, String key) { LOGGER.info("{} -> [远程调用] ", ChannelUtil.class.getName()); Iterator iterator = CHANNELS.iterator(); Channel channel; if (iterator.hasNext()) { channel = iterator.next(); } else { LOGGER.error("{} -> [没有活跃的通道] ", ChannelUtil.class); throw new NoUseableChannel("没有活跃的通道"); } // 将用于获取结果的key保存,以通道id为键 String channelID = channel.id().asLongText(); LOGGER.info("{} -> [保存获取结果的key] key - {} 通道id - {}", ChannelUtil.class, key, channelID); RESULT_MAP.put(channelID, key); ChannelFuture channelFuture = channel.writeAndFlush(methodInvokeMeta); // channelFuture.addListener(ChannelFutureListener.CLOSE); } public static void remove(Channel channel) { CHANNELS.remove(channel); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/NettyBeanScanner.java ================================================ package org.yyx.netty.rpc.util; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.yyx.netty.client.RPCProxyFactoryBean; import java.util.List; /** * 主要用于Netty框架初始化远程服务类 *

* BeanFactoryPostProcessor : Spring初始化bean时对外暴露的扩展点,即可以在Spring工厂初始化的时候做点什么,属于Spring知识点 * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:28 */ public class NettyBeanScanner implements BeanFactoryPostProcessor { /** * 装载bean的工厂 */ private DefaultListableBeanFactory beanFactory; /** * 包名 */ private String basePackage; /** * bean名(引用名) */ private String clientName; /** * 有参构造 * * @param basePackage 待扫描包名 * @param clientName netty客户端beanName */ public NettyBeanScanner(String basePackage, String clientName) { this.basePackage = basePackage; this.clientName = clientName; } /** * 注册远程接口Bean到Spring的bean工厂 * * @param beanFactory 装载bean的工厂 * @throws BeansException bean异常 */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = (DefaultListableBeanFactory) beanFactory; // 从目录中加载远程服务的接口 List resolverClass = PackageClassUtils.resolver(basePackage); for (String clazz : resolverClass) { // 获取接口名 String simpleName; // 接口全限定名 if (clazz.lastIndexOf('.') != -1) { simpleName = clazz.substring(clazz.lastIndexOf('.') + 1); } else { simpleName = clazz; } // 使用建造者模式创建一个Bean定义 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RPCProxyFactoryBean.class); // 对应 RPCProxyFactoryBean 类的 interfaceClass 属性 beanDefinitionBuilder.addPropertyValue("interfaceClass", clazz); // 对应 RPCProxyFactoryBean 的nettyClient 属性 -- 已删 // beanDefinitionBuilder.addPropertyReference("nettyClient", clientName); // 注册对bean的定义 this.beanFactory.registerBeanDefinition(simpleName, beanDefinitionBuilder.getRawBeanDefinition()); } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/PackageClassUtils.java ================================================ package org.yyx.netty.rpc.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; import java.util.List; /** *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:28 */ public class PackageClassUtils { private final static Logger LOGGER = LoggerFactory.getLogger(PackageClassUtils.class); /** * 获取一个目录下的所有文件 * * @param s * @param file * @param classStrs */ private static void getAllFile(String s, File file, List classStrs) { if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) for (File file1 : files) { getAllFile(s, file1, classStrs); } } else { String path = file.getPath(); String cleanPath = path.replaceAll("/", "."); String fileName = cleanPath.substring(cleanPath.indexOf(s), cleanPath.length()); LOGGER.info("[加载完成] 类文件:{}", fileName); classStrs.add(fileName); } } /** * 添加全限定类名到集合 * * @param classStrs 集合 * @return 类名集合 */ private static List getClassReferenceList(List classStrs, File file, String s) { File[] listFiles = file.listFiles(); if (listFiles != null && listFiles.length != 0) { for (File file2 : listFiles) { if (file2.isFile()) { String name = file2.getName(); String fileName = s + "." + name.substring(0, name.lastIndexOf('.')); LOGGER.info("[加载完成] 类文件:{}", fileName); classStrs.add(fileName); } } } return classStrs; } /** * 解析包参数 * * @param basePackage 包名 * @return 包名字符串集合 */ public static List resolver(String basePackage) { // 以";"分割开多个包名 String[] splitFHs = basePackage.split(";"); List classStrs = new ArrayList<>(); // s: com.yyx.util.* for (String s : splitFHs) { LOGGER.info("[加载类目录] {}", s); // 路径中是否存在".*" com.yyx.util.* boolean contains = s.contains(".*"); if (contains) { // 截断星号 com.yyx.util String filePathStr = s.substring(0, s.lastIndexOf(".*")); // 组装路径 com/yyx/util String filePath = filePathStr.replaceAll("\\.", "/"); // 获取路径 xxx/classes/com/yyx/util File file = new File(PackageClassUtils.class.getResource("/").getPath() + "/" + filePath); // 获取目录下获取文件 getAllFile(filePathStr, file, classStrs); } else { String filePath = s.replaceAll("\\.", "/"); File file = new File(PackageClassUtils.class.getResource("/").getPath() + "/" + filePath); classStrs = getClassReferenceList(classStrs, file, s); } } return classStrs; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/RemoteMethodInvokeUtil.java ================================================ package org.yyx.netty.rpc.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.yyx.netty.entity.MethodInvokeMeta; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:28 */ public class RemoteMethodInvokeUtil implements ApplicationContextAware { private ApplicationContext applicationContext; public Object processMethod(MethodInvokeMeta methodInvokeMeta) throws InvocationTargetException, IllegalAccessException { Class interfaceClass = methodInvokeMeta.getInterfaceClass(); Object bean = applicationContext.getBean(interfaceClass); Method[] declaredMethods = interfaceClass.getDeclaredMethods(); Method method = null; for (Method declaredMethod : declaredMethods) { if (methodInvokeMeta.getMethodName().equals(declaredMethod.getName())) { method = declaredMethod; } } Object invoke = method.invoke(bean, methodInvokeMeta.getArgs()); return invoke; } @Override public void setApplicationContext(ApplicationContext app) throws BeansException { applicationContext = app; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/java/org/yyx/netty/rpc/util/WrapMethodUtils.java ================================================ package org.yyx.netty.rpc.util; import org.yyx.netty.entity.MethodInvokeMeta; import java.lang.reflect.Method; /** * 封装接口调用的工具 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:27 */ public class WrapMethodUtils { /** * 封装 method 的元数据信息 * * @param interfaceClass 接口类 * @param method 方法 * @param args 参数列表 * @return 封装的对象 */ public static MethodInvokeMeta readMethod(Class interfaceClass, Method method, Object[] args) { MethodInvokeMeta methodInvokeMeta = new MethodInvokeMeta(); methodInvokeMeta.setInterfaceClass(interfaceClass); methodInvokeMeta.setArgs(args); methodInvokeMeta.setMethodName(method.getName()); methodInvokeMeta.setReturnType(method.getReturnType()); Class[] parameterTypes = method.getParameterTypes(); methodInvokeMeta.setParameterTypes(parameterTypes); return methodInvokeMeta; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/main/resources/application.yml ================================================ netty: port: 12345 basePackage: org.yyx.netty.rpc.service; clientName: nettyClient url: 127.0.0.1 ================================================ FILE: spring-boot-netty/netty-demo/netty-client/src/test/java/org/yyx/netty/client/NettyClientApplicationTests.java ================================================ package org.yyx.netty.client; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class NettyClientApplicationTests { @Test public void contextLoads() { } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/README.md ================================================ # netty-server >博文源码,基于SOA架构的服务端,使用Netty做RPC通信. 这是一个Netty服务端与SpringBoot集成的Demo. ================================================ FILE: spring-boot-netty/netty-demo/netty-server/pom.xml ================================================ 4.0.0 netty-server 0.0.1-SNAPSHOT jar netty-server 基于SpringBoot的Netty服务端整合代码 org.yyx.netty netty-demo 0.0.1-SNAPSHOT org.springframework.boot spring-boot-configuration-processor true org.yyx.netty netty-commons 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/NettyServerApplication.java ================================================ package org.yyx.netty; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.yyx.netty.server.listener.NettyServerListener; import javax.annotation.Resource; /** * Netty服务器启动类 * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:33 */ @SpringBootApplication public class NettyServerApplication implements CommandLineRunner { @Resource private NettyServerListener nettyServerListener; public static void main(String[] args) { SpringApplication.run(NettyServerApplication.class, args); } @Override public void run(String... args) { nettyServerListener.start(); } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/rpc/service/DemoService.java ================================================ package org.yyx.netty.rpc.service; import org.yyx.netty.entity.User; import org.yyx.netty.exception.ErrorParamsException; /** * 测试Service *

* create by 叶云轩 at 2018/3/3-下午1:46 * contact by tdg_yyx@foxmail.com */ public interface DemoService { /** * 除法运算 * * @param numberA 第一个数 * @param numberB 第二个数 * @return 结果 */ double division(int numberA, int numberB) throws ErrorParamsException; /** * 获取用户信息 * * @return 用户信息 */ User getUserInfo(); /** * 打印方法 * * @return 一个字符串 */ String print(); /** * 求和方法 * * @param numberA 第一个数 * @param numberB 第二个数 * @return 两数之和 */ int sum(int numberA, int numberB); } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/adapter/ServerChannelHandlerAdapter.java ================================================ package org.yyx.netty.server.adapter; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.yyx.netty.entity.MethodInvokeMeta; import org.yyx.netty.server.dispatcher.RequestDispatcher; import javax.annotation.Resource; /** * NettyServer通道适配器 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:39 */ @Component @Sharable public class ServerChannelHandlerAdapter extends ChannelHandlerAdapter { /** * ServerChannelHandlerAdapter 日志控制器 * Create by 叶云轩 at 2018/3/3 下午1:25 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(ServerChannelHandlerAdapter.class); /** * 注入请求分排器 */ @Resource private RequestDispatcher dispatcher; private int lossConnectCount = 0; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { LOGGER.error("{} -> [连接异常] {}通道异常,异常原因:{}", this.getClass().getName(), ctx.channel().id(), cause.getMessage()); ctx.close(); } /** * 服务器接收到消息时进行进行的处理 * * @param channelHandlerContext channelHandlerContext * @param msg msg */ @Override public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) { if (msg instanceof String) { if ("ping-pong-ping-pong".equals(msg)) { LOGGER.info("{} -> [心跳监测] {}:通道活跃", this.getClass().getName(), channelHandlerContext.channel().id()); // 心跳消息 lossConnectCount = 0; return; } } // 转换为MethodInvokeMeta MethodInvokeMeta invokeMeta = (MethodInvokeMeta) msg; LOGGER.info("{} -> [客户端信息] \n 方法名 - > {} \n 参数列表 -> {} \n " + "返回值 -> {} ", this.getClass().getName(), invokeMeta.getMethodName(), invokeMeta.getArgs() , invokeMeta.getReturnType()); // 具体的处理类 this.dispatcher.dispatcher(channelHandlerContext, invokeMeta); } /** * 触发器 * * @param channelHandlerContext channelHandlerContext * @param evt * @throws Exception exception */ @Override public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object evt) throws Exception { LOGGER.info("{} -> [已经有5秒中没有接收到客户端的消息了]", this.getClass().getName()); if (evt instanceof IdleStateEvent) { IdleStateEvent idleStateEvent = (IdleStateEvent) evt; if (idleStateEvent.state() == IdleState.READER_IDLE) { lossConnectCount++; if (lossConnectCount > 2) { LOGGER.info("{} -> [释放不活跃通道] {}", this.getClass().getName(), channelHandlerContext.channel().id()); channelHandlerContext.channel().close(); } } } else { super.userEventTriggered(channelHandlerContext, evt); } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/config/NettyServerConfig.java ================================================ package org.yyx.netty.server.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * Netty服务器配置信息 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:39 */ @Component @ConfigurationProperties(prefix = "netty") public class NettyServerConfig { /** * 端口 */ private int port; /** * 最大线程数 */ private int maxThreads; /** * 最大数据包长度 */ private int maxFrameLength; public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getMaxThreads() { return maxThreads; } public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; } public int getMaxFrameLength() { return maxFrameLength; } public void setMaxFrameLength(int maxFrameLength) { this.maxFrameLength = maxFrameLength; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/dispatcher/RequestDispatcher.java ================================================ package org.yyx.netty.server.dispatcher; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.yyx.netty.entity.MethodInvokeMeta; import org.yyx.netty.entity.NullWritable; import org.yyx.netty.exception.ErrorParamsException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 请求分排器 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:37 */ @Component public class RequestDispatcher implements ApplicationContextAware { /** * RequestDispatcher 日志输出 */ private static final Logger LOGGER = LoggerFactory.getLogger(RequestDispatcher.class); /** * Spring上下文 */ private ApplicationContext applicationContext; /** * 发送 * * @param channelHandlerContext channelHandlerContext * @param invokeMeta invokeMeta:用于记录远程调用的服务信息,即调用哪个接口中的哪个方法 */ public void dispatcher(final ChannelHandlerContext channelHandlerContext, final MethodInvokeMeta invokeMeta) { ChannelFuture channelFuture = null; // 指向的接口类 Class interfaceClass = invokeMeta.getInterfaceClass(); // 所调用的方法名 String name = invokeMeta.getMethodName(); // 方法的参数列表 Object[] args = invokeMeta.getArgs(); // 方法的参数列表按顺序对应的类型 Class[] parameterTypes = invokeMeta.getParameterTypes(); // 通过Spring获取实际对象 Object targetObject = this.applicationContext.getBean(interfaceClass); // 声明调用的方法对象 Method method; try { // 获取调用的方法对象 method = targetObject.getClass().getMethod(name, parameterTypes); } catch (NoSuchMethodException e) { // 尚未执行方法调用出现异常 LOGGER.error("[获取远程方法异常] {}", e.getMessage()); // 响应给客户端 channelFuture = channelHandlerContext.writeAndFlush(e); return; } finally { if (channelFuture != null) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } try { // 执行方法 Object result = method.invoke(targetObject, args); if (result == null) { // 方法没有返回值,返回NullWritable对象 LOGGER.info("{} -> [方法没有返回值,返回NullWritable对象]", this.getClass().getName()); channelFuture = channelHandlerContext.writeAndFlush(NullWritable.nullWritable()); } else { // 将方法执行结果写入到Channel LOGGER.info("{} -> [返回结果] {}", this.getClass().getName(), result); channelFuture = channelHandlerContext.writeAndFlush(result); } /* 虽然可以通过ChannelFuture的get()方法获取异步操作的结果,但完成时间是无法预测的,若不设置超时时间则有可能导致线程长时间被阻塞; 若是不能精确的设置超时时间则可能导致I/O操作中断. 因此,Netty建议通过GenericFutureListener接口执行异步操作结束后的回调. */ /// ChannelFutureListener.CLOSE = new ChannelFutureListener() { /// @Override /// public void operationComplete(ChannelFuture future) { /// future.channel().close(); /// } /// }; } catch (Exception e) { Throwable targetException = ((InvocationTargetException) e).getTargetException(); LOGGER.error("{} -> [方法执行异常] {}", this.getClass().getName()); if (targetException instanceof ErrorParamsException) { LOGGER.error("{} -> [异常信息] {}", this.getClass().getName(), targetException.getMessage()); } channelFuture = channelHandlerContext.writeAndFlush(targetException); } finally { if (channelFuture != null) { // channelFuture.addListener(ChannelFutureListener.CLOSE); } } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/impl/DemoServiceImpl.java ================================================ package org.yyx.netty.server.impl; import org.springframework.stereotype.Service; import org.yyx.netty.entity.User; import org.yyx.netty.exception.ErrorParamsException; import org.yyx.netty.rpc.service.DemoService; /** * 测试Service实现类 *

* * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/7/9 - 上午9:36 */ @Service public class DemoServiceImpl implements DemoService { @Override public double division(int numberA, int numberB) throws ErrorParamsException { if (numberB == 0) { throw new ErrorParamsException("除数不能为0"); } return numberA / numberB; } @Override public User getUserInfo() { User leader = new User(); leader.setId(1); leader.setName("上级"); leader.setSource(100); User user = new User(); user.setSource(80); user.setId(0); user.setName("基层"); user.setLeader(leader); return user; } @Override public String print() { return "这是来自服务器中DemoService接口的print方法打印的消息"; } @Override public int sum(int numberA, int numberB) { return numberA + numberB; } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/java/org/yyx/netty/server/listener/NettyServerListener.java ================================================ package org.yyx.netty.server.listener; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.timeout.IdleStateHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.yyx.netty.server.adapter.ServerChannelHandlerAdapter; import org.yyx.netty.server.config.NettyServerConfig; import org.yyx.netty.util.ObjectCodec; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; /** * Netty服务器监听器 *

* create by 叶云轩 at 2018/3/3-下午12:21 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:26 */ @Component public class NettyServerListener { /** * NettyServerListener 日志控制器 * Create by 叶云轩 at 2018/3/3 下午12:21 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerListener.class); /** * 创建bootstrap */ private ServerBootstrap serverBootstrap = new ServerBootstrap(); /** * BOSS */ private EventLoopGroup boss = new NioEventLoopGroup(); /** * Worker */ private EventLoopGroup work = new NioEventLoopGroup(); /** * 通道适配器 */ @Resource private ServerChannelHandlerAdapter channelHandlerAdapter; /** * NETT服务器配置类 */ @Resource private NettyServerConfig nettyConfig; /** * 关闭服务器方法 */ @PreDestroy public void close() { LOGGER.info("关闭服务器...."); //优雅退出 boss.shutdownGracefully(); work.shutdownGracefully(); } /** * 开启及服务线程 */ public void start() { // 从配置文件中(application.yml)获取服务端监听端口号 int port = nettyConfig.getPort(); serverBootstrap.group(boss, work) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)); try { //设置事件处理 serverBootstrap.childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 添加心跳支持 pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS)); // 基于定长的方式解决粘包/拆包问题 pipeline.addLast(new LengthFieldBasedFrameDecoder(nettyConfig.getMaxFrameLength() , 0, 2, 0, 2)); pipeline.addLast(new LengthFieldPrepender(2)); // 序列化 pipeline.addLast(new ObjectCodec()); pipeline.addLast(channelHandlerAdapter); } }); LOGGER.info("netty服务器在[{}]端口启动监听", port); ChannelFuture f = serverBootstrap.bind(port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { LOGGER.info("[出现异常] 释放资源"); boss.shutdownGracefully(); work.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/main/resources/application.yml ================================================ # netty配置 netty: # 端口号 port: 12345 # 最大线程数 maxThreads: 1024 # 数据包的最大长度 max_frame_length: 65535 ================================================ FILE: spring-boot-netty/netty-demo/netty-server/src/test/java/org/yyx/netty/NettyServerApplicationTests.java ================================================ package org.yyx.netty; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class NettyServerApplicationTests { @Test public void contextLoads() { } } ================================================ FILE: spring-boot-netty/netty-demo/pom.xml ================================================ 4.0.0 netty-demo 0.0.1-SNAPSHOT pom netty-demo 可以正常使用的Netty与SpringBoot集成的Demo org.yyx.netty spring-boot-netty 0.0.1-SNAPSHOT netty-server netty-client UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/pom.xml ================================================ 4.0.0 org.yyx.netty spring-boot-netty 0.0.1-SNAPSHOT pom spring-boot-netty Netty 学习之路 netty-server是基于Netty开发的一款Server端代码 netty-client是基于Netty开发的一款Client端代码 study-netty是学习Netty的相关Demo,记录了从0到1的整合过程等 study-netty netty-commons netty-demo org.springframework.boot spring-boot-starter-parent 2.0.1.RELEASE UTF-8 UTF-8 1.8 5.0.0.Alpha2 1.16.20 0.6.12 1.2.47 2.0.5.Final org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test io.netty netty-all ${netty} org.projectlombok lombok ${lombok} org.jboss.marshalling jboss-marshalling ${jboss.marshalling} org.msgpack msgpack ${msgpack} com.alibaba fastjson ${fastjson} org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/study-netty/pom.xml ================================================ 4.0.0 study-netty 0.0.1-SNAPSHOT pom study-netty study netty study-server study-client org.yyx.netty spring-boot-netty 0.0.1-SNAPSHOT org.yyx.netty netty-commons 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/study-netty/study-client/pom.xml ================================================ 4.0.0 study-client 0.0.1-SNAPSHOT jar study-client Demo project for Spring Boot org.yyx.netty study-netty 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/StudyClientApplication.java ================================================ package org.yyx.netty.study; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StudyClientApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(StudyClientApplication.class, args); } @Override public void run(String... args) throws Exception { } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackDecoder.java ================================================ package org.yyx.netty.study.codec.msgpack; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; /** * MsgPack解码器 *

* create by 叶云轩 at 2018/4/12-下午7:31 * contact by tdg_yyx@foxmail.com */ public class MsgPackDecoder extends MessageToMessageDecoder { /** * MsgPackDecoder 日志控制器 * Create by 叶云轩 at 2018/5/3 下午3:19 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackDecoder.class); @Override protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { final byte[] array; final int length = msg.readableBytes(); array = new byte[length]; msg.getBytes(msg.readerIndex(), array, 0, length); MessagePack messagePack = new MessagePack(); out.add(messagePack.read(array)); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackEncoder.java ================================================ package org.yyx.netty.study.codec.msgpack; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; /** * MsgPack编码器 *

* create by 叶云轩 at 2018/4/12-下午7:29 * contact by tdg_yyx@foxmail.com */ public class MsgPackEncoder extends MessageToByteEncoder { /** * MsgPackEncoder 日志控制器 * Create by 叶云轩 at 2018/5/3 下午3:18 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackEncoder.class); @Override protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { MessagePack messagePack = new MessagePack(); // 序列化 byte[] write = new byte[0]; try { write = messagePack.write(msg); } catch (IOException e) { e.printStackTrace(); } out.writeBytes(write); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/delimiter/EchoClient.java ================================================ package org.yyx.netty.study.echo.delimiter; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * MessagePackClient *

* create by 叶云轩 at 2018/4/12-下午4:28 * contact by tdg_yyx@foxmail.com */ public class EchoClient { public void connect(int port, String host) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes()); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture future = bootstrap.connect(host,port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 group.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/delimiter/EchoClientHandler.java ================================================ package org.yyx.netty.study.echo.delimiter; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-下午4:57 * contact by tdg_yyx@foxmail.com */ public class EchoClientHandler extends ChannelHandlerAdapter { static final String ECHO_REQ = "Hi,Welcome to Netty World.$_$"; /** * MessagePackClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:57 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoClientHandler.class); /** * 计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error("--- [异常] {}", cause.getMessage()); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 10; i++) { ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes())); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { LOGGER.info("--- [第{}次接收到服务器的消息] {} | [消息] {}", ++counter, msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/fixlength/EchoClient.java ================================================ package org.yyx.netty.study.echo.fixlength; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * MessagePackClient *

* create by 叶云轩 at 2018/4/12-下午4:28 * contact by tdg_yyx@foxmail.com */ public class EchoClient { public void connect(int port, String host) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes()); ch.pipeline().addLast(new FixedLengthFrameDecoder(20)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 group.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/fixlength/EchoClientHandler.java ================================================ package org.yyx.netty.study.echo.fixlength; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-下午4:57 * contact by tdg_yyx@foxmail.com */ public class EchoClientHandler extends ChannelHandlerAdapter { static final String ECHO_REQ = "Hi,Welcome to Netty World.$_$"; /** * MessagePackClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:57 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoClientHandler.class); /** * 计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error("--- [异常] {}", cause.getMessage()); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 10; i++) { ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes())); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { LOGGER.info("--- [第{}次接收到服务器的消息] {} | [消息] {}", ++counter, msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackClient.java ================================================ package org.yyx.netty.study.echo.megpack; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.yyx.netty.study.codec.msgpack.MsgPackDecoder; import org.yyx.netty.study.codec.msgpack.MsgPackEncoder; /** * MessagePackClient *

* create by 叶云轩 at 2018/4/12-下午4:28 * contact by tdg_yyx@foxmail.com */ public class MessagePackClient { /** * 客户端连接方法 * * @param port 端口号 * @param host 主机名 * * @throws Exception 异常 */ public void connect(int port, String host, int sendNumber) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); // ch.pipeline().addLast(new LengthFieldPrepender(2)); ch.pipeline().addLast(new MsgPackEncoder()); // ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)); ch.pipeline().addLast(new MsgPackDecoder()); ch.pipeline().addLast(new MessagePackClientHandler(sendNumber)); } }); ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 group.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackClientHandler.java ================================================ package org.yyx.netty.study.echo.megpack; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.entity.User; /** *

* create by 叶云轩 at 2018/4/12-下午4:57 * contact by tdg_yyx@foxmail.com */ public class MessagePackClientHandler extends ChannelHandlerAdapter { /** * MessagePackClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:57 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackClientHandler.class); private final int sendNumber; public MessagePackClientHandler(int sendNumber) { this.sendNumber = sendNumber; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { LOGGER.error("--- [异常] {}", cause.getMessage()); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) { User[] userInfo = UserInfo(); ctx.writeAndFlush(userInfo); for (User info : userInfo) { ctx.write(info); } ctx.flush(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { LOGGER.info("--- [收到服务器的消息] {}", msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } private User[] UserInfo() { User[] users = new User[sendNumber]; User user; for (int i = 0; i < sendNumber; i++) { user = new User(); user.setId(i); user.setName("YYX --->" + i); users[i] = user; } return users; } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo1/TimeClient.java ================================================ package org.yyx.netty.study.time.demo1; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** * Netty时间服务器客户端 *

* create by 叶云轩 at 2018/4/12-上午9:53 * contact by tdg_yyx@foxmail.com */ public class TimeClient { public void connect(int port, String host) throws Exception { // 配置客户端的NIO线程组 EventLoopGroup clientGroup = new NioEventLoopGroup(); try { // Client辅助启动类 Bootstrap bootstrap = new Bootstrap(); // 配置bootstrap bootstrap.group(clientGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * @param ch * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); // 发起异步连接操作 同步方法待成功 ChannelFuture future = bootstrap.connect(host, port).sync(); // 等待客户端链路关闭 future.channel().closeFuture().sync(); } finally { // 优雅退出,释放NIO线程组 clientGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo1/TimeClientHandler.java ================================================ package org.yyx.netty.study.time.demo1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-上午10:16 * contact by tdg_yyx@foxmail.com */ public class TimeClientHandler extends ChannelHandlerAdapter { /** * TimeClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午10:16 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class); /** * */ private final ByteBuf firstMessage; /** * */ private byte[] req; public TimeClientHandler() { byte[] req = "QUERY TIME ORDER".getBytes(); firstMessage = Unpooled.buffer(req.length); firstMessage.writeBytes(req); } /** * 捕捉异常 * * @param ctx * @param cause * * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.warn("--- [异常,释放资源] {}", cause.getMessage()); ctx.close(); } /** * 当客户端和服务端TCP链路建立成功之后调用此方法 * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端 * * @param ctx * * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(firstMessage); } /** * 服务端返回应答消息时,调用此方法 * * @param ctx * @param msg * * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; byte[] request = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(request); String body = new String(request, "utf-8"); LOGGER.info("--- [Now is] {}", body); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo2/TimeClient.java ================================================ package org.yyx.netty.study.time.demo2; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** * Netty时间服务器客户端 *

* create by 叶云轩 at 2018/4/12-上午9:53 * contact by tdg_yyx@foxmail.com */ public class TimeClient { public void connect(int port, String host) throws Exception { // 配置客户端的NIO线程组 EventLoopGroup clientGroup = new NioEventLoopGroup(); try { // Client辅助启动类 Bootstrap bootstrap = new Bootstrap(); // 配置bootstrap bootstrap.group(clientGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * @param ch * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); // 发起异步连接操作 同步方法待成功 ChannelFuture future = bootstrap.connect(host, port).sync(); // 等待客户端链路关闭 future.channel().closeFuture().sync(); } finally { // 优雅退出,释放NIO线程组 clientGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo2/TimeClientHandler.java ================================================ package org.yyx.netty.study.time.demo2; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-上午10:16 * contact by tdg_yyx@foxmail.com */ public class TimeClientHandler extends ChannelHandlerAdapter { /** * TimeClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午10:16 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class); /** * 模拟粘包/拆包问题计数器 */ private int counter; /** * */ private byte[] req; public TimeClientHandler() { // region 模拟粘包/拆包问题相关代码 req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes(); // endregion } /** * 捕捉异常 * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.warn("--- [异常,释放资源] {}", cause.getMessage()); ctx.close(); } /** * 当客户端和服务端TCP链路建立成功之后调用此方法 * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端 * * @param ctx * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // region 模拟粘包/拆包问题相关代码 ByteBuf message; for (int i = 0; i < 100; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } // endregion } /** * 服务端返回应答消息时,调用此方法 * * @param ctx * @param msg * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; byte[] request = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(request); String body = new String(request, "utf-8"); LOGGER.info("--- [Now is] {} | [counter] {}", body, ++counter); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo3/TimeClient.java ================================================ package org.yyx.netty.study.time.demo3; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; /** * Netty时间服务器客户端 *

* create by 叶云轩 at 2018/4/12-上午9:53 * contact by tdg_yyx@foxmail.com */ public class TimeClient { public void connect(int port, String host) throws Exception { // 配置客户端的NIO线程组 EventLoopGroup clientGroup = new NioEventLoopGroup(); try { // Client辅助启动类 Bootstrap bootstrap = new Bootstrap(); // 配置bootstrap bootstrap.group(clientGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * @param ch * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { // region 解决粘包/拆包问题相关代码 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); // endregion ch.pipeline().addLast(new TimeClientHandler()); } }); // 发起异步连接操作 同步方法待成功 ChannelFuture future = bootstrap.connect(host, port).sync(); // 等待客户端链路关闭 future.channel().closeFuture().sync(); } finally { // 优雅退出,释放NIO线程组 clientGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/time/demo3/TimeClientHandler.java ================================================ package org.yyx.netty.study.time.demo3; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-上午10:16 * contact by tdg_yyx@foxmail.com */ public class TimeClientHandler extends ChannelHandlerAdapter { /** * TimeClientHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午10:16 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class); /** * 模拟粘包/拆包问题计数器 */ private int counter; /** * */ private byte[] req; public TimeClientHandler() { // region 模拟粘包/拆包问题相关代码 req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes(); // endregion } /** * 捕捉异常 * * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.warn("--- [异常,释放资源] {}", cause.getMessage()); ctx.close(); } /** * 当客户端和服务端TCP链路建立成功之后调用此方法 * 发送指令给服务端,调用ChannelHandlerContext.writeAndFlush方法将请求消息发送给服务端 * * @param ctx * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // region 模拟粘包/拆包问题相关代码 ByteBuf message; for (int i = 0; i < 100; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } // endregion } /** * 服务端返回应答消息时,调用此方法 * * @param ctx * @param msg * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // region 解决粘包/拆包问题相关代码 String body = (String) msg; // endregion LOGGER.info("--- [Now is] {} | [counter] {}", body, ++counter); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketClient.java ================================================ package org.yyx.netty.study.websocket; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import java.net.URI; /** * webSocketClient *

* create by 叶云轩 at 2018/5/17-下午6:04 * contact by tdg_yyx@foxmail.com */ public class WebSocketClient { public void connect(int port, String host, String userName) throws Exception { // 配置客户端的NIO线程组 EventLoopGroup clientGroup = new NioEventLoopGroup(); try { // Client辅助启动类 Bootstrap bootstrap = new Bootstrap(); // 配置bootstrap WebSocketClientHandler webSocketClientHandler = new WebSocketClientHandler( WebSocketClientHandshakerFactory.newHandshaker(new URI("ws://172.0.0.1:9999/websocket/" + userName) , WebSocketVersion.V13, null, false, new DefaultHttpHeaders()) ); bootstrap.group(clientGroup) .channel(NioSocketChannel.class) .handler(new WebSocketHandlerClient(webSocketClientHandler)); // 发起异步连接操作 同步方法待成功 Channel channel = bootstrap.connect(host, port).sync().channel(); // 等待客户端链路关闭 channel.closeFuture().sync(); } finally { // 优雅退出,释放NIO线程组 clientGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketClientHandler.java ================================================ package org.yyx.netty.study.websocket; import com.alibaba.fastjson.JSONObject; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.util.CharsetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/5/17-下午6:06 * contact by tdg_yyx@foxmail.com */ public class WebSocketClientHandler extends SimpleChannelInboundHandler { /** * WebSocketClientHandler 日志控制器 * Create by 叶云轩 at 2018/5/17 下午6:10 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketClientHandler.class); private final WebSocketClientHandshaker webSocketClientHandshaker; private ChannelPromise handshakeFuture; WebSocketClientHandler(WebSocketClientHandshaker webSocketClientHandshaker) { this.webSocketClientHandshaker = webSocketClientHandshaker; } @Override public void handlerAdded(ChannelHandlerContext ctx) { handshakeFuture = ctx.newPromise(); } /** * 异常 * * @param channelHandlerContext channelHandlerContext * @param cause 异常 */ @Override public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [exception]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", cause.getMessage()); channelHandlerContext.close(); } /** * 当客户端主动链接服务端的链接后,调用此方法 * * @param channelHandlerContext ChannelHandlerContext */ @Override public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [建立连接]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); Channel channel = channelHandlerContext.channel(); // 握手 webSocketClientHandshaker.handshake(channel); } /** * 与服务端断开连接时 * * @param channelHandlerContext channelHandlerContext */ @Override public void channelInactive(ChannelHandlerContext channelHandlerContext) { Channel channel = channelHandlerContext.channel(); WebSocketUsers.remove(channel); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [断开连接]:client [{}]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", channel.remoteAddress()); } /** * 读完之后调用的方法 * * @param channelHandlerContext ChannelHandlerContext */ @Override public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception { channelHandlerContext.flush(); } @Override protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { // 获取通道 Channel channel = channelHandlerContext.channel(); // 如果没有握手完成进行握手 if (!webSocketClientHandshaker.isHandshakeComplete()) { webSocketClientHandshaker.finishHandshake(channel, (FullHttpResponse) msg); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [握手成功]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); handshakeFuture.setSuccess(); // 将当前登陆用户保存起来 WebSocketUsers.put("client1-" + getUserNameInPath(), channel); return; } channelHandlerContext.flush(); if (msg instanceof FullHttpResponse) { FullHttpResponse response = (FullHttpResponse) msg; throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; if (frame instanceof TextWebSocketFrame) { TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) frame; LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [服务器响应消息]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", textWebSocketFrame.text()); WebSocketMessage webSocketMessage = new WebSocketMessage(); webSocketMessage.setHeader(WebSocketMessage.Type.send_user); webSocketMessage.setAccept("yyx"); webSocketMessage.setContent("Hello I'm YeYunXuan"); String string = JSONObject.toJSONString(webSocketMessage); WebSocketUsers.sendMessageToUser("client1-YeYunXuan", string); } } /** * 获取登陆用户 * * @return 用户名 */ private String getUserNameInPath() { String path = webSocketClientHandshaker.uri().getPath(); int i = path.lastIndexOf("/"); return path.substring(i + 1, path.length()); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketConstant.java ================================================ package org.yyx.netty.study.websocket; /** * WebSocket常量 *

* create by 叶云轩 at 2018/5/18-下午1:29 * contact by tdg_yyx@foxmail.com */ public interface WebSocketConstant { /** * 点对点 */ String SEND_TO_USER = "send_to_user"; /** * 群发 广播 */ String SEND_TO_USERS = "send_to_users"; /** * 请求成功 */ String REQUEST_SUCCESS = "request_success"; } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketHandlerClient.java ================================================ package org.yyx.netty.study.websocket; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.stream.ChunkedWriteHandler; import org.yyx.netty.study.codec.msgpack.MsgPackDecoder; import org.yyx.netty.study.codec.msgpack.MsgPackEncoder; /** * webSocketChildHandler *

* create by 叶云轩 at 2018/5/15-下午4:54 * contact by tdg_yyx@foxmail.com */ public class WebSocketHandlerClient extends ChannelInitializer { private WebSocketClientHandler webSocketClientHandler; public WebSocketHandlerClient(WebSocketClientHandler webSocketClientHandler) { this.webSocketClientHandler = webSocketClientHandler; } /** * 初始化Channel * * @param socketChannel socketChannel */ @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); // 将请求与应答消息编码或者解码为HTTP消息 pipeline.addLast(new HttpClientCodec()); // 将http消息的多个部分组合成一条完整的HTTP消息 pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // 客户端Handler pipeline.addLast("handler", webSocketClientHandler); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketMessage.java ================================================ package org.yyx.netty.study.websocket; import lombok.Data; import java.io.Serializable; /** * WebSocketMessage *

* create by 叶云轩 at 2018/5/18-下午3:02 * contact by tdg_yyx@foxmail.com */ @Data public class WebSocketMessage implements Serializable { private static final long serialVersionUID = -4666429837358506065L; private String accept; private String content; private Type header; enum Type { send_user, send_users, request_success; } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/java/org/yyx/netty/study/websocket/WebSocketUsers.java ================================================ package org.yyx.netty.study.websocket; import io.netty.channel.Channel; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.util.internal.PlatformDependent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; /** * WebSocket用户集 */ public class WebSocketUsers { /** * 用户集 */ private static final ConcurrentMap USERS = PlatformDependent.newConcurrentHashMap(); /** * WebSocketUsers 日志控制器 * Create by 叶云轩 at 2018/5/15 下午5:41 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class); private static WebSocketUsers ourInstance = new WebSocketUsers(); private WebSocketUsers() { } public static WebSocketUsers getInstance() { return ourInstance; } /** * 存储通道 * * @param key 唯一键 * @param channel 通道 */ public static void put(String key, Channel channel) { USERS.put(key, channel); } /** * 移除通道 * * @param channel 通道 * * @return 移除结果 */ public static boolean remove(Channel channel) { String key = null; boolean b = USERS.containsValue(channel); if (b) { Set> entries = USERS.entrySet(); for (Map.Entry entry : entries) { Channel value = entry.getValue(); if (value.equals(channel)) { key = entry.getKey(); break; } } } else { return true; } return remove(key); } /** * 移出通道 * * @param key 键 */ public static boolean remove(String key) { Channel remove = USERS.remove(key); boolean containsValue = USERS.containsValue(remove); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [移出结果]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", containsValue ? "失败" : "成功"); return containsValue; } /** * 获取在线用户列表 * * @return 返回用户集合 */ public static ConcurrentMap getUSERS() { return USERS; } /** * 群发消息 * * @param message 消息内容 */ public static void sendMessageToUsers(String message) { Collection values = USERS.values(); for (Channel value : values) { value.write(new TextWebSocketFrame(message)); value.flush(); } } /** * 给某个人发送消息 * * @param userName key * @param message 消息 */ public static void sendMessageToUser(String userName, String message) { Channel channel = USERS.get(userName); channel.write(new TextWebSocketFrame(message)); channel.flush(); } } ================================================ FILE: spring-boot-netty/study-netty/study-client/src/main/resources/application.properties ================================================ ================================================ FILE: spring-boot-netty/study-netty/study-client/src/test/java/org/yyx/netty/study/StudyClientApplicationTests.java ================================================ package org.yyx.netty.study; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.yyx.netty.study.echo.megpack.MessagePackClient; import org.yyx.netty.study.websocket.WebSocketClient; import org.yyx.netty.study.websocket.WebSocketUsers; @RunWith(SpringRunner.class) @SpringBootTest public class StudyClientApplicationTests { /** * 测试端口 */ private final int port = 8080; /** * 测试IP */ private final String host = "127.0.0.1"; // region 启动未解决粘包/拆包问题的netty客户端 @Test public void startNettyClient1() throws Exception { new org.yyx.netty.study.time.demo1.TimeClient().connect(port, host); } // endregion // region 启动模拟粘包/拆包问题的netty客户端 @Test public void startNettyClient2() throws Exception { new org.yyx.netty.study.time.demo2.TimeClient().connect(port, host); } // endregion // region 启动已解决粘包/拆包问题的netty客户端 - LineBasedFrameDecoder 实现 @Test public void startNettyClient3() throws Exception { new org.yyx.netty.study.time.demo3.TimeClient().connect(port, host); } // endregion // region 启动已解决粘包/拆包问题的netty客户端 - DelimiterBasedFrameDecoder 实现 @Test public void startNettyClient4() throws Exception { new org.yyx.netty.study.echo.delimiter.EchoClient().connect(port, host); } // endregion // region 启动已解决粘包/拆包问题的netty服务 - FixedLengthFrameDecoder 实现 @Test public void startNettyServer5() throws Exception { new org.yyx.netty.study.echo.fixlength.EchoClient().connect(port, host); } // endregion // region 启动不考虑粘包/拆包问题 基于MessagePack编解码的Netty客户端 @Test public void testMessagePackEchoClient() throws Exception { new MessagePackClient().connect(port, host, 20); } // endregion // region 启动WebSocketClient @Test public void startWebSocketClient() throws Exception { new WebSocketClient().connect(9999, host, "yyx"); } @Test public void startWebSocketClient2() throws Exception { new WebSocketClient().connect(9999, host, "YeYunXuan"); } // endregion } ================================================ FILE: spring-boot-netty/study-netty/study-server/pom.xml ================================================ 4.0.0 study-server 0.0.1-SNAPSHOT jar study-server Demo project for Spring Boot org.yyx.netty study-netty 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/StudyServerApplication.java ================================================ package org.yyx.netty.study; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StudyServerApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(StudyServerApplication.class, args); } @Override public void run(String... args) throws Exception { } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackDecoder.java ================================================ package org.yyx.netty.study.codec.msgpack; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; /** * MsgPack解码器 *

* create by 叶云轩 at 2018/4/12-下午7:31 * contact by tdg_yyx@foxmail.com */ public class MsgPackDecoder extends MessageToMessageDecoder { /** * MsgPackDecoder 日志控制器 * Create by 叶云轩 at 2018/5/3 下午3:15 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackDecoder.class); /** * 解码 byte[] -> Object */ @Override protected void decode(ChannelHandlerContext channelHandlerContext , ByteBuf msg, List out) throws Exception { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [解码]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", msg); final int length = msg.readableBytes(); final byte[] array = new byte[length]; msg.getBytes(msg.readerIndex(), array, 0, length); MessagePack messagePack = new MessagePack(); out.add(messagePack.read(array)); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [out]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", out); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/codec/msgpack/MsgPackEncoder.java ================================================ package org.yyx.netty.study.codec.msgpack; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * MsgPack编码器 *

* create by 叶云轩 at 2018/4/12-下午7:29 * contact by tdg_yyx@foxmail.com */ public class MsgPackEncoder extends MessageToByteEncoder { /** * MsgPackEncoder 日志控制器 * Create by 叶云轩 at 2018/5/3 下午3:15 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MsgPackEncoder.class); /** * 编码 Object -> byte[] */ @Override protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf out) throws Exception { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [编码]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", msg); MessagePack messagePack = new MessagePack(); // 序列化 byte[] write = messagePack.write(msg); out.writeBytes(write); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/delimiter/EchoServer.java ================================================ package org.yyx.netty.study.echo.delimiter; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * EchoServer服务端 *

* create by 叶云轩 at 2018/4/12-下午4:07 * contact by tdg_yyx@foxmail.com */ public class EchoServer { /** * MessagePackServer 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:09 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoServer.class); public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { // 设置分隔符为 $_$ ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes()); // 单条消息最大长度不能超过1024 ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new EchoServerHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/delimiter/EchoServerHandler.java ================================================ package org.yyx.netty.study.echo.delimiter; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-下午4:12 * contact by tdg_yyx@foxmail.com */ public class EchoServerHandler extends ChannelHandlerAdapter { /** * MessagePackServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:20 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoServerHandler.class); /** * 计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.info("--- [发生异常] 释放资源"); ctx.close(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg; LOGGER.info("--- [第{}次接收客户端消息] {}", ++counter, body); body += "$_$"; ByteBuf echo = Unpooled.copiedBuffer(body.getBytes()); ctx.writeAndFlush(echo); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/fixlength/EchoServer.java ================================================ package org.yyx.netty.study.echo.fixlength; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * EchoServer服务端 *

* create by 叶云轩 at 2018/4/12-下午4:07 * contact by tdg_yyx@foxmail.com */ public class EchoServer { /** * MessagePackServer 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:09 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoServer.class); public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FixedLengthFrameDecoder(20)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new EchoServerHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/fixlength/EchoServerHandler.java ================================================ package org.yyx.netty.study.echo.fixlength; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/12-下午4:12 * contact by tdg_yyx@foxmail.com */ public class EchoServerHandler extends ChannelHandlerAdapter { /** * MessagePackServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:20 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(EchoServerHandler.class); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.info("--- [发生异常] 释放资源"); ctx.close(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { LOGGER.info("--- [接收到客户端的数据] {}", msg); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackServer.java ================================================ package org.yyx.netty.study.echo.megpack; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.study.codec.msgpack.MsgPackDecoder; import org.yyx.netty.study.codec.msgpack.MsgPackEncoder; /** * EchoServer服务端 *

* create by 叶云轩 at 2018/4/12-下午4:07 * contact by tdg_yyx@foxmail.com */ public class MessagePackServer { /** * MessagePackServer 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:09 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackServer.class); public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); // ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)); ch.pipeline().addLast(new MsgPackDecoder()); // ch.pipeline().addLast(new LengthFieldPrepender(2)); ch.pipeline().addLast(new MsgPackEncoder()); ch.pipeline().addLast(new MessagePackServerHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/echo/megpack/MessagePackServerHandler.java ================================================ package org.yyx.netty.study.echo.megpack; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.entity.User; import java.util.List; /** *

* create by 叶云轩 at 2018/4/12-下午4:12 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:32 */ public class MessagePackServerHandler extends ChannelHandlerAdapter { /** * MessagePackServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 下午4:20 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(MessagePackServerHandler.class); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.info("--- [发生异常] 释放资源: {}", cause.getMessage()); // todo ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush("Server connect success"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { List userInfo = (List) msg; LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [接收 ]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", userInfo); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/ChildChannelHandler.java ================================================ package org.yyx.netty.study.time.demo1; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * I/O事件处理类 *

* create by 叶云轩 at 2018/4/11-下午6:34 * contact by tdg_yyx@foxmail.com */ public class ChildChannelHandler extends ChannelInitializer { /** * ChildChannelHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:29 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class); /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * * @param ch * * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { LOGGER.info("--- [通道初始化]"); ch.pipeline().addLast(new TimeServerHandler()); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/TimeServer.java ================================================ package org.yyx.netty.study.time.demo1; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/11-下午6:33 * contact by tdg_yyx@foxmail.com */ public class TimeServer { /* NioEventLoopGroup本质是一个线程组,包含一组NIO线程专门用于网络事件的处理 一个用于服务端接受客户端的连接 一个用于进行SocketChannel的网络读写 */ /** * TimeServer 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:26 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class); /** * 绑定端口 * * @param port 端口号 * @throws Exception 异常 */ public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类 .channel(NioServerSocketChannel.class) // 配置NioServerSocketChannel的TCP参数 backlog设置为1024 .option(ChannelOption.SO_BACKLOG, 1024) // 绑定I/O事件处理类 .childHandler(new ChildChannelHandler()); // 绑定端口,同步等待成功 // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调 ChannelFuture channelFuture = bootstrap.bind(port).sync(); // 等待服务端监听端口关闭 channelFuture.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo1/TimeServerHandler.java ================================================ package org.yyx.netty.study.time.demo1; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * 针对网络事件进行读写操作的类 *

* create by 叶云轩 at 2018/4/11-下午6:35 * contact by tdg_yyx@foxmail.com */ public class TimeServerHandler extends ChannelHandlerAdapter { /** * TimeServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:30 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class); /** * 模拟粘包/拆包问题计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } /** * 读事件 * * @param ctx ChannelHandlerContext * @param msg 消息 * * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 将消息转换成ByteBuf ByteBuf buf = (ByteBuf) msg; // 获取缓冲区中的字节数 byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "utf-8"); LOGGER.info("--- [接收到的数据] {}", body); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER"; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); // region 模拟粘包/拆包问题相关代码 // String body = new String(req, "utf-8").substring(0, req.length - System.getProperty("line.separator").length()); // LOGGER.info("--- [接收到的数据] {} | [counter] {}", body, ++counter); // String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER"; // currentTime = currentTime + System.getProperty("line.separator"); // ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); // endregion // 异步发送应答消息给Client ctx.write(resp); // --> 将消息放到发送缓冲数组中 } /** * 读完之后 * * @param ctx * * @throws Exception */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { LOGGER.info("--- [服务器写消息] "); // 将消息发送队列中的消息写到SocketChannel中 ctx.flush(); // --> 将消息写到 SocketChannel 中 } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/ChildChannelHandler.java ================================================ package org.yyx.netty.study.time.demo2; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * I/O事件处理类 *

* create by 叶云轩 at 2018/4/11-下午6:34 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:38 */ public class ChildChannelHandler extends ChannelInitializer { /** * ChildChannelHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:29 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class); /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * * @param ch * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { LOGGER.info("--- [通道初始化]"); ch.pipeline().addLast(new TimeServerHandler()); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/TimeServer.java ================================================ package org.yyx.netty.study.time.demo2; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/11-下午6:33 * contact by tdg_yyx@foxmail.com */ public class TimeServer { /* NioEventLoopGroup本质是一个线程组,包含一组NIO线程专门用于网络事件的处理 一个用于服务端接受客户端的连接 一个用于进行SocketChannel的网络读写 */ /** * TimeServer 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:26 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class); /** * 绑定端口 * * @param port 端口号 * @throws Exception 异常 */ public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类 .channel(NioServerSocketChannel.class) // 配置NioServerSocketChannel的TCP参数 backlog设置为1024 .option(ChannelOption.SO_BACKLOG, 1024) // 绑定I/O事件处理类 .childHandler(new ChildChannelHandler()); // 绑定端口,同步等待成功 // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调 ChannelFuture channelFuture = bootstrap.bind(port).sync(); // 等待服务端监听端口关闭 channelFuture.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo2/TimeServerHandler.java ================================================ package org.yyx.netty.study.time.demo2; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * 针对网络事件进行读写操作的类 *

* create by 叶云轩 at 2018/4/11-下午6:35 * contact by tdg_yyx@foxmail.com */ public class TimeServerHandler extends ChannelHandlerAdapter { /** * TimeServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:30 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class); /** * 模拟粘包/拆包问题计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } /** * 读事件 * * @param ctx ChannelHandlerContext * @param msg 消息 * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 将消息转换成ByteBuf ByteBuf buf = (ByteBuf) msg; // 获取缓冲区中的字节数 byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); // region 模拟粘包/拆包问题相关代码 String body = new String(req, "utf-8").substring(0, req.length - System.getProperty("line.separator").length()); LOGGER.info("--- [接收到的数据] {} | [counter] {}", body, ++counter); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER"; currentTime = currentTime + System.getProperty("line.separator"); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); // endregion // 异步发送应答消息给Client ctx.writeAndFlush(resp); // --> 将消息放到发送缓冲数组中 } /** * 读完之后 * * @param ctx * @throws Exception */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { LOGGER.info("--- [服务器写消息] "); // 将消息发送队列中的消息写到SocketChannel中 ctx.flush(); // --> 将消息写到 SocketChannel 中 } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/ChildChannelHandler.java ================================================ package org.yyx.netty.study.time.demo3; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * I/O事件处理类 *

* create by 叶云轩 at 2018/4/11-下午6:34 * contact by tdg_yyx@foxmail.com */ public class ChildChannelHandler extends ChannelInitializer { /** * ChildChannelHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:29 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(ChildChannelHandler.class); /** * LineBasedFrameDecoder: 以换行符为结束标志的解码器 * 依次遍历ByteBuf中的可读字节, * 如果有"\n" 或者 "\r\n" -> 就以此位置为结束位置 之后将可读索引到结束位置区间的字节组成一行 */ /** * 创建NioSocketChannel成功之后,进行初始化时, * 将ChannelHandler设置到ChannelPipeline中, * 同样,用于处理网络I/O事件 * * @param ch * @throws Exception */ @Override protected void initChannel(SocketChannel ch) throws Exception { LOGGER.info("--- [通道初始化]"); // region 解决粘包/拆包问题相关代码 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); // 将接收到的对象转成字符串 ch.pipeline().addLast(new StringDecoder()); // endregion ch.pipeline().addLast(new TimeServerHandler()); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/TimeServer.java ================================================ package org.yyx.netty.study.time.demo3; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

* create by 叶云轩 at 2018/4/11-下午6:33 * contact by tdg_yyx@foxmail.com */ public class TimeServer { /* NioEventLoopGroup本质是一个线程组,包含一组NIO线程专门用于网络事件的处理 一个用于服务端接受客户端的连接 一个用于进行SocketChannel的网络读写 */ /** * TimeServer 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:26 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServer.class); /** * 绑定端口 * * @param port 端口号 * * @throws Exception 异常 */ public void bind(int port) throws Exception { LOGGER.info("--- [绑定端口] {}", port); // 声明Boss线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 声明Worker线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { LOGGER.info("--- [启动NIO] "); // Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度 ServerBootstrap bootstrap = new ServerBootstrap(); // 将两个NIO线程组传递到ServerBootStrap中 bootstrap.group(bossGroup, workerGroup) // NioServerSocketChannel 相当于NIO中的ServerSocketChannel类 .channel(NioServerSocketChannel.class) // 配置NioServerSocketChannel的TCP参数 backlog设置为1024 .option(ChannelOption.SO_BACKLOG, 1024) // 绑定I/O事件处理类 .childHandler(new ChildChannelHandler()); // 绑定端口,同步等待成功 // channelFuture 相当于JDK的java.util.concurrent.Future用于异步操作通知回调 ChannelFuture channelFuture = bootstrap.bind(port).sync(); // 等待服务端监听端口关闭 channelFuture.channel().closeFuture().sync(); } finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/time/demo3/TimeServerHandler.java ================================================ package org.yyx.netty.study.time.demo3; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * 针对网络事件进行读写操作的类 *

* create by 叶云轩 at 2018/4/11-下午6:35 * contact by tdg_yyx@foxmail.com */ public class TimeServerHandler extends ChannelHandlerAdapter { /** * TimeServerHandler 日志控制器 * Create by 叶云轩 at 2018/4/12 上午11:30 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(TimeServerHandler.class); /** * 模拟粘包/拆包问题计数器 */ private int counter; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } /** * 读事件 * * @param ctx ChannelHandlerContext * @param msg 消息 * * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // region 解决模拟粘包/拆包问题相关代码 String body = (String) msg; LOGGER.info("--- [接收到的数据] {} | [counter] {}", body, ++counter); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER"; currentTime = currentTime + System.getProperty("line.separator"); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); // endregion // 异步发送应答消息给Client ctx.writeAndFlush(resp); // --> 将消息放到发送缓冲数组中 } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketChildHandler.java ================================================ package org.yyx.netty.study.websocket; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; /** * webSocketChildHandler *

* create by 叶云轩 at 2018/5/15-下午4:54 * contact by tdg_yyx@foxmail.com */ public class WebSocketChildHandler extends ChannelInitializer { /** * 初始化Channel * * @param socketChannel socketChannel */ @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); // 将请求与应答消息编码或者解码为HTTP消息 pipeline.addLast("http-codec", new HttpServerCodec()); // 将http消息的多个部分组合成一条完整的HTTP消息 pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // 向客户端发送HTML5文件。主要用于支持浏览器和服务端进行WebSocket通信 pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // 服务端Handler pipeline.addLast("handler", new WebSocketServerHandler()); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketConstant.java ================================================ package org.yyx.netty.study.websocket; /** * WebSocket常量 *

* create by 叶云轩 at 2018/5/18-下午1:29 * contact by tdg_yyx@foxmail.com */ public interface WebSocketConstant { /** * 点对点 */ String SEND_TO_USER = "send_to_user"; /** * 群发 广播 */ String SEND_TO_USERS = "send_to_users"; /** * 请求成功 */ String REQUEST_SUCCESS = "request_success"; } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketMessage.java ================================================ package org.yyx.netty.study.websocket; import lombok.Data; import java.io.Serializable; /** * WebSocketMessage *

* create by 叶云轩 at 2018/5/18-下午3:02 * contact by tdg_yyx@foxmail.com */ @Data public class WebSocketMessage implements Serializable { private static final long serialVersionUID = -4666429837358506065L; private String accept; private String content; private Type header; enum Type { send_user, send_users, request_success; } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketServer.java ================================================ package org.yyx.netty.study.websocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * webSocket服务器 *

* create by 叶云轩 at 2018/5/11-上午11:42 * contact by tdg_yyx@foxmail.com */ public class WebSocketServer { /** * WebSocketServer 日志控制器 * Create by 叶云轩 at 2018/5/11 上午11:48 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); public void run(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new WebSocketChildHandler()); Channel ch = bootstrap.bind(port).sync().channel(); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [服务器启动]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", port); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketServerHandler.java ================================================ package org.yyx.netty.study.websocket; import com.alibaba.fastjson.JSONObject; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentMap; /** *

* create by 叶云轩 at 2018/5/11-上午11:49 * contact by tdg_yyx@foxmail.com */ public class WebSocketServerHandler extends SimpleChannelInboundHandler { /** * WebSocketServerHandler 日志控制器 * Create by 叶云轩 at 2018/5/11 上午11:50 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class); private WebSocketServerHandshaker socketServerHandShaker; /** * 异常 * * @param channelHandlerContext channelHandlerContext * @param cause 异常 */ @Override public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception { channelHandlerContext.close(); } /** * 当客户端主动链接服务端的链接后,调用此方法 * * @param channelHandlerContext ChannelHandlerContext */ @Override public void channelActive(ChannelHandlerContext channelHandlerContext) { // 使用一个结构存储通道 LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [建立连接]: client [{}]\n" + "\t├ [当前在线人数]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", channelHandlerContext.channel().remoteAddress() , WebSocketUsers.getUSERS().size() + 1); } /** * 与客户端断开连接时 * * @param channelHandlerContext channelHandlerContext */ @Override public void channelInactive(ChannelHandlerContext channelHandlerContext) { Channel channel = channelHandlerContext.channel(); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [断开连接]:client [{}]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", channel.remoteAddress()); WebSocketUsers.remove(channel); ConcurrentMap users = WebSocketUsers.getUSERS(); LOGGER.info("\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); for (String s : users.keySet()) { LOGGER.info( "\t├ [当前在线]: {}", s); } LOGGER.info("\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); } /** * 读完之后调用的方法 * * @param channelHandlerContext ChannelHandlerContext */ @Override public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception { channelHandlerContext.flush(); } /** * 接收客户端发送的消息 * * @param ctx ChannelHandlerContext * @param msg 消息 */ @Override protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [收到客户端消息类型]: {} - {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", msg.getClass(), msg.toString()); // 传统http接入 第一次需要使用http建立握手 if (msg instanceof FullHttpRequest) { handlerHttpRequest(ctx, (FullHttpRequest) msg); ctx.channel().write(new TextWebSocketFrame("连接成功")); } // WebSocket接入 else if (msg instanceof WebSocketFrame) { handlerWebSocketFrame(ctx, (WebSocketFrame) msg); } } /** * 第一次握手 * * @param channelHandlerContext channelHandlerContext * @param req 请求 */ private void handlerHttpRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) { // 构造握手响应返回,本机测试 WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:9999/websocket/{uri}", null, false); String uri = req.uri(); String[] split = uri.split("/"); String userName = split[2]; // 加入在线用户 WebSocketUsers.put(userName, channelHandlerContext.channel()); socketServerHandShaker = wsFactory.newHandshaker(req); if (socketServerHandShaker == null) { WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channelHandlerContext.channel()); } else { socketServerHandShaker.handshake(channelHandlerContext.channel(), req); } } /** * webSocket处理逻辑 * * @param channelHandlerContext channelHandlerContext * @param frame webSocketFrame */ private void handlerWebSocketFrame(ChannelHandlerContext channelHandlerContext, WebSocketFrame frame) throws IOException { Channel channel = channelHandlerContext.channel(); // region 判断是否是关闭链路的指令 if (frame instanceof CloseWebSocketFrame) { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [关闭与客户端的链接]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); socketServerHandShaker.close(channel, (CloseWebSocketFrame) frame.retain()); return; } // endregion // region 判断是否是ping消息 if (frame instanceof PingWebSocketFrame) { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [Ping消息]\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓"); channel.write(new PongWebSocketFrame(frame.content().retain())); return; } // endregion if (frame instanceof TextWebSocketFrame) { String text = ((TextWebSocketFrame) frame).text(); WebSocketMessage webSocketMessage = JSONObject.parseObject(text, WebSocketMessage.class); String accept = webSocketMessage.getAccept(); WebSocketUsers.sendMessageToUser(accept, webSocketMessage.getContent()); } if (frame instanceof BinaryWebSocketFrame) { BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame; ByteBuf content = binaryWebSocketFrame.content(); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [二进制数据]:{}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", content); final int length = content.readableBytes(); final byte[] array = new byte[length]; content.getBytes(content.readerIndex(), array, 0, length); MessagePack messagePack = new MessagePack(); List list = new ArrayList<>(); list.add(messagePack.read(array)); for (Object o : list) { LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [解码数据]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", o); } } // 非文本消息处理方式 if (!(frame instanceof TextWebSocketFrame)) { throw new UnsupportedOperationException(String.format( "%s 暂不支持非文本消息", frame.getClass().getName() )); } } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/java/org/yyx/netty/study/websocket/WebSocketUsers.java ================================================ package org.yyx.netty.study.websocket; import io.netty.channel.Channel; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.util.internal.PlatformDependent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; /** * WebSocket用户集 */ public class WebSocketUsers { /** * 用户集 */ private static final ConcurrentMap USERS = PlatformDependent.newConcurrentHashMap(); /** * WebSocketUsers 日志控制器 * Create by 叶云轩 at 2018/5/15 下午5:41 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class); private static WebSocketUsers ourInstance = new WebSocketUsers(); private WebSocketUsers() { } public static WebSocketUsers getInstance() { return ourInstance; } /** * 存储通道 * * @param key 唯一键 * @param channel 通道 */ public static void put(String key, Channel channel) { USERS.put(key, channel); } /** * 移除通道 * * @param channel 通道 * * @return 移除结果 */ public static boolean remove(Channel channel) { String key = null; boolean b = USERS.containsValue(channel); if (b) { Set> entries = USERS.entrySet(); for (Map.Entry entry : entries) { Channel value = entry.getValue(); if (value.equals(channel)) { key = entry.getKey(); break; } } } else { return true; } return remove(key); } /** * 移出通道 * * @param key 键 */ public static boolean remove(String key) { Channel remove = USERS.remove(key); boolean containsValue = USERS.containsValue(remove); LOGGER.info("\n\t⌜⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓\n" + "\t├ [移出结果]: {}\n" + "\t⌞⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓⎓", containsValue ? "失败" : "成功"); return containsValue; } /** * 获取在线用户列表 * * @return 返回用户集合 */ public static ConcurrentMap getUSERS() { return USERS; } /** * 群发消息 * * @param message 消息内容 */ public static void sendMessageToUsers(String message) { Collection values = USERS.values(); for (Channel value : values) { value.write(new TextWebSocketFrame(message)); value.flush(); } } /** * 给某个人发送消息 * * @param userName key * @param message 消息 */ public static void sendMessageToUser(String userName, String message) { Channel channel = USERS.get(userName); channel.write(new TextWebSocketFrame(message)); channel.flush(); } } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/main/resources/application.properties ================================================ ================================================ FILE: spring-boot-netty/study-netty/study-server/src/test/java/org/yyx/netty/study/CodeCTest.java ================================================ package org.yyx.netty.study; import org.junit.Test; import org.msgpack.MessagePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yyx.netty.entity.User; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.nio.ByteBuffer; /** *

* create by 叶云轩 at 2018/4/12-下午6:50 * contact by tdg_yyx@foxmail.com * * @author 叶云轩 contact by tdg_yyx@foxmail.com * @date 2018/8/15 - 12:34 */ public class CodeCTest { /** * CodeCTest 日志控制器 * Create by 叶云轩 at 2018/4/12 下午6:53 * Concat at tdg_yyx@foxmail.com */ private static final Logger LOGGER = LoggerFactory.getLogger(CodeCTest.class); // region jdk序列化与基于ByteBuffer的二进制序列化字节数对比 @Test public void testCodeC() throws IOException { User user = new User(); user.setName("yyx"); user.setId(1); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(user); objectOutputStream.flush(); objectOutputStream.close(); byte[] bytes = byteArrayOutputStream.toByteArray(); LOGGER.info("--- [JDK] {}", bytes.length); byteArrayOutputStream.close(); LOGGER.info("--- [byte] {}", user.codeC().length); MessagePack messagePack = new MessagePack(); // 序列化 byte[] write = messagePack.write(user); LOGGER.info("--- [MessagePack] {}", write.length); } // endregion // region jdk序列化效率与基于ByteBuffer的二进制序列化效率对比 @Test public void testCodec() throws Exception { User user = new User(); user.setName("yyx"); user.setId(1); int loop = 1000000; ByteArrayOutputStream bos; ObjectOutputStream os; long startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { bos = new ByteArrayOutputStream(); os = new ObjectOutputStream(bos); os.writeObject(user); os.flush(); os.close(); byte[] bytes = bos.toByteArray(); bos.close(); } long endTime = System.currentTimeMillis(); LOGGER.info("--- [jdk耗时] {}", endTime - startTime + "ms"); ByteBuffer buffer = ByteBuffer.allocate(1024); startTime = System.currentTimeMillis(); for (int i = 0; i < loop; i++) { byte[] bytes = user.codeC(buffer); } endTime = System.currentTimeMillis(); LOGGER.info("--- [byte耗时] {}", endTime - startTime + "ms"); startTime = System.currentTimeMillis(); MessagePack messagePack = new MessagePack(); for (int i = 0; i < loop; i++) { // 序列化 byte[] write = messagePack.write(user); } endTime = System.currentTimeMillis(); LOGGER.info("--- [MessagePack 耗时] {}", endTime - startTime + "ms"); } // endregion } ================================================ FILE: spring-boot-netty/study-netty/study-server/src/test/java/org/yyx/netty/study/StudyServerApplicationTests.java ================================================ package org.yyx.netty.study; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.yyx.netty.study.echo.megpack.MessagePackServer; import org.yyx.netty.study.websocket.WebSocketServer; @RunWith(SpringRunner.class) @SpringBootTest public class StudyServerApplicationTests { private final int port = 8080; // region 启动不考虑粘包/拆包问题的netty服务 @Test public void startNettyServer1() throws Exception { new org.yyx.netty.study.time.demo1.TimeServer().bind(port); } // endregion // region 启动模拟粘包/拆包问题的netty服务 @Test public void startNettyServer2() throws Exception { new org.yyx.netty.study.time.demo2.TimeServer().bind(port); } // endregion // region 启动已解决粘包/拆包问题的netty服务 - LineBasedFrameDecoder 实现 @Test public void startNettyServer3() throws Exception { new org.yyx.netty.study.time.demo3.TimeServer().bind(port); } // endregion // region 启动已解决粘包/拆包问题的netty服务 - DelimiterBasedFrameDecoder 实现 @Test public void startNettyServer4() throws Exception { new org.yyx.netty.study.echo.delimiter.EchoServer().bind(port); } // endregion // region 启动已解决粘包/拆包问题的netty服务 - FixedLengthFrameDecoder 实现 @Test public void startNettyServer5() throws Exception { new org.yyx.netty.study.echo.fixlength.EchoServer().bind(port); } // endregion // region 启动不考虑粘包/拆包问题 基于MessagePack编解码的Netty服务 @Test public void testMessagePackEchoServer() throws Exception { new MessagePackServer().bind(port); } // endregion // region 启动WebSocket服务器 @Test public void startWebSocketServer() throws Exception { new WebSocketServer().run(9999); } // endregion }