Netty5入門學習筆記002-TCP粘包/拆包問題的解決之道(上)

TCP網絡通訊時候會發生粘包/拆包的問題,接下來探討其解決之道。java

什麼是粘包/拆包編程

通常所謂的TCP粘包是在一次接收數據不能徹底地體現一個完整的消息數據。TCP通信爲什麼存在粘包呢?主要緣由是TCP是以流的方式來處理數據,再加上網絡上MTU的每每小於在應用處理的消息數據,因此就會引起一次接收的數據沒法知足消息的須要,致使粘包的存在。處理粘包的惟一方法就是制定應用層的數據通信協議,經過協議來規範現有接收的數據是否知足消息數據的須要。bootstrap

狀況分析服務器

TCP粘包一般在流傳輸中出現,UDP則不會出現粘包,由於UDP有消息邊界,發送數據段須要等待緩衝區滿了纔將數據發送出去,當滿的時候有可能不是一條消息而是幾條消息合併在換中去內,在成粘包;另外接收數據端沒能及時接收緩衝區的包,形成了緩衝區多包合併接收,也是粘包。網絡

解決辦法app

一、消息定長,報文大小固定長度,不夠空格補全,發送和接收方遵循相同的約定,這樣即便粘包了經過接收方編程實現獲取定長報文也能區分。
socket

二、包尾添加特殊分隔符,例如每條報文結束都添加回車換行符(例如FTP協議)或者指定特殊字符做爲報文分隔符,接收方經過特殊分隔符切分報文區分。ide

三、將消息分爲消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段oop

四、更復雜的自定義應用層協議大數據

代碼例子

一、Netty中提供了FixedLengthFrameDecoder定長解碼器能夠幫助咱們輕鬆實現第一種解決方案,定長解碼報文。

服務器端:

package im;

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;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
 * 定長解碼  服務器端
 * @author xwalker
 */
public class Server {
	
	public void bind(int port) throws Exception{
		//接收客戶端鏈接用
		EventLoopGroup bossGroup=new NioEventLoopGroup();
		//處理網絡讀寫事件
		EventLoopGroup workerGroup=new NioEventLoopGroup();
		try{
		//配置服務器啓動類	
		ServerBootstrap b=new ServerBootstrap();
		b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
		.handler(new LoggingHandler(LogLevel.INFO))//配置日誌輸出
		.childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器 長度設置爲30
				ch.pipeline().addLast(new StringDecoder());//設置字符串解碼器 自動將報文轉爲字符串
				ch.pipeline().addLast(new Serverhandler());//處理網絡IO 處理器
			}
		});
		//綁定端口 等待綁定成功
		ChannelFuture f=b.bind(port).sync();
		//等待服務器退出
		f.channel().closeFuture().sync();
		}finally{
			//釋放線程資源
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}
	public static void main(String[] args) throws Exception {
		int port=8000;
		new Server().bind(port);
	}

}

package im;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
 * 服務器handler
 * @author xwalker
 */
public class Serverhandler extends ChannelHandlerAdapter {
	int counter=0;
	private static final String MESSAGE="It greatly simplifies and streamlines network programming such as TCP and UDP socket server.";
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		System.out.println("接收客戶端msg:["+msg+"]");
		ByteBuf echo=Unpooled.copiedBuffer(MESSAGE.getBytes());
		ctx.writeAndFlush(echo);
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

}

客戶端:

package im;

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
 * 客戶端 
 * @author xwalker
 *
 */
public class Client {
	/**
	 * 連接服務器
	 * @param port
	 * @param host
	 * @throws Exception
	 */
	public void connect(int port,String host)throws Exception{
		//網絡事件處理線程組
		EventLoopGroup group=new NioEventLoopGroup();
		try{
		//配置客戶端啓動類
		Bootstrap b=new Bootstrap();
		b.group(group).channel(NioSocketChannel.class)
		.option(ChannelOption.TCP_NODELAY, true)//設置封包 使用一次大數據的寫操做,而不是屢次小數據的寫操做
		.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器
				ch.pipeline().addLast(new StringDecoder());//設置字符串解碼器
				ch.pipeline().addLast(new ClientHandler());//設置客戶端網絡IO處理器
			}
		});
		//鏈接服務器 同步等待成功
		ChannelFuture f=b.connect(host,port).sync();
		//同步等待客戶端通道關閉
		f.channel().closeFuture().sync();
		}finally{
			//釋放線程組資源
			group.shutdownGracefully();
		}
	}
	public static void main(String[] args) throws Exception {
		int port=8000;
		new Client().connect(port, "127.0.0.1");

	}

}

package im;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
 * 客戶端處理器
 * @author xwalker
 *
 */
public class ClientHandler extends ChannelHandlerAdapter {
	private static final String MESSAGE="Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.";
	public ClientHandler(){}
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
			ctx.writeAndFlush(Unpooled.copiedBuffer(MESSAGE.getBytes()));
	}
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		System.out.println("接收服務器響應msg:["+msg+"]");
	}
	
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

服務器和客戶端分別設置了定長解碼器 長度爲30字節,也就是規定發送和接收一次報文定長爲30字節。

運行結果:

客戶端接收到服務器的響應報文 一段文字被定長分紅若干段接收。

服務器端接收客戶端發送的報文 一段話也是分紅了等長的若干段。

上述是一個簡單長字符串傳輸例子,將一個長字符串分割成若干段。咱們也能夠自定義一系列定長的指令發送出去

例如指令長度都是30個字節,批量發出N條指令,這樣客戶端粘包後發出一個比較大的數據指令集,服務器接收到的數據在緩衝區內,只須要按照定長一個個指令取出來執行便可。


JFinal經典入門到精通課程

相關文章
相關標籤/搜索