Netty - 粘包與拆包
粘包和拆包
產生粘包和拆包問題的主要原因是,作業系統在傳送TCP資料的時候,底層會有一個緩衝區,例如1024個位元組大小,如果一次請求傳送的資料量比較小,沒達到緩衝區大小,TCP則會將多個請求合併為同一個請求進行傳送,這就形成了粘包問題;如果一次請求傳送的資料量比較大,超過了緩衝區大小,TCP就會將其拆分為多次傳送,這就是拆包,也就是將一個大的包拆分為多個小包進行傳送。
問題重現
服務端
NIOServer.java
/**
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-26 20:42
*/
public class NIOServer {
@SneakyThrows
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind("127.0.0.1", 9090).sync();
channelFuture.channel().closeFuture().sync();
}
}
主要看自己寫的處理器類MyServerHandler.java
/**
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-26 20:50
*/
public class MyServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
SocketAddress socketAddress = channelHandlerContext.channel().remoteAddress();
System.out.println(socketAddress + "----" + s.trim());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketAddress socketAddress = ctx.channel().remoteAddress();
System.out.println(socketAddress+"---已連線!");
}
}
客戶端
NIOClient.java
/**
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-26 20:52
*/
public class NIOClient {
@SneakyThrows
public static void main(String[] args) {
EventLoopGroup client = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(client);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9090).sync();
Channel channel = channelFuture.channel();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset()));
while (true){
String message = reader.readLine().trim();
for (int i = 0; i < 10; i++) {
channel.writeAndFlush(message);
}
}
}
}
MyClientHandler.java
/**
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-26 21:02
*/
public class MyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
SocketAddress socketAddress = channelHandlerContext.channel().remoteAddress();
System.out.println(socketAddress + "發來訊息: " + s);
}
}
理想的情況.我們輸入一段文字服務端就應該列印這一段文字出來…
結果圖
可以發現資料並不是一段一段的列印出來.
netty自己封裝了一次java.nio的ByteBuffer使用的是ByteBuf.可以自動擴容.
緩衝區,例如1024個位元組大小,如果一次請求傳送的資料量比較小,沒達到緩衝區大小,TCP則會將多個請求合併為同一個請求進行傳送,這就形成了粘包問題.
解決方案
對於粘包和拆包問題,常見的解決方案有四種
(1)通過FixedLengthFrameDecoder 定長解碼器來解決定長訊息的黏包問題;
(2)通過LineBasedFrameDecoder和StringDecoder來解決以回車換行符作為訊息結束符的TCP黏包的問題;
(3)通過DelimiterBasedFrameDecoder 特殊分隔符解碼器來解決以特殊符號作為訊息結束符的TCP黏包問題;
(4)最後一種,也是本文的重點,通過LengthFieldBasedFrameDecoder 自定義長度解碼器解決TCP黏包問題。
NIOClient.java
public class NIOClient {
@SneakyThrows
public static void main(String[] args) {
EventLoopGroup client = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(client);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/**
* 1) lengthFieldOffset //長度欄位的偏差
* 2) lengthFieldLength //長度欄位佔的位元組數
* 3) lengthAdjustment //新增到長度欄位的補償值
* 4) initialBytesToStrip //從解碼幀中第一次去除的位元組數
*/
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
/**
* 計算當前待傳送訊息的二進位制位元組長度,將該長度新增到ByteBuf的緩衝區頭中
*/
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9090).sync();
Channel channel = channelFuture.channel();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset()));
while (true){
String message = reader.readLine().trim();
for (int i = 0; i < 10; i++) {
channel.writeAndFlush(message);
}
}
}
}
NIOServer.java
public class NIOServer {
@SneakyThrows
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/**
* 1) lengthFieldOffset //長度欄位的偏差
* 2) lengthFieldLength //長度欄位佔的位元組數
* 3) lengthAdjustment //新增到長度欄位的補償值
* 4) initialBytesToStrip //從解碼幀中第一次去除的位元組數
*/
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
/**
* 計算當前待傳送訊息的二進位制位元組長度,將該長度新增到ByteBuf的緩衝區頭中
*/
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind("127.0.0.1", 9090).sync();
channelFuture.channel().closeFuture().sync();
}
}
問題解決!
相關文章
- Netty 中的粘包和拆包Netty
- Netty拾遺(七)——粘包與拆包問題Netty
- Netty如何解決粘包拆包?(二)Netty
- TCP 粘包拆包TCP
- TCP粘包拆包問題TCP
- Netty入門系列(2) --使用Netty解決粘包和拆包問題Netty
- Netty解決粘包和拆包問題的四種方案Netty
- Netty--粘包與分包Netty
- TCP的粘包拆包技術TCP
- 粘包拆包及解決方案
- Netty解決半包(TCP粘包/拆包導致)讀寫問題NettyTCP
- 深入學習Netty(5)——Netty是如何解決TCP粘包/拆包問題的?NettyTCP
- Netty2:粘包/拆包問題與使用LineBasedFrameDecoder的解決方案Netty
- Netty(三) 什麼是 TCP 拆、粘包?如何解決?NettyTCP
- 從零開始netty學習筆記之TCP粘包和拆包Netty筆記TCP
- go語言處理TCP拆包/粘包GoTCP
- TCP 粘包 - 拆包問題及解決方案TCP
- Netty粘包&半包解決方案Netty
- Netty Protobuf處理粘包分析Netty
- 計算機網路 - TCP粘包、拆包以及解決方案計算機網路TCP
- 粘包/拆包問題一直都存在,只是到TCP就拆不動了。TCP
- 64行程式碼實現零拷貝go的TCP拆包粘包行程GoTCP
- netty 解決粘包 和 分包的問題Netty
- 粘包問題
- 25. Socket與粘包問題
- Netty中使用MessagePack時的TCP粘包問題與解決方案NettyTCP
- Netty 原始碼分析之拆包器的奧祕Netty原始碼
- socket的半包,粘包與分包的問題
- Go TCP 粘包問題GoTCP
- 通過大量實戰案例分解Netty中是如何解決拆包黏包問題的?Netty
- 詳說tcp粘包和半包TCP
- Socket 粘包和分包問題
- AS3 TCP Socket 拆包S3TCP
- Netty原始碼學習6——netty編碼解碼器&粘包半包問題的解決Netty原始碼
- 結合RPC框架通訊談 netty如何解決TCP粘包問題RPC框架NettyTCP
- 粘包問題原因和解決方法
- Python:列表也能拆包?Python
- 從零開始實現簡單 RPC 框架 7:網路通訊之自定義協議(粘包拆包、編解碼)RPC框架協議