敞开成长之旅!这是我参与「日新方案 12 月更文挑战」的第2天,点击查看活动概况
Netty是由JBOSS供给的一个java开源结构,现为 Github上的独立项目。Netty供给异步的、事情驱动的网络运用程序结构和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个根据NIO的客户、服务器端的编程结构,运用Netty 能够确保你快速和简略的开宣布一个网络运用,例如完成了某种协议的客户、服务端运用。Netty相当于简化和流线化了网络运用的编程开发过程。
Springboot整合Netty
新建springboot项目,并在项目以来中导入netty包,用fastjson包处理jsonStr。
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<!-- Json处理 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.16</version>
</dependency>
创立netty相关装备信息文件
- yml装备文件——application.yml
# netty 装备
netty:
# boss线程数量
boss: 4
# worker线程数量
worker: 2
# 衔接超时时刻
timeout: 6000
# 服务器主端口
port: 18023
# 服务器备用端口
portSalve: 18026
# 服务器地址
host: 127.0.0.1
- netty装备实体类——NettyProperties与yml装备文件绑定
经过
@ConfigurationProperties(prefix = "netty")
注解读取装备文件中的netty装备,经过反射注入值,需要在实体类中供给对应的setter和getter方法。
@ConfigurationProperties(prefix = "netty")
对应的实体类特点称号不要求必定相同,只需确保“set”字符串拼接装备文件的特点和setter方法名相同即可。
@Configuration
@ConfigurationProperties(prefix = "netty")
public class NettyProperties {
/**
* boss线程数量
*/
private Integer boss;
/**
* worker线程数量
*/
private Integer worker;
/**
* 衔接超时时刻
*/
private Integer timeout = 30000;
/**
* 服务器主端口
*/
private Integer port = 18023;
/**
* 服务器备用端口
*/
private Integer portSalve = 18026;
/**
* 服务器地址 默以为本地
*/
private String host = "127.0.0.1";
// setter、getter 。。。。
}
- 对netty进行装备,绑定netty相关装备设置 Netty一般由一个Bootstrap开端,首要作用是装备整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的发动引导类,ServerBootstrap是服务端发动引导类。
@Configuration
@EnableConfigurationProperties
public class NettyConfig {
final NettyProperties nettyProperties;
public NettyConfig(NettyProperties nettyProperties) {
this.nettyProperties = nettyProperties;
}
/**
* boss线程池-进行客户端衔接
*
* @return
*/
@Bean
public NioEventLoopGroup boosGroup() {
return new NioEventLoopGroup(nettyProperties.getBoss());
}
/**
* worker线程池-进行事务处理
*
* @return
*/
@Bean
public NioEventLoopGroup workerGroup() {
return new NioEventLoopGroup(nettyProperties.getWorker());
}
/**
* 服务端发动器,监听客户端衔接
*
* @return
*/
@Bean
public ServerBootstrap serverBootstrap() {
ServerBootstrap serverBootstrap = new ServerBootstrap()
// 指定运用的线程组
.group(boosGroup(), workerGroup())
// 指定运用的通道
.channel(NioServerSocketChannel.class)
// 指定衔接超时时刻
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())
// 指定worker处理器
.childHandler(new NettyServerHandler());
return serverBootstrap;
}
}
- worker处理器,初始化通道以及装备对应管道的处理器
自定义了
##@##
切割符,经过DelimiterBasedFrameDecoder
来处理拆包沾包问题; 经过MessageDecodeHandler
将接纳音讯解码处理成目标实例; 经过MessageEncodeHandler
将发送音讯添加切割符后并编码; 最后经过ServerListenerHandler
根据音讯类型对应处理不同音讯。
public class NettyServerHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 数据切割符
String delimiterStr = "##@##";
ByteBuf delimiter = Unpooled.copiedBuffer(delimiterStr.getBytes());
ChannelPipeline pipeline = socketChannel.pipeline();
// 运用自定义处理拆包/沾包,而且每次查找的最大长度为1024字节
pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
// 将上一步解码后的数据转码为Message实例
pipeline.addLast(new MessageDecodeHandler());
// 对发送客户端的数据进行编码,并添加数据分隔符
pipeline.addLast(new MessageEncodeHandler(delimiterStr));
// 对数据进行终究处理
pipeline.addLast(new ServerListenerHandler());
}
}
- 数据解码 数据解码和编码都采用UTF8格式
public class MessageDecodeHandler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
ByteBuf frame = in.retainedDuplicate();
final String content = frame.toString(CharsetUtil.UTF_8);
Message message = new Message(content);
list.add(message);
in.skipBytes(in.readableBytes());
}
}
- 数据解码转化的实例 Message类用于承载音讯、转JsonString
public class Message {
/**
* 数据长度
*/
private Integer len;
/**
* 接纳的通讯数据body
*/
private String content;
/**
* 音讯类型
*/
private Integer msgType;
public Message(Object object) {
String str = object.toString();
JSONObject jsonObject = JSONObject.parseObject(str);
msgType = Integer.valueOf(jsonObject.getString("msg_type"));
content = jsonObject.getString("body");
len = str.length();
}
public String toJsonString() {
return "{" +
"\"msg_type\": " + msgType + ",\n" +
"\"body\": " + content +
"}";
}
// setter、getter 。。。。
}
- 数据编码 netty服务端回复音讯时,对音讯转JsonString添加切割符,并进行编码。
public class MessageEncodeHandler extends MessageToByteEncoder<Message> {
// 数据切割符
String delimiter;
public MessageEncodeHandler(String delimiter) {
this.delimiter = delimiter;
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf out) throws Exception {
out.writeBytes((message.toJsonString() + delimiter).getBytes(CharsetUtil.UTF_8));
}
}
- 数据处理器,针对不同类型数据分类处理 在处理不同接纳数据时运用了枚举类型,在运用switch时能够做下处理,详细参考代码,这里只演示如何操作,并没完成数据处理事务类。
public class ServerListenerHandler extends SimpleChannelInboundHandler<Message> {
private static final Logger log = LoggerFactory.getLogger(ServerListenerHandler.class);
/**
* 设备接入衔接时处理
*
* @param ctx
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
log.info("有新的衔接:[{}]", ctx.channel().id().asLongText());
}
/**
* 数据处理
*
* @param ctx
* @param msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message msg) {
// 获取音讯实例中的音讯体
String content = msg.getContent();
// 对不同音讯类型进行处理
MessageEnum type = MessageEnum.getStructureEnum(msg);
switch (type) {
case CONNECT:
// TODO 心跳音讯处理
case STATE:
// TODO 设备状况
default:
System.out.println(type.content + "音讯内容" + content);
}
}
/**
* 设备下线处理
*
* @param ctx
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
log.info("设备下线了:{}", ctx.channel().id().asLongText());
}
/**
* 设备衔接反常处理
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 打印反常
log.info("反常:{}", cause.getMessage());
// 封闭衔接
ctx.close();
}
}
- 数据类型枚举类
public enum MessageEnum {
CONNECT(1, "心跳音讯"),
STATE(2, "设备状况");
public final Integer type;
public final String content;
MessageEnum(Integer type, String content) {
this.type = type;
this.content = content;
}
// case中判断运用
public static MessageEnum getStructureEnum(Message msg) {
Integer type = Optional.ofNullable(msg)
.map(Message::getMsgType)
.orElse(0);
if (type == 0) {
return null;
} else {
List<MessageEnum> objectEnums = Arrays.stream(MessageEnum.values())
.filter((item) -> item.getType() == type)
.distinct()
.collect(Collectors.toList());
if (objectEnums.size() > 0) {
return objectEnums.get(0);
}
return null;
}
}
// setter、getter。。。。
}
到此Netty整个装备已经完成,但假如要跟从springboot一同发动,仍需要做一些装备。
- netty发动类装备
@Component
public class NettyServerBoot {
private static final Logger log = LoggerFactory.getLogger(NettyServerBoot.class);
@Resource
NioEventLoopGroup boosGroup;
@Resource
NioEventLoopGroup workerGroup;
final ServerBootstrap serverBootstrap;
final NettyProperties nettyProperties;
public NettyServerBoot(ServerBootstrap serverBootstrap, NettyProperties nettyProperties) {
this.serverBootstrap = serverBootstrap;
this.nettyProperties = nettyProperties;
}
/**
* 发动netty
*
* @throws InterruptedException
*/
@PostConstruct
public void start() throws InterruptedException {
// 绑定端口发动
serverBootstrap.bind(nettyProperties.getPort()).sync();
// 备用端口
serverBootstrap.bind(nettyProperties.getPortSalve()).sync();
log.info("发动Netty: {},{}", nettyProperties.getPort(), nettyProperties.getPortSalve());
}
/**
* 封闭netty
*/
@PreDestroy
public void close() {
log.info("封闭Netty");
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
添加NettyServerBoot装备后,发动application时,netty服务端会跟从一同发动。 一起,在springboot封闭前,会先毁掉netty服务。
完好源码
github.com/BerBai/Java…