Netty學習路線圖 - 第四階段:Netty基礎應用

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語言學習者 一、驅動是什…