第四章 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是个"流"协议,所谓 ...
随机推荐
- 批量设置ssh无密码登陆脚本
最近要给集群设置ssh无密码登陆,如果需要手动设置这个无密码登陆,所以在网上找了几个脚本,亲测下面这个好使,并且设置比较简单. 需要用root账户执行,我也是要给root账户设置无密码登陆. 首先我们 ...
- Spring面试问题集锦
Q. 对于依赖倒置原则(Dependency Inversion Principle,DIP),依赖注入(Dependency Injection,DI)和控制反转(Inversion of Cont ...
- Java:集合与数组转换
List,Set转换为数组的方法. toArray函数有两种形式,一种无参数,一种带参数,注意带参数形式中,要指明数组的大小. public void convertCollectionToArray ...
- 【转】【Mac】invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library
异常原因 我在昨天升级了 macOX Sierra,悲剧的是,今天我发现git命令无法执行,homebrew也无法使用,这种情景我在升级OS X El Capitan也遇到过一次,完整异常提示如下: ...
- [hadoop读书笔记] 第五章 MapReduce工作机制
P205 MapReduce的两种运行机制 第一种:经典的MR运行机制 - MR 1 可以通过一个简单的方法调用来运行MR作业:Job对象上的submit().也可以调用waitForCompleti ...
- [转]Android WiFi 掉线原因分析
看到一个比较详细的分析wifi断开的文章.收藏一下. 原文: http://blog.csdn.net/chi_wy/article/details/50963279 原因1 .从Log分析来看,这个 ...
- 64位debian系统下安装inodeClient
linux下的inodeClient下载: 链接:http://pan.baidu.com/s/1jIoX6Zk 密码:vnws 里面包括一份说明书,一个32位的,一个64位的文件: 对于64位的客户 ...
- 第三百五十节,Python分布式爬虫打造搜索引擎Scrapy精讲—selenium模块是一个python操作浏览器软件的一个模块,可以实现js动态网页请求
第三百五十节,Python分布式爬虫打造搜索引擎Scrapy精讲—selenium模块是一个python操作浏览器软件的一个模块,可以实现js动态网页请求 selenium模块 selenium模块为 ...
- C# 一个多层循环中,break一次能跳出几个循环?
public class BreakTest { public static void main(String[] args) { ; x<; x++) { ; y<; y++) { ; ...
- 获取 SharpSvn 执行 svn 操作的实时日志
1 获取 SharpSvn 操作日志的方式 之前一篇随笔(使用 SharpSvn 执行 svn 操作)讲到可以通过声称一个绑定到一个 SvnClient 对象的 SvnClientReport 对象. ...