第四章 TCP粘包/拆包问题的解决之道---4.2--- 未考虑TCP粘包导致功能异常案例
4.2 未考虑TCP粘包导致功能异常案例
如果代码没有考虑粘包/拆包问题,往往会出现解码错位或者错误,导致程序不能正常工作。
4.2.1 TimeServer 的改造
Class : TimeServer
package com.phei.netty.chap4; 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; public class TimeServer { public void bind(int port) throws Exception{
// 配置服务端的NIO线程组
// 创建两个NioEventLoopGroup实例。
// NioEventLoopGroup是个线程组,包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组。
// 一个用于服务端接收客户端的连接,一个用于进行SocketChannel的网络读写。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
// 创建ServerBootstrap对象,是Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。
ServerBootstrap b = new ServerBootstrap();
// ServerBootstrap的group方法将两个NIO线程组当作入参传递到ServerBootstrap中。、
// channel方法设置创建的Channel为NioServerSocketChannel,功能对应于JDK NIO类库中的ServerSocketChannel类。
// option方法配置NioServerSocketChannel的TCP参数,此处将它的backlog设置为1024。
// childHandler方法绑定I/O事件的处理类ChildChannelHandler,它的作用类似于Reactor模式中的Handler类,主要用于处理网络I/P事件,例如记录日志、对消息进行编解码等。
b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
// 服务端启动辅助类配置完成之后,调用bind方法绑定监听端口。
// 同步阻塞方法sync等待绑定操作完成,并返回一个ChannelFuture。
// ChannelFuture功能类似于JDK的java.util.concurrent.Future,主要用于异步操作的通知回调。
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
// 进行阻塞,等待服务端链路关闭之后main函数才退出。
f.channel().closeFuture().sync();
}finally{
// 优雅退出,释放线程资源
// 调用NIO线程组的shutdownGraceFully进行优雅退出,它会释放跟shutdownGracefully相关联的资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) throws Exception{
int port = 8080;
if(null != args && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
// 采用默认值
port = 8080;
}
}
new TimeServer().bind(port);
}
}
Class : TimeServerHandler
package com.phei.netty.chap4; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import java.util.Date; public class TimeServerHandler extends ChannelHandlerAdapter { private int counter; /* 每读到一条消息后,就计一次数,然后发送应答消息给客户端。
* 按照设计,服务端接到的消息总数应该跟客户端发送的消息总数相同,
* 而且请求消息删除回车换行符后应该为"QUERY TIME ORDER"。*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8").substring(0,req.length - System.getProperty("line.separator").length());
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
} }
4.2.2 TimeClient 的改造
Class : TimeClient
package com.phei.netty.chap4; 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; public class TimeClient { public void connect(int port,String host) throws Exception{
// 配置客户端NIO线程组
// 创建客户端处理I/O读写的NioEventLoopGroup线程组
EventLoopGroup group = new NioEventLoopGroup();
try{
// 创建客户端辅助启动类Bootstrap
Bootstrap b = new Bootstrap();
// 配置客户端辅助启动类
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true).handler(new ChannelInitializer<SocketChannel>(){ /* initChannel方法:当创建NioSocketChannel成功之后,进行初始化时
将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件*/
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
// 发起异步连接操作
// 调用connect方法发起异步连接,然后调用同步方法等待连接成功
ChannelFuture f = b.connect(host,port).sync();
// 等待客户端链路关闭
// 当客户端连接关闭之后,客户端主函数退出,退出之前释放NIO线程组的资源。
f.channel().closeFuture().sync();
}finally{
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception{
int port = 8080;
if(null != args && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
// 采用默认值
}
}
new TimeClient().connect(port, "127.0.0.1");
}
}
Class : TimeClientHandler
package com.phei.netty.chap4; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; public class TimeClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private int counter;
private byte[] req; public TimeClientHandler() {
req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
/* 客户端跟服务端链路建立成功之后,循环发100条消息,
* 每发送一条就刷新一次,保证每条消息都会被写入Channel中。*/
for(int i = 0;i < 100;i++){
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
/*客户端每接收到服务端一条应答消息之后,就打印一次计数器。*/
System.out.println("Now is : " + body + " ; the counter is : " + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
// 释放资源
logger.warning("Unexpected exception from downstream : " + cause.getMessage());
ctx.close();
} }
Console : 服务端
The time server receive order : QUERY TIME ORDER
此处省略55行QUERY TIME ORDER......
QUERY TIME ORD ; the counter is : 1
The time server receive order :
此处省略42行QUERY TIME ORDER......
QUERY TIME ORDER ; the counter is : 2
服务端运行结果表明它只接收到了两条消息,第一条包含57条”QUERY TIME ORDER“指令,第二条包含了43条”QUERY TIME ORDER”指令,总数正好是100条。我们期待的是收到100条消息,每条包含一条“QUERY TIME ORDER”指令。这说明发生了TCP粘包。
Console : 客户端
Now is : BAD ORDER
BAD ORDER
; the counter is : 1
按照设计初衷,客户端应该收到100条当前系统时间的消息,但实际上只收到了一条。这不难理解,因为服务端只收到了2条消息,所以实际服务端只发送了2条应答,由于请求消息不满足查询条件,所以返回了2条“BAD ORDER”应答消息。但实际上客户端只收到了一条包含2条“BAD ORDER”指令的消息,说明服务端返回的应答消息也发生了粘包。
可以通过Netty的LineBasedFrameDecoder和StringDecoder来解决TCP粘包问题。
啦啦啦
啦啦啦
第四章 TCP粘包/拆包问题的解决之道---4.2--- 未考虑TCP粘包导致功能异常案例的更多相关文章
- 第四章 TCP粘包/拆包问题的解决之道---4.1---
4.1 TCP粘包/拆包 TCP是一个“流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可 ...
- TCP粘包/拆包问题的解决
TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...
- (入门篇 NettyNIO开发指南)第四章-TIP黏包/拆包问题解决之道
熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- TCP粘包/拆包问题
无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...
- TCP粘包/拆包(Netty权威指南)
无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个“流”协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片 ...
- <Netty>(入门篇)TIP黏包/拆包问题原因及换行的初步解决之道
熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...
- TCP 粘包拆包
一.什么是粘包拆包? 粘包拆包是TCP协议传输中一种现象概念.TCP是传输层协议,他传输的是“流”式数据,TCP并不知道传输是哪种业务数据,或者说,并不关心.它只是根据缓冲区状况将数据进行包划分,然后 ...
- Netty2:粘包/拆包问题与使用LineBasedFrameDecoder的解决方案
什么是粘包.拆包 粘包.拆包是Socket编程中最常遇见的一个问题,本文来研究一下Netty是如何解决粘包.拆包的,首先我们从什么是粘包.拆包开始说起: TCP是个"流"协议,所谓 ...
随机推荐
- Extjs4.2 Grid搜索Ext.ux.grid.feature.Searching的使用
背景 Extjs4.2 默认提供的Search搜索,功能还是非常强大的,只是对于国内的用户来说,还是不习惯在每列里面单击好几下再筛选,于是相当当初2.2里面的搜索,更加的实用点,于是在4.2里面实现. ...
- css 可穿透
pointer-events : none; 引自:http://www.thinkphp.cn/topic/4702.html
- SpringBoot2 启动报错 Failed to auto-configure a DataSource
今天Spring Boot 2.0正式版发布,寻思着搭个小demo尝试一下Spring Boot的新特性,使用idea创建项目.在选择组件时添加了mysql.mybatis 然后在第一次启动的时候启动 ...
- php curl批处理--可控并发异步
通常情况下 PHP 中的 cURL 是阻塞运行的,就是说创建一个 cURL 请求以后必须等它执行成功或者超时才会执行下一个请求:API接口访问一般会首选CURL 在实际项目或者自己编写小工具(比如新闻 ...
- R语言比较运算符和逻辑运算符
比较运算:>,<,>=,<=,==,!=.(大于,小于,大于等于,小于等于,等于,不等于.) 逻辑运算:&,|,!.(与,或,非.)
- Ubuntu16.04安装Mininet
Ubuntu16.04源码安装Mininet 本文介绍了VMware虚拟机ubuntu16.04中安装Mininet的方法,物理机中的方法与之相同.主要参考了Mininet官方的教程.官方提供了四种安 ...
- OpenGL OBJ模型加载.
在我们前面绘制一个屋,我们可以看到,需要每个立方体一个一个的自己来推并且还要处理位置信息.代码量大并且要时间.现在我们通过加载模型文件的方法来生成模型文件,比较流行的3D模型文件有OBJ,FBX,da ...
- android wifi RSSI达到阈值自动断开
设置wifi的RSSI达到阈值之后自动断开. wifi状态改变,会更新状态栏,在状态栏中更改. --- a/packages/SystemUI/src/com/android/systemui/sta ...
- Prolog学习:用八卦的精神走进Prolog
最近枕头书是<七周七语言:理解多种编程范型>这本,前面两章分别看了Ruby和IO,都是命令式语言.虽然它们在语法上跟之前接触过的C,C#,Java这些C家族的语言差别很大,但是编程范型却是 ...
- struts2系列(一):struts2入门(struts2的产生、struts2的工作流程、搭建struts2开发环境)
一. struts2的产生 struts1的缺点: 1. ActionForm过多,而且这个ActionForm在很大程度上又和VO(POJO)重复 ...