Netty封装了NIO的很多细节,使用更简单?简单在哪里?
Netty封装了NIO的很多细节,使得我们在使用NIO进行网络编程时更加简单和高效。但是,对于初学者而言,理解Netty的工作原理和使用方式可能比较困难,需要花费一定的学习时间和精力。相对于直接使用Java NIO来说,Netty确实会增加一些学习成本。
那么为什么还说Netty使用更简单呢?主要原因如下:
-
Netty将复杂的NIO操作封装在自己的框架中,开发者只需要关注业务逻辑的实现,而不需要关注底层的网络通信细节,这大大简化了网络编程的复杂度。
-
Netty提供了一套完善的API和组件,开发者可以快速构建高性能的网络应用程序。
-
Netty的代码结构清晰,注释详细,文档齐全,生态完善,开发者可以轻松地找到所需的资料和工具。
总之,虽然Netty的学习成本可能比较高,但它的确是一款非常优秀的网络编程框架,适用于构建高性能、可扩展的网络应用程序。
下面用一个简单的代码示例,演示如何使用Netty和Java NIO实现一个简单的网络服务器。这个服务器会监听指定的端口,一旦有客户端连接上来,就会向客户端发送一条欢迎消息,并且将客户端发送的消息原样返回。
使用Netty实现的代码如下:
public class EchoServer {
private static final Logger logger = LoggerFactory.getLogger(EchoServer.class);
private final int port;
// 构造函数,传入端口号
public EchoServer(int port) {
this.port = port;
}
// 启动服务器的方法
public void start() throws Exception {
// 创建事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建服务端启动器
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group) // 设置事件循环组
.channel(NioServerSocketChannel.class) // 设置通道类型
.localAddress(new InetSocketAddress(port)) // 设置本地地址
.childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道处理器
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler()); // 添加处理器
}
});
// 绑定端口并启动服务器
ChannelFuture future = bootstrap.bind().sync();
logger.info("EchoServer started and listening on {}", future.channel().localAddress());
future.channel().closeFuture().sync();
logger.info("server close");
} finally {
// 优雅关闭服务器
group.shutdownGracefully().sync();
}
}
// 主函数
public static void main(String[] args) throws Exception {
int port = 8080;
new EchoServer(port).start(); // 启动服务器
}
}
// 自定义处理器
class EchoServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class);
// 处理连接建立事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("Channel active: {}", ctx.channel().remoteAddress());
}
// 处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("Server received: {}", msg);
ctx.write(msg); // 将消息写回客户端
}
// 读事件读取完成后的处理
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
ctx.flush(); // 刷新缓冲区
}
// 处理异常事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("Exception caught: {}", cause.getMessage(), cause);
ctx.close(); // 关闭通道
}
}
使用Java NIO实现的代码如下:
```
public class NioServer {
public static void main(String[] args) throws IOException {
// 创建一个ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 将ServerSocketChannel设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 绑定本地地址和端口
serverSocketChannel.bind(new InetSocketAddress("localhost", 8888));
// 创建一个Selector对象
Selector selector = Selector.open();
// 将ServerSocketChannel注册到Selector上,并监听OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port 8888");
while (true) {
// 阻塞等待事件发生
selector.select();
// 获取事件集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
// 处理事件
if (key.isAcceptable()) {
// 如果是OP_ACCEPT事件,则创建一个SocketChannel,并将它注册到Selector上
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + socketChannel.getRemoteAddress());
} else if (key.isReadable()) {
// 如果是OP_READ事件,则读取客户端发送的数据
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
if (len > 0) {
buffer.flip();
String msg = new String(buffer.array(), 0, len);
System.out.println("Received message from " + socketChannel.getRemoteAddress() + ": " + msg);
} else if (len == -1) {
// 如果读取到-1,说明客户端已经断开连接
System.out.println("Client disconnected: " + socketChannel.getRemoteAddress());
socketChannel.close();
key.cancel();
}
}
}
}
}
}
```
相对于基于Java NIO手写服务端的实现,上面使用Netty实现的服务端代码有以下简洁之处:
- 代码结构简单:Netty的服务端代码结构非常简单,只需要实例化一个ServerBootstrap对象,设置相关配置参数,绑定端口即可。而使用Java NIO实现服务端需要手动创建ServerSocketChannel、Selector、SelectionKey等对象,并且需要手动管理缓冲区等资源,代码结构相对复杂。
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 创建boss线程组,用于处理连接请求
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 创建worker线程组,用于处理客户端请求
try {
ServerBootstrap b = new ServerBootstrap(); // 创建ServerBootstrap对象
b.group(bossGroup, workerGroup) // 设置线程组
.channel(NioServerSocketChannel.class) // 指定使用NIO进行网络通信
.childHandler(new ChannelInitializer<SocketChannel>() { // 设置事件处理器
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new MyServerHandler()); // 添加自定义的事件处理器
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 设置TCP参数
.childOption(ChannelOption.SO_KEEPALIVE, true); // 设置TCP参数
// 绑定端口并启动服务
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully(); // 释放线程池资源
bossGroup.shutdownGracefully(); // 释放线程池资源
}
- 事件处理代码简单:在Netty中,使用childHandler方法可以将所有相关的事件处理代码放在一个地方,包括读写事件、异常事件等。而在Java NIO中,需要手动编写所有事件处理代码,代码量比较大,维护成本高。
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // 处理读操作
// 处理读事件
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常操作
// 处理异常事件
}
}
- I/O操作简单:Netty自动进行缓冲区管理和数据编解码等操作,代码量少,可读性高。而Java NIO需要手动进行缓冲区管理和数据编解码等操作,代码量相对较大,可读性较差。
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// msg已经被自动解码了
// 处理读操作
ByteBuf in = (ByteBuf) msg;
System.out.println(in.toString(CharsetUtil.UTF_8));
// 输出客户端发送的信息
}
@Override
public void channelActive(final ChannelHandlerContext ctx) { // 通道激活时执行
// 写入数据时,不需要手动进行编码操作
final ByteBuf time = ctx.alloc().buffer(4); // 创建缓冲区
time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L)); // 向缓冲区写入数据
final ChannelFuture f = ctx.writeAndFlush(time); // 发送数据
f.addListener(new ChannelFutureListener() { // 监听发送结果
@Override
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close(); // 发送完毕后关闭通道
}
}); // (4)
}
}
- 功能丰富:Netty提供了许多其他功能,例如编码器和解码器等,这些功能可以简化I/O操作并提高性能。而Java NIO只提供了基本的I/O操作,需要手动编写其他功能的实现。
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加编解码器
pipeline.addLast(new MyMessageDecoder());
pipeline.addLast(new MyMessageEncoder());
// 添加业务处理器
pipeline.addLast(new MyServerHandler());
}
}
综上所述,相对于基于Java NIO手写服务端的实现,使用Netty实现服务端代码更简洁,更易于维护,并且还提供了许多其他功能,可以提高开发效率和程序性能。
以上代码可正常运行,有疑问可以交流~~
欢迎关注公众号:程序员的思考与落地
公众号提供大量实践案例,Java入门者不容错过哦,可以陪聊!!
转载自:https://juejin.cn/post/7217053578710761529