TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用如下4种方式。

(1)消息长度固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报;

(2)将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛;

(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符;

(4)通过在消息头中定义长度字段来标识消息的总长度。

Netty对上面四种应用做了统一的抽象,提供了4种解码器来解决对应的问题,使用起来非常方便。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑TCP的粘包和拆包。

两种实用的解码器——DelimiterBasedFrameDecoder和FixedLengthFrameDecoder,前者可以自动完成以分隔符做结束标志的消息的解码,后者可以自动完成对定长消息的解码,它们都能解决TCP粘包/拆包导致的读半包问题。

DelimiterBasedFrameDecoder应用开发

演示程序以经典的Echo服务为例。EchoServer接收到EchoClient的请求消息后,将其打印出来,然后将原始消息返回给客户端,消息以“$_”作为分隔符。

服务端示例:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; public class EchoServer { public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
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 ChildChannelHandler());
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync(); // 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} private class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(Channel arg0) throws Exception {
//首先创建分隔符缓冲对象ByteBuf,本例程中使用“$_”作为分隔符。
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
//创建DelimiterBasedFrameDecoder对象,将其加入到ChannelPipeline中。
//DelimiterBasedFrameDecoder有多个构造方法,这里我们传递两个参数,
//第一个1024表示单条消息的最大长度,当达到该长度后仍然没有查找到分隔符,
//就抛出TooLongFrame Exception异常,防止由于异常码流缺失分隔符导致的内存溢出,
//这是Netty解码器的可靠性保护;第二个参数就是分隔符缓冲对象。
arg0.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
arg0.pipeline().addLast(new StringDecoder());
arg0.pipeline().addLast(new EchoServerHandler());
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
} import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; @ChannelHandler.Sharable
public class EchoServerHandler extends ChannelHandlerAdapter { int counter = 0; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//直接将接收的消息打印出来,由于DelimiterBasedFrameDecoder自动对请求消息进行了解码
//后续的ChannelHandler接收到的msg对象就是个完整的消息包;
//第二个ChannelHandler是StringDecoder,它将ByteBuf解码成字符串对象
//第三个EchoServerHandler接收到的msg消息就是解码后的字符串对象。
String body = (String) msg;
System.out.println("This is " + ++counter + " times receive client : ["+ body + "]");
body += "$_";
//由于我们设置DelimiterBasedFrameDecoder过滤掉了分隔符,
//所以,返回给客户端时需要在请求消息尾部拼接分隔符“$_”,
//最后创建ByteBuf,将原始消息重新返回给客户端。
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 发生异常,关闭链路
}
}

客户端示例:

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class EchoClient { public void connect(int port, String host) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(Channel ch)
throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoClientHandler());
}
}); // 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync(); // 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoClient().connect(port, "127.0.0.1");
}
} import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; public class EchoClientHandler extends ChannelHandlerAdapter { private int counter; static final String ECHO_REQ = "Hi, Netty. Welcome to Netty.$_"; public EchoClientHandler() {
} @Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));
}
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("This is " + ++counter + " times receive server : ["
+ msg + "]");
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

运行结果:

服务端

This is 1 times receive client : [Hi, Netty. Welcome to Netty.]
...............................
This is 10 times receive client : [Hi, Netty. Welcome to Netty.]

客户端

This is 1 times receive client : [Hi, Netty. Welcome to Netty.]
...............................
This is 10 times receive client : [Hi, Netty. Welcome to Netty.]

FixedLengthFrameDecoder应用开发

FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包问题,非常实用。

利用FixedLengthFrameDecoder解码器,无论一次接收到多少数据报,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。

服务端示例:

在服务端的ChannelPipeline中新增FixedLengthFrameDecoder,长度设置为20,然后再依次增加字符串解码器和EchoServerHandler

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
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; public class EchoServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
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() {
@Override
public void initChannel(Channel ch)throws Exception {
ch.pipeline().addLast( new FixedLengthFrameDecoder(20));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoServerHandler());
}
}); // 绑定端口,同步等待成功
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 = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
} import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; @ChannelHandler.Sharable
public class EchoServerHandler extends ChannelHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Receive client : [" + msg + "]");
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 发生异常,关闭链路
}
}

客户端示例:

  1. telnet localhost 8080
  2. 通过set localecho命令打开本地回显功能
➜  zcy-fixed git:(feature/transaction) ✗ telnet localhost 8080
Trying ::1...
Connected to localhost.
Escape character is '^]'.
12
123456789012345678

//连接日志
10:10:59.755 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x979fc7a5, /0:0:0:0:0:0:0:0:8080] RECEIVED: [id: 0x79dc7a78, /0:0:0:0:0:0:0:1:50749 => /0:0:0:0:0:0:0:1:8080]
Receive client : []
Receive client : [12
1234567890123456]

