工作场景:使用Netty长连接实时获取第三方接口的车辆定位数据
开发环境:JDK8
Netty是由JBOSS提供的一个Java开源框架,现为Github上的独立项目。它是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络IO程序。Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下的大量数据持续传输的应用。
Netty提供了一套完整的API,用于处理网络IO操作,如TCP和UDP套接字。它封装了底层的网络编程细节,使得开发者可以更加专注于业务逻辑的实现。Netty使用了一种高效的线程模型,可以处理大量的并发连接,并且具有很好的伸缩性。
Netty在多个领域都有广泛的应用,如RPC框架、游戏行业、大数据领域等。它支持多种传输类型和协议,如阻塞和非阻塞、基于BIO和NIO的UDP传输、本地传输(in-VM传输)、HTTP通道等。同时,Netty还提供了丰富的编解码器,用于处理各种协议的编解码操作。
Netty的整体结构包括核心层和协议支持层。核心层提供了底层网络通信的通用抽象和实现,包括可扩展的事件模型、通用的通信API、支持零拷贝的ByteBuf等。协议支持层则覆盖了主流协议的编解码实现,如HTTP、SSL、Protobuf等。
总的来说,Netty是一个功能强大、易于使用的网络应用框架,它可以帮助开发者快速构建高性能、高可靠性的网络应用程序。
Netty的核心组件主要包括以下几个部分:
以上这些组件共同构成了Netty的核心框架,使得开发者可以更加专注于业务逻辑的实现,而无需过多关心底层的网络通信细节。
<dependencies>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
@Component
public class NettyServer {
//负责处理接受进来的链接
private EventLoopGroup bossGroup;
//负责处理已经被接收的连接上的I/O操作
private EventLoopGroup workerGroup;
//在这个场景中,它表示服务器的绑定操作的结果
private ChannelFuture future;
@PostConstruct
public void startServer() throws Exception {
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
try {
//创建ServerBootstrap,这个类封装了服务器端的网络配置,使得我们可以轻松地设置服务器参数
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new NettyServerInitializer());
// 绑定端口并开始接受进来的连接
future = bootstrap.bind(7000).sync();
// 等待服务器套接字关闭
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
@PreDestroy
public void stopServer() {
if (future != null && !future.isDone()) {
future.cancel(true);
}
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
@Component
: 这是Spring框架的注解,表示这个类是一个组件,Spring会扫描到这个类并将其作为Bean注册到Spring容器中。因此,这个类可以被其他Spring管理的Bean自动装配(如果需要的话)。bossGroup
和 workerGroup
: 这两个是EventLoopGroup
的实例,用于处理网络事件。bossGroup
主要负责接收进来的连接,而 workerGroup
负责处理已经被接收的连接上的I/O操作。future
: 这是一个ChannelFuture
的实例,代表了一个异步的I/O操作的结果。在这个场景中,它表示服务器的绑定操作的结果。startServer
方法@PostConstruct
注解,这意味着当Spring容器实例化这个Bean并完成依赖注入后,会自动调用这个方法。NioEventLoopGroup
实例,一个用于boss,一个用于worker。ServerBootstrap
类来配置和启动服务器。这个类封装了服务器端的网络配置,使得我们可以轻松地设置服务器参数。group
方法设置boss和worker的EventLoopGroup
。channel
方法指定使用NioServerSocketChannel
作为服务器的通道实现。childHandler
方法设置一个新的连接被接受后如何处理。这里使用了NettyServerInitializer
(这个类没有在提供的代码段中定义,但我们可以假设它是一个ChannelInitializer
的实现,用于配置新的Channel
)。bind
方法绑定服务器到指定的端口(这里是7000),并使用sync
方法阻塞直到绑定完成。closeFuture().sync()
方法阻塞当前线程,直到服务器套接字关闭。finally
块中,无论是否发生异常,都会优雅地关闭EventLoopGroup
。stopServer
方法@PreDestroy
注解,意味着当Spring容器销毁这个Bean之前,会自动调用这个方法。future
是否已经完成(即服务器是否已经关闭)。如果没有,就调用cancel(true)
方法来尝试取消这个操作。但是,需要注意的是,这里的cancel
可能并不总是能立即停止服务器,它更多的是尝试停止服务器,而不是强制停止。public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch){
ChannelPipeline pipeline = ch.pipeline();
// 添加一个字符串解码器,用于将接收到的ByteBuf转换成字符串
// 这里假设使用的是UTF-8字符集
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
// 添加一个字符串编码器,用于将发送的字符串转换成ByteBuf
// 这样服务器发送字符串时,客户端可以直接接收到字符串
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
// 添加自定义的ChannelInboundHandlerAdapter来处理业务逻辑
pipeline.addLast("handler", new MyChannelHandler());
}
}
NettyServerInitializer
类,它继承自ChannelInitializer<SocketChannel>
,并覆盖了initChannel
方法。在Netty中,ChannelInitializer
是一个特殊的处理器,它的主要目的是帮助用户配置一个新的Channel
的ChannelPipeline
。当一个新的连接被接受时,Netty会自动调用ChannelInitializer
的initChannel
方法来设置这个新连接的ChannelPipeline
。具体来说,initChannel
方法会在以下情况下被调用:
ServerBootstrap
的bind
方法被调用并成功绑定到某个端口后,开始监听传入的连接。ServerBootstrap
会接受这个连接,并创建一个新的SocketChannel
来表示这个连接。SocketChannel
,Netty会调用之前设置的ChannelInitializer
(在这个例子中是NettyServerInitializer
)的initChannel
方法。initChannel
方法内部会配置这个新SocketChannel
的ChannelPipeline
,添加解码器、编码器、业务处理器等。initChannel
方法执行完毕,这个ChannelInitializer
的使命就完成了,并且会从ChannelPipeline
中移除自身,因为它只负责初始化工作,不参与后续的数据处理。所以,总结来说,NettyServerInitializer
的initChannel
方法会在一个新的客户端连接被服务器接受时运行,用于初始化这个新连接的ChannelPipeline
。
public class MyChannelHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 在这里处理接收到的数据
System.out.println("msg = " + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 在这里处理异常
}
}
1.字符解析器定义之后,接收到的消息仍然乱码
2.项目启动后,可以访问netty的端口号,但是访问不了项目的端口号(已解决)
// future.channel().closeFuture().sync();把这段代码屏蔽就可以