TCP協議是一種字節流協議,沒有記錄邊界,咱們在接收消息的時候,不能人爲接收到的數據包就是一個整包消息java
當客戶端向服務器端發送多個消息數據的時候,TCP協議可能將多個消息數據合併成一個數據包進行發送,這就是粘包bootstrap
當客戶端向服務器端發送的消息過大的時候,tcp協議可能將一個數據包拆成多個數據包來進行發送,這就是拆包服務器
如下一netty爲例,展現一下tcp粘包和拆包的例子:dom
ServerBusinessHanler:socket
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset; import java.util.UUID; public class ServerBusinessHanler extends SimpleChannelInboundHandler<ByteBuf> { int count = 0; @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { byte[] bytes = new byte[msg.readableBytes()]; msg.readBytes(bytes); String message = new String(bytes, Charset.forName("UTF-8")); System.out.println("從客戶端收到的字符串:" + message); System.out.println("服務端接收到的請求數:" + (++count)); ctx.writeAndFlush(Unpooled.copiedBuffer(UUID.randomUUID().toString(),Charset.forName("UTF-8")) ); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println(cause.getStackTrace()); ctx.channel().close(); } }
Server:tcp
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; 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 java.net.InetSocketAddress; public class Server { public static void main(String[] args) throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ServerBusinessHanler()); } }); serverBootstrap.bind(new InetSocketAddress(8899)).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
ClientBusinessHandler:ide
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset; public class ClientBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> { int count = 1; @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { byte[] bytes = new byte[msg.readableBytes()]; msg.readBytes(bytes); System.out.println("從服務器端接收到的信息: " + new String(bytes, Charset.forName("UTF-8"))); System.out.println("從服務器端讀到的請求的個數: " + count++); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 10; i++) { ctx.writeAndFlush(Unpooled.copiedBuffer("中國移動".getBytes())); } ctx.writeAndFlush(Unpooled.copiedBuffer("jiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyiping".getBytes())); } }
Client:oop
import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class Client { public static void main(String[] args) throws InterruptedException { EventLoopGroup eventExecutors = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ClientBusinessHandler()); } }); Channel channel = bootstrap.connect("127.0.0.1", 8899).channel(); channel.closeFuture().sync(); } }
分別運行服務器端和客戶端,咱們看到服務器端的輸出:編碼
相應的,服務器端也只給客戶端了兩個返回,而不是咱們期待的11個.net
咱們從客戶端傳入的十個 "中國移動"和第二次發送的長的字符串的前一部分發生了粘包,而第二個長字符串的後一部分則被拆分到第二個數據包裏了
粘包和拆包的狀況都出現了
那怎麼解決粘包和拆包的問題呢? 一種方法是在字符串中使用特定的分隔符來分隔,另一種方法是,咱們在發送數據包的時候在前邊附加上數據包的長度
netty爲咱們提供了多種的解碼器,好比定長解碼器和基於分隔符的解碼器等,這裏,我麼使用 LengthFieldBasedFrameDecoder這個解碼器來解決粘包和拆包的問題,關於這個解碼器的詳細信息,能夠參考這個類的java doc文檔,上邊講述的很詳細,簡單來講,就是咱們須要在請求中添加一些關於數據字段長度的信息(如下簡稱length),LengthFieldBasedFrameDecoder的構造方法中,設置一下關於length所佔的字節數和要跳過的字節數等信息(咱們只須要讀取數據內容的話,能夠跳過length的信息) 這樣的話,咱們從客戶端發送的每一個數據包,均可以正確地解析
首先,咱們增長一個編碼器,爲數據包附件上lenght:
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; //本類做用,加上四個字節的消息頭(int類型),表示數據包長度 public class LengthEncoder extends MessageToByteEncoder<ByteBuf> { @Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { int length = msg.readableBytes(); byte[] bytes = new byte[length]; msg.readBytes(bytes); out.writeInt(length); out.writeBytes(bytes); } }
而後,將咱們的編碼器和對應的 LengthFieldBasedFrameDecoder添加到服務器端和客戶端的ChannelInitializer裏邊去
修改以後的服務器端代碼:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; 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.LengthFieldBasedFrameDecoder; import java.net.InetSocketAddress; public class Server { public static void main(String[] args) throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(4096,0,4,0,4)); ch.pipeline().addLast(new LengthEncoder()); ch.pipeline().addLast(new ServerBusinessHanler()); } }); serverBootstrap.bind(new InetSocketAddress(8899)).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
new LengthFieldBasedFrameDecoder(4096,0,4,0,4));的意思是:使用LengthFieldBasedFrameDecoder的最大的數據包大小是4096個,其中length佔四個字節,讀取的時候跳過4個字節(也就是最終解碼的時候,忽略掉length字段,只返回真正的數據內容)
修改以後的客戶端代碼:
import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; public class Client { public static void main(String[] args) throws InterruptedException { EventLoopGroup eventExecutors = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(4096,0,4,0,4)); ch.pipeline().addLast(new LengthEncoder()); ch.pipeline().addLast(new ClientBusinessHandler()); } }); Channel channel = bootstrap.connect("127.0.0.1", 8899).channel(); channel.closeFuture().sync(); } }
分別運行服務器端和客戶端以後咱們獲得的結果:
服務器端輸出:
客戶端輸出:
這樣就解決了粘包和拆包致使的問題