TCP拆包粘包之分隔符解码器的更多相关文章

  1. tomcat Http11NioProtocol如何解析http请求及如何解决TCP拆包粘包

    前言 tomcat是常用的Web 应用服务器,目前国内有很多文章讲解了tomcat架构,请求流程等,但是没有如何解析http请求及如何解决TCP粘包拆包,所以这篇文章的目的就是介绍这块内容,一下内容完 ...

  2. 架构师养成记--20.netty的tcp拆包粘包问题

    问题描述 比如要发ABC DEFG HIJK 这一串数据,其中ABC是一个包,DEFG是一个包,HIJK是一个包.由于TCP是基于流发送的,所以有可能出现ABCD EFGH 这种情况,那么ABC和D就 ...

  3. Netty—TCP的粘包和拆包问题

    一.前言 虽然TCP协议是可靠性传输协议,但是对于TCP长连接而言,对于消息发送仍然可能会发生粘贴的情形.主要是因为TCP是一种二进制流的传输协议,它会根据TCP缓冲对包进行划分.有可能将一个大数据包 ...

  4. 关于TCP的粘包和拆包

    问题产生 一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题. 下面可以看一张图,是客户端向服务端发送包: 1. 第一种情况 ...

  5. Netty 拆包粘包和服务启动流程分析

    Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你能掌握EventLoopGroup的工作流程,ServerBootstrap的启动流程,ChannelPipeline是如何操作管理Ch ...

  6. Netty_TCP拆包粘包解决方案

    一.问题 熟悉tcp编程的可能都知道,无论是服务器端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包/拆包机制. TCP是一个“流”协议,所谓流就是没有界限的遗传数据,大家可以想象 ...

  7. 【转】Netty 拆包粘包和服务启动流程分析

    原文:https://www.cnblogs.com/itdragon/archive/2018/01/29/8365694.html Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你 ...

  8. 使用Netty如何解决拆包粘包的问题

    首先,我们通过一个DEMO来模拟TCP的拆包粘包的情况:客户端连续向服务端发送100个相同消息.服务端的代码如下: AtomicLong count = new AtomicLong(0); NioE ...

  9. python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

随机推荐

  1. Mathematics:Find a multiple(POJ 2356)

    找组合 题目大意:给你N个自然数,请你求出若干个数的组合的和为N的整数倍的数 经典鸽巢原理题目,鸽巢原理的意思是,有N个物品,放在N-1个集合中,则一定存在一个集合有2个元素或以上. 这一题是说有找出 ...

  2. codeforces 515B. Drazil and His Happy Friends 解题报告

    题目链接:http://codeforces.com/problemset/problem/515/B 题目意思:有 n 个 boy 和 m 个 girl,有 b 个 boy 和 g 个 girl ( ...

  3. JS中数组Array的用法示例介绍 (转)

    new Array() new Array(len) new Array([item0,[item1,[item2,...]]] 使用数组对象的方法: var objArray=new Array() ...

  4. js中masonry与infinitescroll结合 形成瀑布流

    后台:(有点问题 page应该从1开始 而不是从0开始)     public function actionExperts()    {        $top=5;        $page=em ...

  5. Tmux的安装、使用与配置

    tmux 安装.使用.配置 因上线需求,故需要使用tumx,方便上线 tmux功能 提供了强大的.易于使用的命令行界面 可横向.纵向分割窗口 窗格可以自由移动和调整大小,或者直接利用四个预设布局之一 ...

  6. 比较原声socket 、GCDAsyncSocket

    原声socket NSInputStream 输入流(OC)NSOutputStream 输出流(OC)1:通过c语言的输入输出流CFReadStreamRef/CFWirteStreamRef(输入 ...

  7. iOS 访问粘贴板

    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = self.label.text;

  8. core

    http://blog.csdn.net/xiaoxiaoniaoer1/article/details/7740820 1.core文件的生成开关和大小限制--------------------- ...

  9. Java实现颜色渐变效果

    RGB色彩,在自然界中肉眼所能看到的任何色彩都可以由红(R).绿(G).蓝(B)这三种色彩混合叠加而成,因此我们只要递增递减的修改其特定值就能得到相应的渐变效果. 运行效果:(图1) 运行5秒后:(图 ...

  10. Swift - 推送之本地推送(UILocalNotification)添加Button的点击事件

    上一篇讲到的本地推送是普通的消息推送,本篇要讲一下带按钮动作的推送消息 import UIKit @UIApplicationMain class AppDelegate: UIResponder, ...