Netty学习路线图 - 第四阶段:Netty基础应用

📚 Netty学习系列之四

本文是Netty学习路线的第四篇,我们将用大白话讲解Netty的基础应用,带你从理论走向实践。

写在前面

大家好!在前面三篇文章中,我们学习了Java基础、NIO编程和Netty的核心概念。但是光有理论可不行,这次我们就动手实践,看看Netty到底能干些啥!

本文我们会通过几个实用的例子,一步步带你掌握Netty的基础应用,包括:

  • 搭建简单的Netty服务器和客户端
  • 实现一个迷你HTTP服务器
  • 开发一个WebSocket聊天室
  • 设计自定义协议
  • 玩转编解码器

好了,话不多说,我们直接开始动手吧!

一、搭建简单Netty服务器与客户端

1. 先来个Echo服务器

Echo服务器是最简单的网络应用之一 - 它就像个"复读机",客户端发啥它就回啥。我们就从这个入手。

首先,我们需要创建一个Maven项目,并添加Netty依赖:

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.86.Final</version>
</dependency>

接下来,我们创建一个处理器(Handler),它负责处理客户端消息:

public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 接收到消息后直接发回去ctx.write(msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {// 刷新队列中的数据ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 异常处理cause.printStackTrace();ctx.close();}
}

然后是服务器主类:

public class EchoServer {public static void main(String[] args) throws Exception {// 创建两个线程池EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理数据try {// 创建服务器启动器ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // 使用NIO.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoServerHandler());}});// 绑定端口并启动ChannelFuture f = b.bind(8888).sync();System.out.println("Echo服务器已启动,端口:8888");// 等待服务器关闭f.channel().closeFuture().sync();} finally {// 优雅地关闭线程池bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

2. 再来个Echo客户端

服务器有了,我们还需要一个客户端来测试:

public class EchoClientHandler extends ChannelInboundHandlerAdapter {private final ByteBuf message;public EchoClientHandler() {// 创建一条测试消息message = Unpooled.buffer();message.writeBytes("你好,Netty!".getBytes());}@Overridepublic void channelActive(ChannelHandlerContext ctx) {// 连接建立后发送消息ctx.writeAndFlush(message.copy());}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 收到服务器回复ByteBuf in = (ByteBuf) msg;System.out.println("收到服务器回复: " + in.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

客户端主类:

public class EchoClient {public static void main(String[] args) throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoClientHandler());}});// 连接服务器ChannelFuture f = b.connect("localhost", 8888).sync();System.out.println("已连接到服务器");// 等待连接关闭f.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}
}

运行这两个程序,你就能看到客户端发送的消息被服务器原样返回了!这就是一个最基础的Netty应用。

3. 关于这个例子的几点解释

可能有人会问,这个例子看起来代码挺多的,比Socket编程复杂啊?别急,我来解释一下Netty的优势:

  1. 异步非阻塞:虽然代码看着多,但Netty是完全异步的,可以处理成千上万的连接
  2. 线程模型清晰:BossGroup负责接收连接,WorkerGroup负责处理数据
  3. Pipeline机制:可以轻松添加多个处理器,形成处理链
  4. 扩展性强:这个例子很简单,但架构适用于任何复杂度的应用

二、实现HTTP服务器

接下来,我们来实现一个简单的HTTP服务器。这比Echo服务器稍微复杂一点,但Netty已经为我们提供了HTTP编解码器,省了不少事。

1. HTTP服务器处理器

public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {if (msg instanceof HttpRequest) {HttpRequest request = (HttpRequest) msg;// 获取请求URIString uri = request.uri();System.out.println("收到请求: " + uri);// 构建响应内容StringBuilder content = new StringBuilder();content.append("<!DOCTYPE html>\r\n");content.append("<html>\r\n");content.append("<head><title>Netty HTTP 服务器</title></head>\r\n");content.append("<body>\r\n");content.append("<h1>你好,这是一个Netty HTTP服务器</h1>\r\n");content.append("<p>请求路径: ").append(uri).append("</p>\r\n");content.append("</body>\r\n");content.append("</html>\r\n");// 创建响应FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,Unpooled.copiedBuffer(content.toString(), CharsetUtil.UTF_8));// 设置响应头response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());// 发送响应ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

2. HTTP服务器主类

public class HttpServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline pipeline = ch.pipeline();// 添加HTTP编解码器pipeline.addLast(new HttpServerCodec());// 添加HTTP对象聚合器pipeline.addLast(new HttpObjectAggregator(65536));// 添加我们的处理器pipeline.addLast(new HttpServerHandler());}});ChannelFuture f = b.bind(8080).sync();System.out.println("HTTP服务器已启动,访问地址: http://localhost:8080");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

启动这个服务器后,打开浏览器访问 http://localhost:8080 ,你就能看到一个网页了!是不是很神奇?

3. 关键点解析

看起来我们只写了几十行代码,就实现了一个HTTP服务器,这是怎么做到的?关键在于Netty的几个特殊处理器:

  • HttpServerCodec:HTTP编解码器,负责将字节流转换为HTTP请求/响应
  • HttpObjectAggregator:HTTP消息聚合器,将HTTP消息的多个部分合并成一个完整的HTTP请求或响应
  • SimpleChannelInboundHandler:简化的入站处理器,帮我们自动释放资源

通过这些处理器的组合,我们可以轻松处理HTTP请求,而不用关心底层的编解码细节。

三、WebSocket应用开发

接下来,我们实现一个简单的WebSocket聊天室。WebSocket是HTML5的一个新特性,允许浏览器和服务器建立持久连接,非常适合聊天应用。

1. WebSocket处理器

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {// 用于保存所有WebSocket连接private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof HttpRequest) {// 处理HTTP请求,WebSocket握手HttpRequest request = (HttpRequest) msg;// 如果是WebSocket请求,进行升级if (isWebSocketRequest(request)) {// 将HTTP升级为WebSocketctx.pipeline().replace(this, "websocketHandler", new WebSocketFrameHandler());// 执行握手handleHandshake(ctx, request);} else {// 如果不是WebSocket请求,返回HTML页面sendHttpResponse(ctx, request, getWebSocketHtml());}} else {// 如果不是HTTP请求,传递给下一个处理器ctx.fireChannelRead(msg);}}private boolean isWebSocketRequest(HttpRequest req) {return req.headers().contains(HttpHeaderNames.UPGRADE, "websocket", true);}private void handleHandshake(ChannelHandlerContext ctx, HttpRequest req) {WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://" + req.headers().get(HttpHeaderNames.HOST) + "/websocket", null, false);WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);if (handshaker == null) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {handshaker.handshake(ctx.channel(), req);}}private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, String html) {FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,Unpooled.copiedBuffer(html, CharsetUtil.UTF_8));res.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);}private String getWebSocketHtml() {return "<!DOCTYPE html>\r\n" +"<html>\r\n" +"<head>\r\n" +"    <title>WebSocket聊天室</title>\r\n" +"    <script type=\"text/javascript\">\r\n" +"        var socket;\r\n" +"        if (window.WebSocket) {\r\n" +"            socket = new WebSocket(\"ws://\" + window.location.host + \"/websocket\");\r\n" +"            socket.onmessage = function(event) {\r\n" +"                var chat = document.getElementById('chat');\r\n" +"                chat.innerHTML += event.data + '<br>';\r\n" +"            };\r\n" +"            socket.onopen = function(event) {\r\n" +"                console.log(\"WebSocket已连接\");\r\n" +"            };\r\n" +"            socket.onclose = function(event) {\r\n" +"                console.log(\"WebSocket已关闭\");\r\n" +"            };\r\n" +"        } else {\r\n" +"            alert(\"浏览器不支持WebSocket!\");\r\n" +"        }\r\n" +"        \r\n" +"        function send(message) {\r\n" +"            if (!socket) return;\r\n" +"            if (socket.readyState == WebSocket.OPEN) {\r\n" +"                socket.send(message);\r\n" +"            }\r\n" +"        }\r\n" +"    </script>\r\n" +"</head>\r\n" +"<body>\r\n" +"    <h1>Netty WebSocket聊天室</h1>\r\n" +"    <div id=\"chat\" style=\"height:300px;overflow:auto;border:1px solid #ccc;padding:10px;\"></div>\r\n" +"    <input type=\"text\" id=\"message\" style=\"width:300px\">\r\n" +"    <button onclick=\"send(document.getElementById('message').value)\">发送</button>\r\n" +"</body>\r\n" +"</html>";}
}

2. WebSocket帧处理器

public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overridepublic void channelActive(ChannelHandlerContext ctx) {// 有新连接加入channels.add(ctx.channel());System.out.println("客户端加入: " + ctx.channel().remoteAddress());}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {if (frame instanceof TextWebSocketFrame) {// 处理文本帧String message = ((TextWebSocketFrame) frame).text();System.out.println("收到消息: " + message);// 广播消息给所有连接String response = "用户" + ctx.channel().remoteAddress() + " 说: " + message;channels.writeAndFlush(new TextWebSocketFrame(response));} else {// 不支持的帧类型System.out.println("不支持的帧类型: " + frame.getClass().getName());}}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {// 连接断开channels.remove(ctx.channel());System.out.println("客户端断开: " + ctx.channel().remoteAddress());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

3. WebSocket服务器主类

public class WebSocketServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();p.addLast(new HttpServerCodec());p.addLast(new HttpObjectAggregator(65536));p.addLast(new WebSocketServerHandler());}});ChannelFuture f = b.bind(8080).sync();System.out.println("WebSocket服务器已启动,访问地址: http://localhost:8080");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

启动服务器后,打开浏览器访问 http://localhost:8080 ,你就能看到一个简单的聊天室页面。打开多个浏览器窗口,就可以实现多人聊天了!

4. WebSocket关键点解析

WebSocket应用比HTTP服务器稍微复杂一些,关键点在于:

  1. HTTP升级为WebSocket:客户端首先发起HTTP请求,然后协议升级为WebSocket
  2. 握手过程:服务器需要返回特定的HTTP响应完成握手
  3. 不同类型的帧:WebSocket有多种帧类型,我们这里只处理了文本帧
  4. 广播消息:使用ChannelGroup可以轻松地将消息广播给所有连接的客户端

四、自定义协议开发

在实际应用中,我们经常需要设计自己的通信协议。下面我们来实现一个简单的自定义协议。

1. 协议设计

我们设计一个简单的消息协议,格式如下:

+--------+------+--------+----------------+
| 魔数    | 版本 | 消息长度 |    消息内容     |
| 4字节   | 1字节 | 4字节   |    变长数据     |
+--------+------+--------+----------------+
  • 魔数:固定值0xCAFEBABE,用于快速识别协议
  • 版本:协议版本号,便于后续扩展
  • 消息长度:消息内容的长度,单位为字节
  • 消息内容:实际传输的数据,格式为JSON字符串

2. 消息对象定义

public class MyMessage {private String content;private int type;private long timestamp;// getter、setter和构造方法省略@Overridepublic String toString() {return "MyMessage{" +"content='" + content + '\'' +", type=" + type +", timestamp=" + timestamp +'}';}
}

3. 编码器实现

public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {@Overrideprotected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception {// 1. 写入魔数out.writeInt(0xCAFEBABE);// 2. 写入版本号out.writeByte(1);// 3. 将消息对象转为JSONString json = new ObjectMapper().writeValueAsString(msg);byte[] content = json.getBytes(CharsetUtil.UTF_8);// 4. 写入消息长度out.writeInt(content.length);// 5. 写入消息内容out.writeBytes(content);}
}

4. 解码器实现

public class MyMessageDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 1. 检查是否有足够的字节if (in.readableBytes() < 9) {  // 魔数(4) + 版本(1) + 长度(4) = 9字节return;}// 2. 标记当前读取位置in.markReaderIndex();// 3. 检查魔数int magic = in.readInt();if (magic != 0xCAFEBABE) {in.resetReaderIndex();throw new CorruptedFrameException("Invalid magic number: " + magic);}// 4. 读取版本号byte version = in.readByte();if (version != 1) {in.resetReaderIndex();throw new CorruptedFrameException("Invalid version: " + version);}// 5. 读取消息长度int length = in.readInt();if (length < 0 || length > 65535) {in.resetReaderIndex();throw new CorruptedFrameException("Invalid length: " + length);}// 6. 检查是否有足够的字节if (in.readableBytes() < length) {in.resetReaderIndex();return;}// 7. 读取消息内容byte[] content = new byte[length];in.readBytes(content);// 8. 将JSON转换为对象MyMessage message = new ObjectMapper().readValue(content, MyMessage.class);// 9. 将对象添加到输出列表out.add(message);}
}

5. 使用自定义协议的服务器

public class CustomProtocolServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();// 添加编解码器p.addLast(new MyMessageDecoder());p.addLast(new MyMessageEncoder());// 添加业务处理器p.addLast(new SimpleChannelInboundHandler<MyMessage>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) {System.out.println("收到消息: " + msg);// 回复一条消息MyMessage response = new MyMessage();response.setContent("已收到你的消息: " + msg.getContent());response.setType(200);response.setTimestamp(System.currentTimeMillis());ctx.writeAndFlush(response);}});}});ChannelFuture f = b.bind(8888).sync();System.out.println("自定义协议服务器已启动,端口: 8888");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

6. 自定义协议的关键点

设计自定义协议时,需要注意以下几点:

  1. 协议格式明确:需要明确定义消息的格式,包括头部、长度、内容等
  2. 魔数校验:使用魔数可以快速识别协议,过滤掉非法请求
  3. 版本控制:协议需要有版本号,便于后续升级
  4. 长度字段:必须有长度字段,便于拆包
  5. 编解码器分离:将编码和解码逻辑分开,便于维护

五、编解码器实践

最后,我们来看几种常见的编解码器实现。

1. 基于长度的拆包器

TCP是流式协议,数据可能会被分成多个包传输,也可能多个消息会合并成一个包。为了正确地拆分消息,我们需要使用拆包器。

public class LengthBasedServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();// 添加长度字段解码器,格式为: 长度(4字节) + 内容p.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4));// 添加长度字段编码器p.addLast(new LengthFieldPrepender(4));// 添加字符串编解码器p.addLast(new StringDecoder(CharsetUtil.UTF_8));p.addLast(new StringEncoder(CharsetUtil.UTF_8));// 业务处理器p.addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("收到消息: " + msg);ctx.writeAndFlush("回复: " + msg);}});}});ChannelFuture f = b.bind(8888).sync();System.out.println("拆包示例服务器已启动,端口: 8888");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

2. 基于分隔符的拆包器

有时候我们使用特定的字符作为消息分隔符,比如换行符。

public class DelimiterBasedServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();// 添加行分隔符解码器p.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));// 添加字符串编解码器p.addLast(new StringDecoder(CharsetUtil.UTF_8));p.addLast(new StringEncoder(CharsetUtil.UTF_8));// 业务处理器p.addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("收到消息: " + msg);ctx.writeAndFlush("回复: " + msg + "\r\n");}});}});ChannelFuture f = b.bind(8888).sync();System.out.println("分隔符示例服务器已启动,端口: 8888");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

3. 对象序列化编解码器

如果我们想直接传输Java对象,可以使用对象序列化编解码器。

// 可序列化的消息对象
public class User implements Serializable {private static final long serialVersionUID = 1L;private String name;private int age;// getter、setter和构造方法省略@Overridepublic String toString() {return "User{name='" + name + "', age=" + age + "}";}
}
public class SerializationServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline p = ch.pipeline();// 添加对象编解码器p.addLast(new ObjectEncoder());p.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));// 业务处理器p.addLast(new SimpleChannelInboundHandler<User>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, User user) {System.out.println("收到用户: " + user);// 回复一个用户对象User response = new User();response.setName("服务器用户");response.setAge(20);ctx.writeAndFlush(response);}});}});ChannelFuture f = b.bind(8888).sync();System.out.println("序列化示例服务器已启动,端口: 8888");f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

4. 编解码器的选择

  • LengthFieldBasedFrameDecoder:适合自定义协议,灵活性强
  • DelimiterBasedFrameDecoder:适合基于文本的协议,如HTTP、SMTP等
  • ObjectEncoder/ObjectDecoder:适合Java对象传输,但不适合跨语言通信
  • JsonEncoder/JsonDecoder:适合跨语言通信
  • ProtobufEncoder/ProtobufDecoder:高性能、跨语言通信的首选

总结与实践建议

通过本文,我们学习了Netty的四种基础应用:

  1. 简单的Echo服务器与客户端
  2. HTTP服务器
  3. WebSocket聊天室
  4. 自定义协议

在实际开发中,记住以下几点:

  1. 选择合适的编解码器:根据需求选择合适的编解码器,避免重复造轮子
  2. 正确处理粘包/拆包:TCP是流式的,必须正确处理消息边界
  3. 异常处理要完善:网络编程中异常情况很多,一定要做好异常处理
  4. 资源要及时释放:关闭连接、释放ByteBuf等资源
  5. 避免阻塞EventLoop:耗时操作放到单独的线程池中执行

Netty的基础应用非常广泛,掌握了这些基础应用,你就能开发出各种高性能的网络应用了。

在下一篇文章中,我们将探讨Netty的高级特性,包括心跳检测、空闲连接处理、内存管理等内容,敬请期待!


作者:by.G
如需转载,请注明出处

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87076.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87076.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/87076.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源项目推荐:MCP Registry——管理MCP服务器的利器

探索MCP Registry:未来模型上下文协议的核心注册服务 随着人工智能技术的迅速发展,机器学习模型的管理和配置变得愈发重要。今天,我们将探索一个颇具潜力的开源项目——MCP Registry。这是一个由社区驱动的注册服务,专为模型上下文协议(Model Context Protocol,简称MCP)…

Spring Boot 统一功能处理:拦截器详解

一、拦截器核心概念 作用&#xff1a;拦截器是 Spring 框架提供的核心功能&#xff0c;用于在请求处理前后执行预定义逻辑&#xff0c;实现统一处理&#xff08;如登录校验、日志记录等&#xff09;。 核心方法&#xff1a; public class LoginInterceptor implements Handl…

在docker容器中安装docker服务,基于fuse-overlayfs进行overlay挂载,而不是vfs

1、docker 安装 正常安装docker软件&#xff0c;运行docker时&#xff0c;会提示&#xff1a;No docker socket 服务 2、启动docker服务&#xff08;包含守护进程&#xff09; systemctl start docker #dockerd &if ! ps aux | grep -v grep | grep -q "dockerd&qu…

虚拟机配置注意事项

一.VM大部分产品免费&#xff0c;遇到付费的要斟酌一下 在小编之前的文章中有简单下载VM的教程VMwareWorkstPro安装-CSDN博客 二.配置过程中的设置大部分都可以在配置完成后更改 例如下图设备所涉及到的&#xff0c;都是可以更改设置的 三.电脑关机时&#xff0c;要注意先把…

openGL+QT快速学习和入门案列

openGLQT快速学习和入门案列

深度学习03 人工神经网络ANN

什么是神经网络 人工神经网络&#xff08; Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络&#xff08;NN&#xff09;,是一种模仿生物神经网络结构和功能的计算模型,人脑可以看做是一个生物神经网络,由众多的神经元连接而成.各个神经元传递复…

Linux中部署Jenkins保姆间教程

本文将以docker的方式&#xff0c;讲述如何部署Jenkins 一、拉取Jenkins镜像 1.1 最新版Jenkins介绍 最新版Jenkins地址&#xff1a;Download and deploy 当前最新版的如下图所示&#xff1a; 1.2 各版本支持的JDK版本 地址如下&#xff1a;Java Support Policy 如果你安装…

【软考中级·软件评测师】下午题·面向对象测试之架构考点全析:分层、分布式、微内核与事件驱动

一、分层架构&#xff1a;分层独立与质量特性的双向约束 分层架构通过“垂直分层&#xff08;表示层→服务层→业务逻辑层→数据层&#xff09;”实现职责隔离&#xff0c;是Web应用、企业级系统的主流架构模式。 1. 父类成员函数重测场景 子类继承父类时&#xff0c;若父类…

C++ 快速回顾(五)

C 快速回顾&#xff08;五&#xff09; 前言一、Dll和Lib的区别区别在开发中使用 二、封装并使用C库1.封装库2.使用库 三、封装并使用C库1.封装库2.使用库 前言 用于快速回顾之前遗漏或者补充C知识 一、Dll和Lib的区别 静态库&#xff08;LIB&#xff09;在编译时链接&#…

【ARM】解决ArmDS的工程没有生成Map文件的问题

1、 文档目标 在嵌入式开发过程中&#xff0c;使用Arm Development Studio&#xff08;简称ArmDS&#xff09;进行项目构建时&#xff0c;Map文件的生成是调试和分析代码的重要环节。Map文件不仅记录了程序中各个段&#xff08;sections&#xff09;的内存分布情况&#xff0c…

Java如何导出word(根据模板生成),通过word转成pdf,放压缩包

<!-- 导出word文档所需依赖--><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.10.0-beta</version></dependency><dependency><groupId>org.apache.poi</gr…

【C#】 DevExpress.XtraEditors.SidePanel

DevExpress.XtraEditors.SidePanel&#xff0c; 它是 DevExpress 提供的“侧边滑出”面板&#xff08;类似于抽屉、浮动信息区&#xff09;&#xff0c;非常适合做可隐藏的参数区、帮助区、临时交互区等。 SidePanel 用法核心点 1. 基本用法 可容纳其它控件&#xff0c;就像普…

1.1_2 计算机网络的组成和功能

在这个视频中&#xff0c;我们会探讨计算机网络的组成和功能。我们会从三个视角去探讨计算机网络由哪些部分组成&#xff0c;其次&#xff0c;我们会简单的了解计算机网络的功能。 首先我们可以把计算机网络看作是由硬件、软件和协议共同组成的一个庞大复杂的系统。首先在硬件上…

Linux驱动学习day11(定时器)

定时器 定时器主要作用就是&#xff1a;设置超时时间&#xff0c;执行超时函数。 按键按下存在抖动&#xff0c;为了消除抖动可以设置定时器&#xff0c;如上图所示&#xff0c;按下一次按键会产生多次抖动&#xff0c;即会产生多次中断&#xff0c;在每次中断产生的时候&…

Java 编程之观察者模式详解

一、什么是观察者模式&#xff1f; 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;用于对象之间的一对多依赖关系&#xff1a;当被观察对象&#xff08;Subject&#xff09;状态发生变化时&#xff0c;所有依赖它的观察者&#xff08;O…

【C++】经典string类问题

目录 1. 浅拷贝 2. 深拷贝 3. string类传统写法 4. string类现代版写法 5. 自定义类实现swap成员函数 6. 标准库swap函数的调用 7. 引用计数和写时拷贝 1. 浅拷贝 若string类没有显示定义拷贝构造函数与赋值运算符重载&#xff0c;编译器会自动生成默认的&#xff0c…

kotlin中object:的用法

在Kotlin中&#xff0c;object: 用于声明匿名对象&#xff08;Anonymous Object&#xff09;&#xff0c;这是实现接口或继承类的轻量级方式&#xff0c;无需显式定义具名类。以下是核心用法和场景&#xff1a; 1. 基本语法 val obj object : SomeInterface { // 实现接口ov…

js代码04

题目 非常好。我们刚刚看到了回调函数在处理多个异步操作时会变得多么混乱&#xff08;回调地狱&#xff09;。为了解决这个问题&#xff0c;现代 JavaScript 提供了一个更强大、更优雅的工具&#xff1a;Promise。 Promise&#xff0c;正如其名&#xff0c;是一个“承诺”。…

Jenkins初探-通过Docker部署Jenkins并安装插件

简介 本文介绍了使用Docker安装Jenkins并进行初始配置的完整流程。主要内容包括&#xff1a; (1)通过docker pull命令获取Jenkins镜像&#xff1b;(2)使用docker run命令启动容器并映射端口&#xff1b;(3)访问Jenkins界面获取初始管理员密码&#xff1b;(4)安装推荐插件并创…

嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例

&#x1f4cd; 本文为嵌入式学习系列第二篇&#xff0c;基于 GitHub 开源项目&#xff1a;0voice/EmbeddedSoftwareLearn &#x1f4ac; 作者&#xff1a;0voice &#x1f440; 适合对象&#xff1a;嵌入式初学者、STM32学习者、想搞明白外设驱动开发的C语言学习者 一、驱动是什…