1 Netty基礎知識
1.1 Netty作用
1.2 TCP和UDP
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是面向非連接的協議,它不與對方建立連接,而是直接就把數據包發送過去!
TCP | UDP | |
是否連接 | 面向連接 | 面向非連接 |
傳輸可靠性 | 可靠 | 不可靠 |
應用場合 | 傳輸大量數據 | 傳輸少量數據 |
速度 | 慢 | 快 |
2 HelloWorld代碼
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
/**
* DISCARD協議 丟棄協議,其實就是隻接收數據,不做任何處理。
* ECHO服務(響應式協議),其實就是返回數據。
* 實現步驟:
* step1 繼承 ChannelHandlerAdapter
* step2 覆蓋chanelRead()事件處理方法
* step3 釋放ByteBuffer,ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放
* step4 異常處理,即當Netty由於IO錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來並且把關聯的channel給關閉掉。
*/
public class DiscardServerHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext chc, Object msg) {
try {
// 簡單的讀寫操作
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Server :" + body);
chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回數據
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg); // 釋放msg
}
}
@Override
public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
cause.printStackTrace();
chc.close();
}
}
然後是服務端Netty啓動代碼import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* step1 創建兩個線程組分別負責接收和處理
* step2 啓動NIO服務輔助類
* step3 綁定兩個線程組,指定一個通道,關聯一個處理類,設置一些相關參數
* step4 監聽端口
* step5 關閉一些資源
*/
public class DiscardServer {
private static final int PORT = 8888; // 監聽的端口號
public static void main(String[] args) {
// NioEventLoopGroup 是用來處理I/O操作的多線程事件循環器
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用於接收進來的連接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用於處理進來的連接
try {
ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一個啓動NIO服務的輔助啓動類
bootstrap.group(bossGroup, workerGroup) // 綁定倆個線程組
.channel(NioServerSocketChannel.class) // 指定用 NioServerSocketChannel 通道
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我們自定義的服務器處理類,負責處理連接
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 設置tcp緩衝區
.childOption(ChannelOption.SO_KEEPALIVE, true); // 設置保持連接
ChannelFuture future = bootstrap.bind(PORT).sync(); // 綁定端口
future.channel().closeFuture().sync(); // 等待關閉
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully(); // 關閉線程組,先打開的後關閉
bossGroup.shutdownGracefully();
}
}
}
客戶端代碼和服務端代碼類似,區別在於:服務端提供監聽端口,客戶端負責連接端口;服務端的輔助類是ServerBootstrap,而客戶端的輔助類是Bootstrap(和ServerSocket,Socket關係很像)import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
/**
*
* 和ServerHandler類似
*
*/
public class ClientHandler extends ChannelHandlerAdapter{
public void channelRead(ChannelHandlerContext chc, Object msg) {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Client :" + body);
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg); // 釋放msg
}
}
public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
cause.printStackTrace();
chc.close();
}
}
客戶端啓動服務類import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
private static final int PORT = 8888;
private static final String HOST = "127.0.0.1";
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandler());
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立連接
future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,還有幾個bug沒有改".getBytes())); // 向服務端發送數據
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
到這裏,便是Netty的簡單應用。打印的結果:Server :快醒醒,還有幾個bug沒有改
Client :卒......
3 拆包粘包
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
/**
* DISCARD協議 丟棄協議,其實就是隻接收數據,不做任何處理。
* ECHO服務(響應式協議),其實就是返回數據。
* 實現步驟:
* step1 繼承 ChannelHandlerAdapter
* step2 覆蓋chanelRead()事件處理方法
* step3 釋放ByteBuffer,ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放
* step4 異常處理,即當Netty由於IO錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來並且把關聯的channel給關閉掉。
*/
public class DiscardServerHandler extends ChannelHandlerAdapter{
private static final String DELIMITER = "^_^"; // 拆包分隔符
@Override
public void channelRead(ChannelHandlerContext chc, Object msg) {
try {
// 簡單的讀寫操作
/*
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Server :" + body);
chc.writeAndFlush(Unpooled.copiedBuffer("卒...... ".getBytes())); // 返回數據
*/
// 加了 StringDecoder 字符串解碼器後可以直接讀取
System.out.println("Server :" + msg);
// 分隔符拆包
// String response = msg + " , 騙你的" + DELIMITER;
// chc.channel().writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
// 限定長度拆包
chc.channel().writeAndFlush(Unpooled.copiedBuffer(msg.toString().getBytes()));
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg); // 釋放msg
}
}
@Override
public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
cause.printStackTrace();
chc.close();
}
}
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* step1 創建兩個線程組分別負責接收和處理
* step2 啓動NIO服務輔助類
* step3 綁定兩個線程組,指定一個通道,關聯一個處理類,設置一些相關參數
* step4 監聽端口
* step5 關閉一些資源
*/
public class DiscardServer {
private static final int PORT = 8888; // 監聽的端口號
private static final String DELIMITER = "^_^"; // 拆包分隔符
public static void main(String[] args) {
// NioEventLoopGroup 是用來處理I/O操作的多線程事件循環器
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用於接收進來的連接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用於處理進來的連接
try {
ServerBootstrap bootstrap = new ServerBootstrap(); // ServerBootstrap 是一個啓動NIO服務的輔助啓動類
bootstrap.group(bossGroup, workerGroup) // 綁定倆個線程組
.channel(NioServerSocketChannel.class) // 指定用 NioServerSocketChannel 通道
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 考慮到tcp拆包粘包的問題,升級代碼
// step1 獲取特殊分隔符的ByteBuffer
ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());
// step2 設置特殊分隔符
// socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));
// 還有一種就是指定長度 二選一 (用的比較少)
socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
// step3 設置字符串形式的解碼
socketChannel.pipeline().addLast(new StringDecoder());
// step4 設置處理類
socketChannel.pipeline().addLast(new DiscardServerHandler()); // DiscardServerHandler是我們自定義的服務器處理類,負責處理連接
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 設置tcp緩衝區
.childOption(ChannelOption.SO_KEEPALIVE, true); // 設置保持連接
ChannelFuture future = bootstrap.bind(PORT).sync(); // 綁定端口
future.channel().closeFuture().sync(); // 等待關閉
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully(); // 關閉線程組,先打開的後關閉
bossGroup.shutdownGracefully();
}
}
}
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
/**
*
* 和ServerHandler類似
*
*/
public class ClientHandler extends ChannelHandlerAdapter{
public void channelRead(ChannelHandlerContext chc, Object msg) {
try {
/*
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
*/
System.out.println("Client : " + msg);
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg); // 釋放msg
}
}
public void exceptionCaught(ChannelHandlerContext chc, Throwable cause) {
// 這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
cause.printStackTrace();
chc.close();
}
}
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class Client {
private static final int PORT = 8888;
private static final String HOST = "127.0.0.1";
private static final String DELIMITER = "^_^"; // 拆包分隔符
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 考慮到tcp拆包粘包的問題,升級代碼
ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER.getBytes());
// socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(128, delimiter));
socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new ClientHandler());
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); // 建立連接
// future.channel().writeAndFlush(Unpooled.copiedBuffer("快醒醒,還有幾個bug沒有改".getBytes())); // 向服務端發送數據
// future.channel().writeAndFlush(Unpooled.copiedBuffer(("又要加班了"+DELIMITER).getBytes()));
// future.channel().writeAndFlush(Unpooled.copiedBuffer(("好開心啊T。T"+DELIMITER).getBytes()));
future.channel().writeAndFlush(Unpooled.copiedBuffer("123456789".getBytes())); // 傳的個數是9個,只打印了5個,還有4個在等待中
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
4 優質博客
以上便是Netty5快速入門的內容,如果你覺得不錯,可以點個贊哦 ,下一章介紹Netty5的編解碼知識。