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
}